OdbDesignLib
OdbDesign ODB++ Parsing Library
 
Loading...
Searching...
No Matches
FileArchive.cpp
1#include "FileArchive.h"
2#include <filesystem>
3#include "ArchiveExtractor.h"
4#include "CrossPlatform.h"
5#include "MiscInfoFile.h"
6#include "Logger.h"
7#include "../invalid_odb_error.h"
8#include "StopWatch.h"
9#include "fastmove.h"
10#include <system_error>
11#include <cstdio>
12#include <string>
13#include <memory>
14#include "../../Text/Utf8Sanitizer.h"
15
16using namespace Utils;
17using namespace std::filesystem;
18
19namespace Odb::Lib::FileModel::Design
20{
21 FileArchive::FileArchive()
22 : m_filePath()
23 {
24 }
25
26 FileArchive::FileArchive(const std::string& path)
27 : m_filePath(path)
28 {
29 }
30
31 FileArchive::~FileArchive()
32 {
33 m_stepsByName.clear();
34 m_symbolsDirectoriesByName.clear();
35 }
36
37 std::string FileArchive::GetRootDir() const
38 {
39 return m_rootDir;
40 }
41
42 std::string FileArchive::GetProductName() const
43 {
44 return m_productName;
45 }
46
47 std::string FileArchive::GetFilePath() const
48 {
49 return m_filePath;
50 }
51
52 std::string FileArchive::GetFilename() const
53 {
54 //return m_filename;
55 return path(m_filePath).filename().string();
56 }
57
58 const StepDirectory::StringMap& FileArchive::GetStepsByName() const
59 {
60 return m_stepsByName;
61 }
62
63 const SymbolsDirectory::StringMap& FileArchive::GetSymbolsDirectoriesByName() const
64 {
65 return m_symbolsDirectoriesByName;
66 }
67
68 bool FileArchive::ParseFileModel()
69 {
70 //try
71 {
72 StopWatch timer(true);
73
74 if (!exists(m_filePath)) return false;
75
76 if (is_regular_file(m_filePath))
77 {
78 path extractedPath;
79 if (!ExtractDesignArchive(m_filePath, extractedPath))
80 {
81 logerror("failed to extract archive: (" + m_filePath + ")");
82 return false;
83 }
84
85 m_rootDir = findRootDir(extractedPath);
86 }
87
88 if (is_directory(m_rootDir))
89 {
90 loginfo("Parsing... ");
91
92 if (ParseDesignDirectory(m_rootDir))
93 {
94 timer.stop();
95 auto s = timer.getElapsedSecondsString();
96 loginfo("Successfully parsed. (" + s + "s)");
97
98 return true;
99 }
100 else
101 {
102 logerror("Parsing failed.");
103 throw std::runtime_error("Parsing failed.");
104 }
105 }
106 else
107 {
108 logerror("Failed to find root directory");
109 }
110 }
111 //catch (std::exception& e)
112 //{
113 // logexception(e);
114 // throw e;
115 //}
116
117 return false;
118 }
119
120 bool FileArchive::SaveFileModel(const path& directory)
121 {
122 return SaveFileModel(directory, m_productName);
123 }
124
125 bool FileArchive::SaveFileModel(const path& directory, const std::string& archiveName)
126 {
127 // create directory in /tmp
128 // write dir structure and files to it
129 // gzip with archiveName
130 // move archive to directory
131
132 std::string tempName;
133 if (!CrossPlatform::tmpnam_safe(tempName)) return false;
134
135 auto tempPath = temp_directory_path() / tempName;
136 if (!create_directory(tempPath)) return false;
137
138 auto rootPath = tempPath / archiveName;
139 if (!create_directory(rootPath)) return false;
140 if (!Save(rootPath)) return false;
141
142 // compress the written file structure
143 std::string createdArchivePath;
144 if (! Utils::ArchiveExtractor::CompressDir(rootPath.string(), tempPath.string(), archiveName, createdArchivePath)) return false;
145 if (createdArchivePath.empty()) return false;
146
147 // move the compressed file to the requested save directory
148 path archiveFilename = path(createdArchivePath).filename();
149 path destPath = directory / archiveFilename;
150 std::error_code ec;
151 Utils::fastmove_file(createdArchivePath, destPath, true, ec);
152 if (ec.value() != 0) return false;
153
154 return true;
155 }
156
157 bool FileArchive::Save(const path& directory)
158 {
159 // MiscInfoFile
160 auto miscPath = directory / "misc";
161 if (!create_directory(miscPath)) return false;
162
163 std::ofstream ofs1(miscPath / "info", std::ios::out);
164 if (!m_miscInfoFile.Save(ofs1)) return false;
165 ofs1.close();
166
167 // MiscAttrFile
168 std::ofstream ofs2(miscPath / "sysattr");
169 if (!m_miscAttrListFile.Save(ofs2)) return false;
170 ofs2.close();
171
172 // StandardFontsFile
173 auto fontsPath = directory / "fonts";
174 if (!create_directory(fontsPath)) return false;
175 std::ofstream ofs3(fontsPath / "standard");
176 if (!m_standardFontsFile.Save(ofs3)) return false;
177 ofs3.close();
178
179 // MatrixFile
180 auto matrixPath = directory / "matrix";
181 if (!create_directory(matrixPath)) return false;
182 std::ofstream ofs4(matrixPath / "matrix");
183 if (!m_matrixFile.Save(ofs4)) return false;
184 ofs4.close();
185
186 // Steps
187 const auto stepsDirectory = directory / "steps";
188 if (!create_directory(stepsDirectory)) return false;
189 for (auto& kvStepDirectory : m_stepsByName)
190 {
191 if (!kvStepDirectory.second->Save(stepsDirectory)) return false;
192 }
193
194 // Symbols
195 const auto symbolsDirectory = directory / "symbols";
196 if (!create_directory(symbolsDirectory)) return false;
197 for (auto& kvSymbolsDirectory : m_symbolsDirectoriesByName)
198 {
199 if (!kvSymbolsDirectory.second->Save(symbolsDirectory)) return false;
200 }
201
202 return true;
203 }
204
205 bool FileArchive::ExtractDesignArchive(const path& archivePath, path& extractedPath)
206 {
207 loginfo("Extracting... ");
208
209 if (!Utils::ArchiveExtractor::IsArchiveTypeSupported(archivePath))
210 {
211 logerror("Unsupported archive type: (" + archivePath.string() + ")");
212 return false;
213 }
214
215 Utils::ArchiveExtractor extractor(archivePath.string());
216 if (!extractor.Extract()) return false;
217
218 auto extracted = path(extractor.GetExtractionDirectory());
219 if (!exists(extracted)) return false;
220
221 extractedPath = extracted;
222
223 loginfo("Successfully extracted.");
224
225 return true;
226
227 }
228
229 /*static*/ std::string FileArchive::findRootDir(const path& extractedPath)
230 {
231 if (pathContainsTopLevelDesignDirs(extractedPath))
232 {
233 return extractedPath.string();
234 }
235 else
236 {
237 for (const auto& p : directory_iterator(extractedPath))
238 {
239 if (is_directory(p))
240 {
241 if (pathContainsTopLevelDesignDirs(p.path()))
242 {
243 return p.path().string();
244 }
245 }
246 }
247
248 return "";
249 }
250 }
251
252 /*static*/ bool FileArchive::pathContainsTopLevelDesignDirs(const path& path)
253 {
254 for (const auto& topLevelRootDirName : TOPLEVEL_DESIGN_DIR_NAMES)
255 {
256 auto rootLevelDirPath = path / topLevelRootDirName;
257 if (!exists(rootLevelDirPath)) return false;
258 }
259 return true;
260 }
261
262 std::unique_ptr<Protobuf::FileArchive> FileArchive::to_protobuf() const
263 {
264 std::unique_ptr<Protobuf::FileArchive> pFileArchiveMessage(new Protobuf::FileArchive);
265 pFileArchiveMessage->set_productname(Odb::Lib::Text::ToUtf8(m_productName));
266 pFileArchiveMessage->set_filename(Odb::Lib::Text::ToUtf8(m_filename));
267 //pFileArchiveMessage->set_filepath(m_filePath);
268 pFileArchiveMessage->mutable_matrixfile()->CopyFrom(*m_matrixFile.to_protobuf());
269 pFileArchiveMessage->mutable_miscinfofile()->CopyFrom(*m_miscInfoFile.to_protobuf());
270 pFileArchiveMessage->mutable_standardfontsfile()->CopyFrom(*m_standardFontsFile.to_protobuf());
271 pFileArchiveMessage->mutable_miscattrlistfile()->CopyFrom(*m_miscAttrListFile.to_protobuf());
272
273 for (const auto& kvStepDirectoryRecord : m_stepsByName)
274 {
275 (*pFileArchiveMessage->mutable_stepsbyname())[Odb::Lib::Text::ToUtf8(kvStepDirectoryRecord.first)] = *kvStepDirectoryRecord.second->to_protobuf();
276 }
277
278 for (const auto& kvSymbolsDirectory : m_symbolsDirectoriesByName)
279 {
280 (*pFileArchiveMessage->mutable_symbolsdirectoriesbyname())[Odb::Lib::Text::ToUtf8(kvSymbolsDirectory.first)] = *kvSymbolsDirectory.second->to_protobuf();
281 }
282
283#ifndef NDEBUG
284 // Debug-only assertion: verify all string fields are valid UTF-8 before serialization
285 Odb::Lib::Text::AssertAllStringFieldsAreValidUtf8(*pFileArchiveMessage, "FileArchive");
286#endif
287
288 return pFileArchiveMessage;
289 }
290
291 void FileArchive::from_protobuf(const Protobuf::FileArchive& message)
292 {
293 m_productName = message.productname();
294 m_filename = message.filename();
295 //m_filePath = message.filepath();
296 m_matrixFile.from_protobuf(message.matrixfile());
297 m_miscInfoFile.from_protobuf(message.miscinfofile());
298 m_standardFontsFile.from_protobuf(message.standardfontsfile());
299 m_miscAttrListFile.from_protobuf(message.miscattrlistfile());
300
301 for (const auto& kvStepDirectoryRecord : message.stepsbyname())
302 {
303 auto pStepDirectory = std::make_shared<StepDirectory>("");
304 pStepDirectory->from_protobuf(kvStepDirectoryRecord.second);
305 m_stepsByName[kvStepDirectoryRecord.first] = pStepDirectory;
306 }
307
308 for (const auto& kvSymbolsDirectory : message.symbolsdirectoriesbyname())
309 {
310 auto pSymbolsDirectory = std::make_shared<SymbolsDirectory>("");
311 pSymbolsDirectory->from_protobuf(kvSymbolsDirectory.second);
312 m_symbolsDirectoriesByName[kvSymbolsDirectory.first] = pSymbolsDirectory;
313 }
314 }
315
316 bool FileArchive::ParseDesignDirectory(const path& path)
317 {
318 if (!exists(path)) return false;
319 else if (!is_directory(path)) return false;
320
321 m_productName = path.stem().string();
322
323 if (! ParseStepDirectories(path)) return false;
324 if (! ParseMiscInfoFile(path)) return false;
325 if (! ParseMatrixFile(path)) return false;
326 if (! ParseStandardFontsFile(path)) return false;
327 if (! ParseSymbolsDirectories(path)) return false;
328 if (! ParseMiscAttrListFile(path)) return false;
329
330 return true;
331 }
332
333 bool FileArchive::ParseStepDirectories(const path& path)
334 {
335 loginfo("Parsing steps...");
336
337 auto stepsPath = path / "steps";
338 for (auto& d : directory_iterator(stepsPath))
339 {
340 if (is_directory(d))
341 {
342 auto pStep = std::make_shared<StepDirectory>(d.path());
343 if (pStep->Parse())
344 {
345 m_stepsByName[pStep->GetName()] = pStep;
346 }
347 else
348 {
349 logwarn("Failed to parse step: " + pStep->GetName());
350 //return false;
351 }
352 }
353 }
354
355 loginfo("Parsing steps complete");
356
357 return true;
358 }
359
360 bool FileArchive::ParseMiscInfoFile(const path& path)
361 {
362 loginfo("Parsing misc/info file...");
363
364 auto miscDirectory = path / "misc";
365 if (!exists(miscDirectory))
366 {
367 auto message = "misc directory does not exist: [" + miscDirectory.string() + "]";
368 throw invalid_odb_error(message);
369 }
370 if (!is_directory(miscDirectory))
371 {
372 auto message = "misc path is not a directory: [" + miscDirectory.string() + "]";
373 throw invalid_odb_error(message);
374 }
375
376 if (!m_miscInfoFile.Parse(miscDirectory)) return false;
377
378 loginfo("Parsing misc/info file complete");
379
380 return true;
381 }
382
383 bool FileArchive::ParseMiscAttrListFile(const path& path)
384 {
385 loginfo("Parsing misc/attrlist file...");
386
387 auto miscDirectory = path / "misc";
388 if (!exists(miscDirectory))
389 {
390 auto message = "misc directory does not exist: [" + miscDirectory.string() + "]";
391 throw invalid_odb_error(message);
392 }
393 if (!is_directory(miscDirectory))
394 {
395 auto message = "misc path is not a directory: [" + miscDirectory.string() + "]";
396 throw invalid_odb_error(message);
397 }
398
399 if (!m_miscAttrListFile.Parse(miscDirectory)) return false;
400
401 loginfo("Parsing misc/attrlist file complete");
402
403 return true;
404 }
405
406 bool FileArchive::ParseMatrixFile(const path& path)
407 {
408 loginfo("Parsing matrix/matrix file...");
409
410 auto matrixDir = path / "matrix";
411 if (!exists(matrixDir))
412 {
413 auto message = "matrix directory does not exist: [" + matrixDir.string() + "]";
414 throw invalid_odb_error(message);
415 }
416 if (!is_directory(matrixDir))
417 {
418 auto message = "matrix path is not a directory: [" + matrixDir.string() + "]";
419 throw invalid_odb_error(message);
420 }
421
422 if (!m_matrixFile.Parse(matrixDir)) return false;
423
424 loginfo("Parsing matrix/matrix file complete");
425
426 return true;
427 }
428 bool FileArchive::ParseStandardFontsFile(const path& path)
429 {
430 loginfo("Parsing fonts/standard file...");
431
432 auto fontsDir = path / "fonts";
433 if (!exists(fontsDir))
434 {
435 auto message = "fonts directory does not exist: [" + fontsDir.string() + "]";
436 throw invalid_odb_error(message);
437 }
438 if (!is_directory(fontsDir))
439 {
440 auto message = "fonts path is not a directory: [" + fontsDir.string() + "]";
441 throw invalid_odb_error(message);
442 }
443
444 if (!m_standardFontsFile.Parse(fontsDir)) return false;
445
446 loginfo("Parsing fonts/standard file complete");
447
448 return true;
449 }
450
451 bool FileArchive::ParseSymbolsDirectories(const path& path)
452 {
453 loginfo("Parsing symbols root directory...");
454
455 auto symbolsDirectory = path / "symbols";
456
457 if (!exists(symbolsDirectory))
458 {
459 logwarn("symbols root directory does not exist (" + symbolsDirectory.string() + ")");
460 return true;
461 }
462 else if (!is_directory(symbolsDirectory))
463 {
464 logerror("symbols root directory exists but is a regular file/not a directory (" + symbolsDirectory.string() + ")");
465 return false;
466 }
467
468 for (auto& d : directory_iterator(symbolsDirectory))
469 {
470 if (is_directory(d))
471 {
472 auto pSymbolsDirectory = std::make_shared<SymbolsDirectory>(d.path());
473 if (pSymbolsDirectory->Parse())
474 {
475 //loginfo("Parsing symbols directory: " + pSymbolsDirectory->GetName() + " complete");
476
477 m_symbolsDirectoriesByName[pSymbolsDirectory->GetName()] = pSymbolsDirectory;
478 }
479 else
480 {
481 logerror("Parsing symbol directory: " + pSymbolsDirectory->GetName() + " failed");
482 return false;
483 }
484 }
485 }
486
487 loginfo("Parsing symbols root directory complete");
488
489 return true;
490 }
491
492 const MiscInfoFile &FileArchive::GetMiscInfoFile() const
493 {
494 return m_miscInfoFile;
495 }
496
497 const MatrixFile& FileArchive::GetMatrixFile() const
498 {
499 return m_matrixFile;
500 }
501
502 const StandardFontsFile& FileArchive::GetStandardFontsFile() const
503 {
504 return m_standardFontsFile;
505 }
506
507 const AttrListFile& FileArchive::GetMiscAttrListFile() const
508 {
509 return m_miscAttrListFile;
510 }
511
512 std::shared_ptr<StepDirectory> FileArchive::GetStepDirectory(const std::string& stepName /*= ""*/) const
513 {
514 std::shared_ptr<FileModel::Design::StepDirectory> pStepDirectory;
515
516 const auto& steps = GetStepsByName();
517 if (!steps.empty())
518 {
519 if (stepName.empty())
520 {
521 // return first step
522 pStepDirectory = steps.begin()->second;
523 }
524 else
525 {
526 auto findIt = steps.find(stepName);
527 if (findIt != steps.end())
528 {
529 pStepDirectory = findIt->second;
530 }
531 }
532 }
533
534 return pStepDirectory;
535 }
536}
void AssertAllStringFieldsAreValidUtf8(const google::protobuf::Message &msg, std::string_view msgName)
Debug-only assertion to verify all string fields in a message are valid UTF-8.