TDME2  1.9.200
ArchiveFileSystem.cpp
Go to the documentation of this file.
2 
3 #include <string.h>
4 
5 #include <algorithm>
6 #include <cassert>
7 #include <filesystem>
8 #include <fstream>
9 #include <string>
10 #include <vector>
11 
12 #include <ext/sha256/sha256.h>
13 #include <ext/zlib/zlib.h>
14 
15 #include <tdme/tdme.h>
16 #include <tdme/math/Math.h>
20 #include <tdme/utilities/Console.h>
24 
25 using std::ifstream;
26 using std::ios;
27 using std::sort;
28 using std::string;
29 using std::to_string;
30 using std::vector;
31 
32 using tdme::math::Math;
39 
40 ArchiveFileSystem::ArchiveFileSystem(const string& fileName): fileName(fileName), ifsMutex("afs-ifs-mutex")
41 {
42  // open
43  ifs.open(std::filesystem::u8path(fileName), ifstream::binary);
44  if (ifs.is_open() == false) {
45  throw FileSystemException("Unable to open file for reading(" + to_string(errno) + "): " + fileName);
46  }
47 
48  // read toc offset
49  uint64_t fileInformationOffset;
50  ifs.seekg(-static_cast<int64_t>(sizeof(fileInformationOffset)), ios::end);
51  ifs.read((char*)&fileInformationOffset, sizeof(fileInformationOffset));
52  ifs.seekg(fileInformationOffset, ios::beg);
53 
54  // read toc
55  while (true == true) {
56  uint32_t nameSize;
57  ifs.read((char*)&nameSize, sizeof(nameSize));
58  if (nameSize == 0) break;
59 
60  FileInformation fileInformation;
61  fileInformation.name.resize(nameSize);
62  ifs.read(fileInformation.name.data(), nameSize);
63  ifs.read((char*)&fileInformation.bytes, sizeof(fileInformation.bytes));
64  ifs.read((char*)&fileInformation.compressed, sizeof(fileInformation.compressed));
65  ifs.read((char*)&fileInformation.bytesCompressed, sizeof(fileInformation.bytesCompressed));
66  ifs.read((char*)&fileInformation.offset, sizeof(fileInformation.offset));
67  ifs.read((char*)&fileInformation.executable, sizeof(fileInformation.executable));
68  fileInformations[fileInformation.name] = fileInformation;
69  }
70 }
71 
73 {
74  ifs.close();
75 }
76 
78  return fileName;
79 }
80 
81 const string ArchiveFileSystem::composeURI(const string& pathName, const string& fileName) {
82  return pathName + "/" + fileName;
83 }
84 
85 void ArchiveFileSystem::list(const string& pathName, vector<string>& files, FileNameFilter* filter, bool addDrives)
86 {
87  // TODO: this currently lists all files beginning from given path, also files in sub folders
88  auto _pathName = pathName;
89  if (_pathName.empty() == false && StringTools::endsWith(pathName, "/") == false) _pathName+= "/";
90  for (const auto& [mapfileName, fileInformation]: fileInformations) {
91  const auto& fileName = fileInformation.name;
92  if (StringTools::startsWith(fileName, _pathName) == true) {
93  try {
94  if (filter != nullptr && filter->accept(
97  ) == false) continue;
98  } catch (Exception& exception) {
99  Console::println("StandardFileSystem::list(): Filter::accept(): " + pathName + "/" + fileName + ": " + exception.what());
100  continue;
101  }
102  files.push_back(StringTools::substring(fileName, pathName.size()));
103  }
104  }
105  sort(files.begin(), files.end());
106 }
107 
108 bool ArchiveFileSystem::isPath(const string& pathName) {
109  return false;
110 }
111 
112 bool ArchiveFileSystem::isDrive(const string& pathName) {
113  return false;
114 }
115 
116 bool ArchiveFileSystem::exists(const string& fileName) {
117  // compose relative file name and remove ./
118  auto relativeFileName = fileName;
119  if (StringTools::startsWith(relativeFileName, "./") == true) relativeFileName = StringTools::substring(relativeFileName, 2);
120 
121  //
122  return fileInformations.find(relativeFileName) != fileInformations.end();
123 }
124 
125 bool ArchiveFileSystem::isExecutable(const string& pathName, const string& fileName) {
126  // compose relative file name and remove ./
127  auto relativeFileName = pathName + "/" + fileName;
128  if (StringTools::startsWith(relativeFileName, "./") == true) relativeFileName = StringTools::substring(relativeFileName, 2);
129 
130  // determine file
131  auto fileInformationIt = fileInformations.find(relativeFileName);
132  if (fileInformationIt == fileInformations.end()) {
133  throw FileSystemException("Unable to open file for reading: " + relativeFileName + ": " + pathName + "/" + fileName);
134  }
135  const auto& fileInformation = fileInformationIt->second;
136  //
137  return fileInformation.executable;
138 }
139 
140 void ArchiveFileSystem::setExecutable(const string& pathName, const string& fileName) {
141  throw FileSystemException("ArchiveFileSystem::createPath(): This operation is not supported in archive file system");
142 }
143 
144 uint64_t ArchiveFileSystem::getFileSize(const string& pathName, const string& fileName) {
145  // compose relative file name and remove ./
146  auto relativeFileName = pathName + "/" + fileName;
147  if (StringTools::startsWith(relativeFileName, "./") == true) relativeFileName = StringTools::substring(relativeFileName, 2);
148 
149  // determine file
150  auto fileInformationIt = fileInformations.find(relativeFileName);
151  if (fileInformationIt == fileInformations.end()) {
152  throw FileSystemException("Unable to open file for reading: " + relativeFileName + ": " + pathName + "/" + fileName);
153  }
154  const auto& fileInformation = fileInformationIt->second;
155  //
156  return fileInformation.bytes;
157 }
158 
159 void ArchiveFileSystem::decompress(vector<uint8_t>& inContent, vector<uint8_t>& outContent) {
160  // see: https://www.zlib.net/zpipe.c
161 
162  #define CHUNK 16384
163 
164  int ret;
165  size_t have;
166  z_stream strm;
167  unsigned char in[CHUNK];
168  unsigned char out[CHUNK];
169 
170  // allocate inflate state
171  strm.zalloc = Z_NULL;
172  strm.zfree = Z_NULL;
173  strm.opaque = Z_NULL;
174  strm.avail_in = 0;
175  strm.next_in = Z_NULL;
176  ret = inflateInit(&strm);
177  if (ret != Z_OK) {
178  throw FileSystemException("ArchiveFileSystem::decompress(): Error while decompressing: inflate init");
179  }
180 
181  // decompress until deflate stream ends or end of file */
182  size_t outPosition = 0;
183  size_t inPosition = 0;
184  size_t inBytes = inContent.size();
185  do {
186  // see: https://www.zlib.net/zpipe.c
187  auto inStartPosition = inPosition;
188  for (size_t i = 0; i < CHUNK; i++) {
189  if (inPosition == inBytes) break;
190  in[i] = inContent[inPosition];
191  inPosition++;
192  }
193  strm.avail_in = inPosition - inStartPosition;
194  if (strm.avail_in == 0) break;
195  strm.next_in = in;
196 
197  // run inflate() on input until output buffer not full
198  do {
199  strm.avail_out = CHUNK;
200  strm.next_out = out;
201  ret = inflate(&strm, Z_NO_FLUSH);
202  assert(ret != Z_STREAM_ERROR); // state not clobbered
203  switch (ret) {
204  case Z_NEED_DICT:
205  throw FileSystemException("ArchiveFileSystem::decompress(): Error while decompressing: Z_NEED_DICT");
206  case Z_DATA_ERROR:
207  case Z_MEM_ERROR:
208  (void)inflateEnd(&strm);
209  throw FileSystemException("ArchiveFileSystem::decompress(): Error while decompressing: Z_DATA_ERROR | Z_MEM_ERROR");
210  }
211  have = CHUNK - strm.avail_out;
212  for (size_t i = 0; i < have; i++) {
213  outContent[outPosition++] = out[i];
214  }
215  } while (strm.avail_out == 0);
216 
217  // done when inflate() says it's done
218  } while (ret != Z_STREAM_END);
219 
220  // clean up and return
221  (void) inflateEnd(&strm);
222 
223  // check if eof
224  if (ret != Z_STREAM_END) {
225  throw FileSystemException("ArchiveFileSystem::decompress(): Error while decompressing: missing eof");
226  }
227 }
228 
229 const string ArchiveFileSystem::getContentAsString(const string& pathName, const string& fileName) {
230  // compose relative file name and remove ./
231  auto relativeFileName = pathName + "/" + fileName;
232  if (StringTools::startsWith(relativeFileName, "./") == true) relativeFileName = StringTools::substring(relativeFileName, 2);
233 
234  // determine file
235  auto fileInformationIt = fileInformations.find(relativeFileName);
236  if (fileInformationIt == fileInformations.end()) {
237  throw FileSystemException("Unable to open file for reading: " + relativeFileName + ": " + pathName + "/" + fileName);
238  }
239  const auto& fileInformation = fileInformationIt->second;
240 
241  //
242  ifsMutex.lock();
243 
244  // seek
245  ifs.seekg(fileInformation.offset, ios::beg);
246 
247  // result
248  string result;
249  if (fileInformation.compressed == 1) {
250  vector<uint8_t> compressedBuffer;
251  compressedBuffer.resize(fileInformation.bytesCompressed);
252  ifs.read((char*)compressedBuffer.data(), fileInformation.bytesCompressed);
253  ifsMutex.unlock();
254  vector<uint8_t> decompressedBuffer;
255  decompressedBuffer.resize(fileInformation.bytes);
256  decompress(compressedBuffer, decompressedBuffer);
257  result.append((char*)decompressedBuffer.data(), fileInformation.bytes);
258  } else {
259  vector<uint8_t> buffer;
260  buffer.resize(fileInformation.bytes);
261  ifs.read((char*)buffer.data(), fileInformation.bytes);
262  ifsMutex.unlock();
263  result.append((char*)buffer.data(), fileInformation.bytes);
264  }
265 
266  // done
267  return result;
268 }
269 
270 void ArchiveFileSystem::setContentFromString(const string& pathName, const string& fileName, const string& content) {
271  throw FileSystemException("ArchiveFileSystem::setContentFromString(): This operation is not supported in archive file system");
272 }
273 
274 void ArchiveFileSystem::getContent(const string& pathName, const string& fileName, vector<uint8_t>& content)
275 {
276  // compose relative file name and remove ./
277  auto relativeFileName = pathName + "/" + fileName;
278  if (StringTools::startsWith(relativeFileName, "./") == true) relativeFileName = StringTools::substring(relativeFileName, 2);
279 
280  // determine file
281  auto fileInformationIt = fileInformations.find(relativeFileName);
282  if (fileInformationIt == fileInformations.end()) {
283  throw FileSystemException("Unable to open file for reading: " + relativeFileName + ": " + pathName + "/" + fileName);
284  }
285  const auto& fileInformation = fileInformationIt->second;
286 
287  //
288  ifsMutex.lock();
289 
290  // seek
291  ifs.seekg(fileInformation.offset, ios::beg);
292 
293  // result
294  if (fileInformation.compressed == 1) {
295  vector<uint8_t> compressedContent;
296  compressedContent.resize(fileInformation.bytesCompressed);
297  ifs.read((char*)compressedContent.data(), fileInformation.bytesCompressed);
298  ifsMutex.unlock();
299  content.resize(fileInformation.bytes);
300  decompress(compressedContent, content);
301  } else {
302  content.resize(fileInformation.bytes);
303  ifs.read((char*)content.data(), fileInformation.bytes);
304  ifsMutex.unlock();
305  }
306 }
307 
308 void ArchiveFileSystem::setContent(const string& pathName, const string& fileName, const vector<uint8_t>& content) {
309  throw FileSystemException("ArchiveFileSystem::setContent(): This operation is not supported in archive file system");
310 }
311 
312 void ArchiveFileSystem::getContentAsStringArray(const string& pathName, const string& fileName, vector<string>& content)
313 {
314  auto contentAsString = getContentAsString(pathName, fileName);
315  contentAsString = StringTools::replace(contentAsString, "\r", "");
316  StringTokenizer t;
317  t.tokenize(contentAsString, "\n");
318  while (t.hasMoreTokens() == true) {
319  content.push_back(StringTools::replace(t.nextToken(), "\r", ""));
320  }
321 }
322 
323 void ArchiveFileSystem::setContentFromStringArray(const string& pathName, const string& fileName, const vector<string>& content)
324 {
325  throw FileSystemException("ArchiveFileSystem::setContentFromStringArray(): This operation is not supported in archive file system");
326 }
327 
328 const string ArchiveFileSystem::getCanonicalURI(const string& pathName, const string& fileName) {
329  string unixPathName = StringTools::replace(pathName, "\\", "/");
330  string unixFileName = StringTools::replace(fileName, "\\", "/");
331 
332  //
333  auto pathString = composeURI(unixPathName, unixFileName);
334 
335  // separate into path components
336  vector<string> pathComponents;
337  StringTokenizer t;
338  t.tokenize(pathString, "/");
339  while (t.hasMoreTokens()) {
340  pathComponents.push_back(t.nextToken());
341  }
342 
343  // process path components
344  for (auto i = 0; i < pathComponents.size(); i++) {
345  auto pathComponent = pathComponents[i];
346  if (pathComponent == ".") {
347  pathComponents[i].clear();
348  } else
349  if (pathComponent == "..") {
350  pathComponents[i].clear();
351  int j = i - 1;
352  for (int pathComponentReplaced = 0; pathComponentReplaced < 1 && j >= 0; ) {
353  if (pathComponents[j].empty() == false) {
354  pathComponents[j].clear();
355  pathComponentReplaced++;
356  }
357  j--;
358  }
359  }
360  }
361 
362  // process path components
363  string canonicalPath = "";
364  bool slash = StringTools::startsWith(pathString, "/");
365  for (auto i = 0; i < pathComponents.size(); i++) {
366  auto pathComponent = pathComponents[i];
367  if (pathComponent.empty() == true) {
368  // no op
369  } else {
370  canonicalPath = canonicalPath + (slash == true?"/":"") + pathComponent;
371  slash = true;
372  }
373  }
374 
375  // add cwd if required
376  auto canonicalPathString = canonicalPath.empty() == true?"/":canonicalPath;
377  /*
378  if (canonicalPathString.length() == 0 ||
379  (StringTools::startsWith(canonicalPathString, "/") == false &&
380  StringTools::regexMatch(canonicalPathString, "^[a-zA-Z]\\:.*$") == false)) {
381  canonicalPathString = getCurrentWorkingPathName() + "/" + canonicalPathString;
382  }
383  */
384 
385  //
386  return canonicalPathString;
387 }
388 
390  return ".";
391 }
392 
393 void ArchiveFileSystem::changePath(const string& pathName) {
394 }
395 
396 const string ArchiveFileSystem::getPathName(const string& fileName) {
397  string unixFileName = StringTools::replace(fileName, L'\\', L'/');
398  int32_t lastPathSeparator = StringTools::lastIndexOf(unixFileName, L'/');
399  if (lastPathSeparator == -1) return ".";
400  return StringTools::substring(unixFileName, 0, lastPathSeparator);
401 }
402 
403 const string ArchiveFileSystem::getFileName(const string& fileName) {
404  string unixFileName = StringTools::replace(fileName, L'\\', L'/');
405  int32_t lastPathSeparator = StringTools::lastIndexOf(unixFileName, L'/');
406  if (lastPathSeparator == -1) return fileName;
407  return StringTools::substring(unixFileName, lastPathSeparator + 1, unixFileName.length());
408 }
409 
410 const string ArchiveFileSystem::removeFileExtension(const string& fileName) {
411  auto idx = fileName.rfind('.');
412  if (idx == string::npos) {
413  return fileName;
414  } else {
415  return fileName.substr(0, idx);
416  }
417 }
418 
419 void ArchiveFileSystem::createPath(const string& pathName) {
420  throw FileSystemException("ArchiveFileSystem::createPath(): This operation is not supported in archive file system");
421 }
422 
423 void ArchiveFileSystem::removePath(const string& pathName, bool recursive) {
424  throw FileSystemException("ArchiveFileSystem::removePath(): This operation is not supported in archive file system");
425 }
426 
427 void ArchiveFileSystem::removeFile(const string& pathName, const string& fileName) {
428  throw FileSystemException("ArchiveFileSystem::removeFile(): This operation is not supported in archive file system");
429 }
430 
431 void ArchiveFileSystem::rename(const string& fileNameFrom, const string& fileNameTo) {
432  throw FileSystemException("ArchiveFileSystem::rename(): This operation is not supported in archive file system");
433 }
434 
435 bool ArchiveFileSystem::getThumbnailAttachment(const string& pathName, const string& fileName, vector<uint8_t>& thumbnailAttachmentContent) {
436  throw FileSystemException("ArchiveFileSystem::getThumbnailAttachment(): This operation is not supported in archive file system");
437 }
438 
439 bool ArchiveFileSystem::getThumbnailAttachment(const vector<uint8_t>& content, vector<uint8_t>& thumbnailAttachmentContent) {
440  throw FileSystemException("ArchiveFileSystem::getThumbnailAttachment(): This operation is not supported in archive file system");
441 }
442 
444  ifs.seekg(0, ios::end);
445  auto bytesTotal = ifs.tellg();
446  ifs.seekg(0, ios::beg);
447 
448  uint8_t input[16384];
449  unsigned char digest[SHA256::DIGEST_SIZE];
450  memset(digest, 0, SHA256::DIGEST_SIZE);
451 
452  auto ctx = SHA256();
453  ctx.init();
454  int64_t bytesRead = 0LL;
455  while (bytesRead < bytesTotal) {
456  auto bytesToRead = Math::min(static_cast<int64_t>(bytesTotal) - bytesRead, sizeof(input));
457  ifs.read((char*)input, bytesToRead);
458  ctx.update((const uint8_t*)input, bytesToRead);
459  bytesRead+= bytesToRead;
460  }
461  ctx.final(digest);
462 
463  char buf[2 * SHA256::DIGEST_SIZE + 1];
464  buf[2 * SHA256::DIGEST_SIZE] = 0;
465  for (int i = 0; i < SHA256::DIGEST_SIZE; i++) sprintf(buf + i * 2, "%02x", digest[i]);
466  return std::string(buf);
467 }
468 
#define CHUNK
Standard math functions.
Definition: Math.h:19
Archive file system implementation.
const string removeFileExtension(const string &fileName)
Remove file extension, e.g.
bool isDrive(const string &uri)
Check if file is a drive (applies to Microsoft Windows only)
map< string, FileInformation > fileInformations
void setContentFromString(const string &pathName, const string &fileName, const string &content)
Set content from string.
virtual ~ArchiveFileSystem()
Public destructor.
const string composeURI(const string &pathName, const string &fileName)
Compose URI from path name and file name.
const string getPathName(const string &uri)
Get path name.
const string getCanonicalURI(const string &pathName, const string &fileName)
Get canonical URI from given path name and file name.
void createPath(const string &pathName)
Create path.
bool getThumbnailAttachment(const string &pathName, const string &fileName, vector< uint8_t > &thumbnailAttachmentContent)
Reads a thumbnail attachment from binary file.
void decompress(vector< uint8_t > &inContent, vector< uint8_t > &outContent)
Decompress from archive.
void list(const string &pathName, vector< string > &files, FileNameFilter *filter=nullptr, bool addDrives=false)
List files for given path and filter by a file name filter if not null.
void changePath(const string &pathName)
Change path.
void removePath(const string &pathName, bool recursive)
Remove path.
void setExecutable(const string &pathName, const string &fileName) override
Set up file to be an executable file.
void setContent(const string &pathName, const string &fileName, const vector< uint8_t > &content)
Set file content.
void getContent(const string &pathName, const string &fileName, vector< uint8_t > &content)
Get file content.
void setContentFromStringArray(const string &pathName, const string &fileName, const vector< string > &content)
Set file content as string array.
const string computeSHA256Hash()
Compute SHA256 hash.
void removeFile(const string &pathName, const string &fileName)
Remove file.
uint64_t getFileSize(const string &pathName, const string &fileName)
Return file size of given file.
void getContentAsStringArray(const string &pathName, const string &fileName, vector< string > &content)
Get file content as string array.
const string getFileName(const string &uri)
Get file name.
bool isPath(const string &uri)
Check if file is a path.
bool exists(const string &uri)
Check if file exists.
const string getCurrentWorkingPathName()
Get current working path name.
bool isExecutable(const string &pathName, const string &fileName) override
Returns if file is a executable file.
const string getContentAsString(const string &pathName, const string &fileName)
Get content as string.
void rename(const string &fileNameFrom, const string &fileNameTo)
Rename file.
Mutex implementation.
Definition: Mutex.h:19
void unlock()
Unlocks this mutex.
Definition: Mutex.h:54
void lock()
Locks the mutex, additionally mutex locks will block until other locks have been unlocked.
Definition: Mutex.h:47
Console class.
Definition: Console.h:29
String tokenizer class.
void tokenize(const string &str, const string &delimiters, bool emptyTokens=false)
Tokenize.
String tools class.
Definition: StringTools.h:22
std::exception Exception
Exception base class.
Definition: Exception.h:18
File system file name filter interface.
virtual bool accept(const string &path, const string &file)=0
Accept a file.