Main Page · Modules · All Classes · Class Hierarchy
MCDataStorage.cpp
1 /*
2  * This file is part of the AiBO+ project
3  *
4  * Copyright (C) 2005-2016 Csaba Kertész (csaba.kertesz@gmail.com)
5  *
6  * AiBO+ is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * AiBO+ is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
19  *
20  */
21 
22 #include "MCDataStorage.hpp"
23 
24 #include "MCBinaryData.hpp"
25 #include "MCDataContainer.hpp"
26 #include "MCLog.hpp"
27 #include "MCThreadLocalData.hpp"
28 #include "MCZipper.hpp"
29 
30 #include <boost/archive/binary_iarchive.hpp>
31 #include <boost/archive/binary_oarchive.hpp>
32 #include <boost/iostreams/device/back_inserter.hpp>
33 #include <boost/iostreams/stream.hpp>
34 
35 #include <fstream>
36 
37 namespace
38 {
39 // Verbose mode
40 MCThreadLocalData<bool> Verbose(true);
41 
42 void CheckStaticDataStorageVariables()
43 {
44  if (unlikely(!Verbose.get()))
45  {
46  Verbose.reset(new bool);
47  *Verbose = false;
48  }
49 }
50 }
51 
52 std::string MCDataStorage::VersionID = "DataStorage 1.00";
53 
54 MCDataStorage::MCDataStorage(const std::string& name, bool portable) : Name(name),
55  Portable(portable)
56 {
57  CheckStaticDataStorageVariables();
58  if (unlikely(IsVerbose()))
59  {
60  MC_LOG("Storage: %s - ctor", Name.c_str());
61  }
62 }
63 
64 
65 MCDataStorage::~MCDataStorage()
66 {
67  if (unlikely(IsVerbose()))
68  {
69  MC_LOG("Storage: %s - dtor", Name.c_str());
70  }
71  Clear();
72 }
73 
74 
76 {
77  return Portable;
78 }
79 
80 
82 {
83  return DataContainers.empty();
84 }
85 
86 
87 std::string MCDataStorage::GetName() const
88 {
89  return Name;
90 }
91 
92 
93 void MCDataStorage::SetName(const std::string& name)
94 {
95  Name = name;
96 }
97 
98 
100 {
101  MC::StringList ContainerNames = GetContainerNames();
102 
103  printf("Containers (%d): ", (int)ContainerNames.size());
104  for (unsigned int i = 0; i < ContainerNames.size(); ++i)
105  printf("%s, ", ContainerNames[i].c_str());
106  printf("\n");
107 }
108 
109 
110 bool MCDataStorage::LoadFromFile(const std::string& file_name)
111 {
112  MC_TRY_BEGIN
113  std::ifstream InputFileStream(file_name.c_str(), std::ios::binary);
114 
115  if (!InputFileStream.is_open())
116  {
117  MC_WARNING("Unable to open file to load binary data: %s", file_name.c_str());
118  return false;
119  }
120  InputFileStream.seekg(0, std::ios::end);
121  int FileSize = (int)InputFileStream.tellg();
122 
123  InputFileStream.seekg(0, std::ios::beg);
124  LoadFromInputStream(InputFileStream, FileSize);
125  InputFileStream.close();
126  MC_CATCH_BEGIN
127  MC_WARNING("Incompatible encoded data storage file (%s)", file_name.c_str());
128  return false;
129  MC_CATCH_END
130  return true;
131 }
132 
133 
134 bool MCDataStorage::SaveToFile(const std::string& file_name)
135 {
136  MC_TRY_BEGIN
137  std::ofstream OutputFileStream(file_name.c_str(), std::ios::out | std::ios::binary);
138 
139  if (!OutputFileStream.is_open())
140  {
141  MC_WARNING("Unable to create file to save binary data: %s", file_name.c_str());
142  return false;
143  }
144  SaveToOutputStream(OutputFileStream);
145  OutputFileStream.flush();
146  OutputFileStream.close();
147  MC_CATCH_BEGIN
148  MC_WARNING("Unable to save encoded data storage file (%s)", file_name.c_str());
149  return false;
150  MC_CATCH_END
151  return true;
152 }
153 
154 
156 {
157  MCDataStorage* DataStorage = new MCDataStorage("", portable);
158 
159  MC_TRY_BEGIN
160  boost::iostreams::basic_array_source<char> Source((char*)data.GetData(), (std::size_t)data.GetSize());
161  boost::iostreams::stream<boost::iostreams::basic_array_source<char> > InStream(Source);
162 
163  DataStorage->LoadFromInputStream(InStream, data.GetSize());
164  MC_CATCH_BEGIN
165  MC_WARNING("Incompatible encoded data storage version");
166  delete DataStorage;
167  return nullptr;
168  MC_CATCH_END
169  return DataStorage;
170 }
171 
172 
174 {
175  // Huh, idea is from:
176  // http://boost.2283326.n4.nabble.com/in-memory-stream-binary-archive-serialization-td2578880.html
177  std::vector<char> TempBuffer;
178 
179  boost::iostreams::stream<boost::iostreams::back_insert_device<std::vector<char> > > OutStream(TempBuffer);
180 
181  SaveToOutputStream(OutStream);
182  MCBinaryData* BinaryData = new MCBinaryData((int)TempBuffer.size());
183 
184  memcpy(BinaryData->GetData(), &TempBuffer[0], TempBuffer.size());
185  return BinaryData;
186 }
187 
188 
189 void MCDataStorage::LoadFromInputStream(std::istream& input_stream, unsigned int max_bytes)
190 {
191  eos::portable_iarchive* InputPArchive = nullptr;
192  boost::archive::binary_iarchive* InputArchive = nullptr;
193  std::string DataStorageID;
194 
195  if (Portable)
196  {
197  InputPArchive = new eos::portable_iarchive(input_stream);
198  *InputPArchive & DataStorageID;
199  } else {
200  InputArchive = new boost::archive::binary_iarchive(input_stream);
201  *InputArchive & DataStorageID;
202  }
203  if (DataStorageID != VersionID)
204  {
205  MC_WARNING("The archive is not a data storage.");
206  delete InputPArchive;
207  delete InputArchive;
208  return;
209  }
210  if (InputArchive)
211  {
212  *InputArchive & Name;
213  } else {
214  *InputPArchive & Name;
215  }
216  Clear();
217  while (input_stream.tellg() < max_bytes)
218  {
219  std::string ContainerName;
220  MCBinaryData* ContainerData = new MCBinaryData;
221  bool Compressed = false;
222 
223  if (InputArchive)
224  {
225  *InputArchive & ContainerName;
226  // cppcheck-suppress clarifyCondition
227  *InputArchive & Compressed;
228  *InputArchive & *ContainerData;
229  } else {
230  *InputPArchive & ContainerName;
231  // cppcheck-suppress clarifyCondition
232  *InputPArchive & Compressed;
233  *InputPArchive & *ContainerData;
234  }
235  if (Compressed)
236  {
237  MCBinaryData* OutputData = new MCBinaryData;
238 
239  // The best compromise is the zlib (fast) method because it is pretty quick for
240  // data containers and it compresses best over every algorithms except bzip2.
241  // See more in comments of SaveToOutputStream()
242  if (MCZipper::DetectCompressionMethod(*ContainerData, MC::ZlibCompression) == MC::ZlibCompression)
243  {
244  MCZipper::DecompressZlib(*ContainerData, *OutputData);
245  } else {
246  // This is left for backward compatibility of old saved sessions (it can lead to crashes)
247  MCZipper::DecompressQuickLz(*ContainerData, *OutputData, true);
248  }
249  // Drop the malformed containers otherwise crash is very likely later
250  if (OutputData->GetSize() > 0)
251  BinaryContainers.insert(std::make_pair(ContainerName, OutputData));
252  delete ContainerData;
253  ContainerData = nullptr;
254  } else {
255  BinaryContainers.insert(std::make_pair(ContainerName, ContainerData));
256  }
257  }
258  delete InputPArchive;
259  delete InputArchive;
260 }
261 
262 
263 void MCDataStorage::SaveToOutputStream(std::ostream& output_stream)
264 {
265  eos::portable_oarchive* OutputPArchive = nullptr;
266  boost::archive::binary_oarchive* OutputArchive = nullptr;
267 
268  if (Portable)
269  {
270  OutputPArchive = new eos::portable_oarchive(output_stream);
271  *OutputPArchive & VersionID;
272  *OutputPArchive & Name;
273  } else {
274  OutputArchive = new boost::archive::binary_oarchive(output_stream);
275  *OutputArchive & VersionID;
276  *OutputArchive & Name;
277  }
278  // Encode the containers again
279  for (auto& container : DataContainers)
280  {
281  bool Encoded = false;
282 
283  for (auto& container2 : BinaryContainers)
284  {
285  if (container.first == container2.first)
286  {
287  Encoded = true;
288  delete container2.second;
289  container2.second = container.second->Encode();
290  break;
291  }
292  }
293  if (!Encoded)
294  {
295  BinaryContainers.insert(std::make_pair(container.first, container.second->Encode()));
296  }
297  }
298  // Write data containers
299  for (auto& container : BinaryContainers)
300  {
301  // Compress the data if a container size is bigger than 2 KB
302  MCBinaryData* EncodedData = container.second;
303  bool Compressing = EncodedData->GetSize() > 2000;
304 
305  if (OutputArchive)
306  {
307  *OutputArchive & container.first;
308  // cppcheck-suppress clarifyCondition
309  *OutputArchive & Compressing;
310  } else {
311  *OutputPArchive & container.first;
312  // cppcheck-suppress clarifyCondition
313  *OutputPArchive & Compressing;
314  }
315  if (Compressing)
316  {
317  MCBinaryData CompressedData;
318 
319  /*
320  * The best compromise is the zlib (fast) method because it is pretty quick for
321  * data containers and it compresses best over every algorithms except bzip2.
322  *
323  * Compression size and speed (Lz4/Medium): 7110 bytes, 526320 bytes / msec
324  * Decompression speed (Lz4/Medium): 547373 bytes / msec
325  * Compression size and speed (Zlib/Fast): 4443 bytes, 123282 bytes / msec
326  * Decompression speed (Zlib/Fast): 234588 bytes / msec
327  * Compression size and speed (Zlib/Medium): 3714 bytes, 43259 bytes / msec
328  * Decompression speed (Zlib/Medium): 295345 bytes / msec
329  * Compression size and speed (Zlib/Best): 3640 bytes, 36426 bytes / msec
330  * Decompression speed (Zlib/Best): 235936 bytes / msec
331  * Compression size and speed (QuickLZ/Medium): 5684 bytes, 37871 bytes / msec
332  * Decompression speed (QuickLZ/Medium): 912288 bytes / msec
333  * Compression size and speed (MiniLzo/Medium): 6076 bytes, 494614 bytes / msec
334  * Decompression speed (MiniLzo/Medium): 746418 bytes / msec
335  * Compression size and speed (Lz4/Medium): 6576 bytes, 366544 bytes / msec
336  * Decompression speed (Lz4/Medium): 1001292 bytes / msec
337  * Compression size and speed (Snappy/Medium): 8026 bytes, 331072 bytes / msec
338  * Decompression speed (Snappy/Medium): 720228 bytes / msec
339  * Compression size and speed (Bzip2/Fast): 2266 bytes, 1956 bytes / msec
340  * Decompression speed (Bzip2/Fast): 35299 bytes / msec
341  * Compression size and speed (Bzip2/Medium): 2266 bytes, 1962 bytes / msec
342  * Decompression speed (Bzip2/Medium): 31848 bytes / msec
343  * Compression size and speed (Bzip2/Best): 2266 bytes, 2173 bytes / msec
344  * Decompression speed (Bzip2/Best): 49224 bytes / msec
345  */
346  MCZipper::CompressZlib(*EncodedData, CompressedData, MC::BestCompression);
347  if (OutputArchive)
348  {
349  *OutputArchive & CompressedData;
350  } else {
351  *OutputPArchive & CompressedData;
352  }
353  } else {
354  if (OutputArchive)
355  {
356  *OutputArchive & *EncodedData;
357  } else {
358  *OutputPArchive & *EncodedData;
359  }
360  }
361  }
362  delete OutputPArchive;
363  delete OutputArchive;
364 }
365 
366 
368 {
369  return MCDataContainer::Decode(data, Portable);
370 }
371 
372 
373 MC::StringList MCDataStorage::GetContainerNames() const
374 {
375  MC::StringList FinalNames = MCGetContainerKeys<std::string>(DataContainers);
376  MC::StringList OtherNames = MCGetContainerKeys<std::string>(BinaryContainers);
377 
378  for (auto& name : OtherNames)
379  {
380  if (!MCContainerContains(FinalNames, name))
381  {
382  FinalNames.push_back(name);
383  }
384  }
385  // Guarantee the sorted container name list
386  std::sort(FinalNames.begin(), FinalNames.end());
387  return FinalNames;
388 }
389 
390 
391 MC::DataContainerSPtr MCDataStorage::CreateContainer(const std::string& container_name)
392 {
393  for (auto& container : DataContainers)
394  {
395  if (container.first == container_name)
396  {
397  MC_WARNING("Can't add new container with the same name (%s)", container_name.c_str());
398  return MC::DataContainerSPtr();
399  }
400  }
401  for (auto& container : BinaryContainers)
402  {
403  if (container.first == container_name)
404  {
405  MC_WARNING("Can't add new container with the same name (%s)", container_name.c_str());
406  return MC::DataContainerSPtr();
407  }
408  }
409  MC::DataContainerSPtr NewContainer(new MCDataContainer(container_name, Portable));
410 
411  DataContainers.insert(std::make_pair(container_name, NewContainer));
412  return NewContainer;
413 }
414 
415 
416 MC::DataContainerSPtr MCDataStorage::GetContainer(const std::string& container_name)
417 {
418  // Get a data container
419  for (auto& container : DataContainers)
420  {
421  if (container.first == container_name)
422  return container.second;
423  }
424  // Decode and return a data container
425  for (auto& container : BinaryContainers)
426  {
427  if (container.first == container_name)
428  {
429  MC::DataContainerSPtr DataContainer(DecodeContainer(*container.second));
430 
431  return DataContainers.insert(std::make_pair(container.first, DataContainer)).first->second;
432  }
433  }
434  return MC::DataContainerSPtr();
435 }
436 
437 
439 {
440  for (auto iter = DataContainers.begin(); iter != DataContainers.end();)
441  {
442  if (iter->second.use_count() == 1)
443  {
444  bool Encoded = false;
445 
446  for (auto& container : BinaryContainers)
447  {
448  if (iter->first == container.first)
449  {
450  Encoded = true;
451  delete container.second;
452  container.second = iter->second->Encode();
453  break;
454  }
455  }
456  if (!Encoded)
457  {
458  BinaryContainers.insert(std::make_pair(iter->first, iter->second->Encode()));
459  }
460  if (unlikely(IsVerbose()))
461  {
462  MC_LOG("Storage: %s - drop container: %s", Name.c_str(), iter->first.c_str());
463  }
464  // Note: Erasing from map/multimap while iterating: see http://stackoverflow.com/questions/446205
465  DataContainers.erase(iter++);
466  } else {
467  ++iter;
468  }
469  }
470 }
471 
472 
473 void MCDataStorage::RemoveContainer(const std::string& container_name)
474 {
475  for (auto iter = DataContainers.begin(); iter != DataContainers.end(); ++iter)
476  {
477  if (iter->first == container_name)
478  {
479  if (unlikely(IsVerbose()))
480  {
481  MC_LOG("Storage: %s - remove container: %s", Name.c_str(), iter->first.c_str());
482  }
483  DataContainers.erase(iter);
484  break;
485  }
486  }
487  DataContainers.clear();
488  for (auto iter = BinaryContainers.begin(); iter != BinaryContainers.end(); ++iter)
489  {
490  if (iter->first == container_name)
491  {
492  if (unlikely(IsVerbose()))
493  {
494  MC_LOG("Storage: %s - remove container (binary): %s", Name.c_str(), iter->first.c_str());
495  }
496  delete iter->second;
497  BinaryContainers.erase(iter);
498  break;
499  }
500  }
501 }
502 
503 
505 {
506  if (unlikely(IsVerbose()))
507  {
508  MC_LOG("Storage: %s - clear", Name.c_str());
509  }
510  DataContainers.clear();
511  for (auto& container : BinaryContainers)
512  {
513  delete container.second;
514  }
515  BinaryContainers.clear();
516 }
517 
518 
519 void MCDataStorage::SetVerbose(bool new_state)
520 {
521  CheckStaticDataStorageVariables();
522  *Verbose = new_state;
523 }
524 
525 
527 {
528  CheckStaticDataStorageVariables();
529  return *Verbose;
530 }
bool MCContainerContains(const U &container, const T &value)
Check if a container contains a value.
MCBinaryData * Encode()
Save the data storage into binary form.
void RemoveContainer(const std::string &container_name)
Remove a container from the data storage.
MC::DataContainerSPtr CreateContainer(const std::string &container_name)
Create and add a container to the data storage.
Data container.
Binary data class.
bool IsCompact() const
Check if the data storage is compact.
void LoadFromInputStream(std::istream &input_stream, unsigned int max_bytes)
Load the data storage from a stream.
bool Portable
Data storage type (portable/non-portable)
#define MC_WARNING(...)
Warning macro.
Definition: MCLog.hpp:43
static bool IsVerbose()
Get the verbosity of the data storage domain.
static MCDataStorage * Decode(const MCBinaryData &data, bool portable=false)
Load the data storage from binary form.
void Clear()
Clear the data storage.
MC::DataContainerSPtr GetContainer(const std::string &container_name)
Get a container from the data storage.
static void DecompressQuickLz(const MCBinaryData &input_buffer, MCBinaryData &output_buffer, bool without_header=false)
Decompress data with QuickLZ algorithm.
Definition: MCZipper.cpp:106
static void SetVerbose(bool new_state)
Set the verbosity of the data storage domain.
MC::StringList GetContainerNames() const
Get the container names in the data storage.
void SetName(const std::string &name)
Set the data storage name.
Data storage with file support.
std::string Name
Storage name.
bool IsPortable() const
Check if the data storage is portable.
MC::StrDataContainerSPtrMap DataContainers
Data containers.
void DumpContainerNames() const
Dump container names.
static void DecompressZlib(const MCBinaryData &input_buffer, MCBinaryData &output_buffer)
Decompress data with zlib algorithm.
Definition: MCZipper.cpp:445
static MCDataContainer * Decode(const MCBinaryData &data, bool portable=false, MCDataContainer *instance=nullptr)
Load the data container from binary form.
bool SaveToFile(const std::string &file_name)
Save the data storage to a file.
void OptimizeCachedContainers()
Optimize cached containers.
unsigned char * GetData() const
Get direct access to the binary data.
MCDataStorage(const std::string &name, bool portable=false)
Class constructor.
MC::StrBinaryDataPtrMap BinaryContainers
Data containers in binary form.
A wrapper class to cover boost::thread_specific_ptr/folly::ThreadLocal API on certain targets...
static MC::CompressionsType DetectCompressionMethod(const MCBinaryData &input_buffer, MC::CompressionsType compression=MC::InvalidCompression)
Detect the compression method of a buffer.
Definition: MCZipper.cpp:628
std::string GetName() const
Get the data storage name.
static std::string VersionID
Version ID string.
#define MC_LOG(...)
Debug macro.
Definition: MCLog.hpp:41
static void CompressZlib(const MCBinaryData &input_buffer, MCBinaryData &output_buffer, MC::CompressionLevelsType compression_level)
Compress data with zlib algorithm.
Definition: MCZipper.cpp:411
bool LoadFromFile(const std::string &file_name)
Load the data storage from a file.
void SaveToOutputStream(std::ostream &output_stream)
Save the data storage to a stream.
virtual MCDataContainer * DecodeContainer(const MCBinaryData &data)
Decode a container.
int GetSize() const
Get binary data size.