OdbDesignLib
OdbDesign ODB++ Parsing Library
 
Loading...
Searching...
No Matches
DesignCache.cpp
1#include "DesignCache.h"
2#include "ArchiveExtractor.h"
3#include "Logger.h"
4#include <exception>
5#include <filesystem>
6#include <stdexcept>
7#include <vector>
8#include "../FileModel/Design/FileArchive.h"
9#include <memory>
10#include "../ProductModel/Design.h"
11#include <iosfwd>
12#include <string>
13#include <type_traits>
14#include <StringVector.h>
15#include <shared_mutex>
16
17using namespace Utils;
18using namespace std::filesystem;
19
20namespace Odb::Lib::App
21{
22 DesignCache::DesignCache(std::string directory) :
23 m_directory(std::move(directory))
24 {
25 ensureDirectoryExists();
26 }
27
28 DesignCache::~DesignCache()
29 {
30 Clear();
31 }
32
33 std::shared_ptr<ProductModel::Design> DesignCache::GetDesign(const std::string& designName)
34 {
35 // Fast path: shared (read) lock for cache hit
36 {
37 std::shared_lock readLock(m_cacheMutex);
38 auto findIt = m_designsByName.find(designName);
39 if (findIt != m_designsByName.end())
40 {
41 return findIt->second;
42 }
43 }
44
45 // Slow path: load from disk without holding lock (LoadDesign may call GetFileArchive)
46 auto pDesign = LoadDesign(designName);
47
48 // Insert into cache under exclusive lock (double-check another thread didn't load it)
49 if (pDesign != nullptr)
50 {
51 std::unique_lock writeLock(m_cacheMutex);
52 auto findIt = m_designsByName.find(designName);
53 if (findIt != m_designsByName.end())
54 {
55 return findIt->second; // Another thread loaded it first
56 }
57 m_designsByName[designName] = pDesign;
58 }
59 return pDesign;
60 }
61
62 std::shared_ptr<FileModel::Design::FileArchive> DesignCache::GetFileArchive(const std::string& designName)
63 {
64 std::stringstream ss;
65 ss << "Retrieving design \"" << designName << "\" from cache... ";
66 logdebug(ss.str());
67
68 // Fast path: shared (read) lock for cache hit
69 {
70 std::shared_lock readLock(m_cacheMutex);
71 auto findIt = m_fileArchivesByName.find(designName);
72 if (findIt != m_fileArchivesByName.end())
73 {
74 logdebug("Found. Returning from cache.");
75 return findIt->second;
76 }
77 }
78
79 loginfo("Not found in cache, attempting to load from file...");
80
81 // Slow path: load from disk without holding lock
82 auto pFileArchive = LoadFileArchive(designName);
83
84 if (pFileArchive == nullptr)
85 {
86 logwarn("Failed loading from file");
87 }
88 else
89 {
90 loginfo("Loaded from file");
91
92 // Insert into cache under exclusive lock (double-check)
93 std::unique_lock writeLock(m_cacheMutex);
94 auto findIt = m_fileArchivesByName.find(designName);
95 if (findIt != m_fileArchivesByName.end())
96 {
97 logdebug("Found. Returning from cache (loaded by another thread).");
98 return findIt->second; // Another thread loaded it first
99 }
100 m_fileArchivesByName[designName] = pFileArchive;
101 }
102 return pFileArchive;
103 }
104
105 void DesignCache::AddFileArchive(const std::string& designName, std::shared_ptr<FileModel::Design::FileArchive> fileArchive, bool save)
106 {
107 std::unique_lock writeLock(m_cacheMutex);
108 m_fileArchivesByName[designName] = fileArchive;
109 if (save)
110 {
111 // SaveFileArchive calls GetFileArchive internally, which takes the lock.
112 // We must release before calling to avoid deadlock.
113 writeLock.unlock();
114 SaveFileArchive(designName);
115 }
116 }
117
118 bool DesignCache::SaveFileArchive(const std::string& designName)
119 {
120 auto fileArchive = GetFileArchive(designName);
121 if (fileArchive != nullptr)
122 {
123 std::string directory;
124 {
125 std::shared_lock readLock(m_cacheMutex);
126 directory = m_directory;
127 }
128 return fileArchive->SaveFileModel(directory);
129 }
130 return false;
131 }
132
133 std::vector<std::string> DesignCache::getLoadedDesignNames(const std::string& filter) const
134 {
135 std::shared_lock readLock(m_cacheMutex);
136 std::vector<std::string> loadedDesigns;
137 for (const auto& kv : m_designsByName)
138 {
139 loadedDesigns.push_back(kv.first);
140 }
141 return loadedDesigns;
142 }
143
144 std::vector<std::string> DesignCache::getLoadedFileArchiveNames(const std::string& filter) const
145 {
146 std::shared_lock readLock(m_cacheMutex);
147 std::vector<std::string> loadedFileArchives;
148 for (const auto& kv : m_fileArchivesByName)
149 {
150 loadedFileArchives.push_back(kv.first);
151 }
152 return loadedFileArchives;
153 }
154
155 std::vector<std::string> DesignCache::getUnloadedDesignNames(const std::string& filter) const
156 {
157 std::string directory;
158 {
159 std::shared_lock readLock(m_cacheMutex);
160 directory = m_directory;
161 }
162
163 std::vector<std::string> unloadedNames;
164
165 //try
166 {
167 path dir(directory);
168 for (const auto& entry : directory_iterator(dir))
169 {
170 if (entry.is_regular_file())
171 {
172 unloadedNames.push_back(entry.path().stem().string());
173 }
174 }
175 }
176 //catch (std::filesystem::filesystem_error& fe)
177 //{
178 // logexception(fe);
179 // // re-throw it so we get a HTTP 500 response to the client
180 // throw fe;
181 //}
182
183 return unloadedNames;
184 }
185
186 int DesignCache::loadAllFileArchives(bool stopOnError)
187 {
188 int loaded = 0;
189
190 std::string directory;
191 {
192 std::shared_lock readLock(m_cacheMutex);
193 directory = m_directory;
194 }
195
196 for (const auto& entry : directory_iterator(directory))
197 {
198 if (entry.is_regular_file())
199 {
200 if (ArchiveExtractor::IsArchiveTypeSupported(entry.path().filename()))
201 {
202 try
203 {
204 auto pFileArchive = GetFileArchive(entry.path().stem().string());
205 if (pFileArchive != nullptr)
206 {
207 loaded++;
208 }
209 }
210 catch (std::exception& e)
211 {
212 // continue if we encounter an error loading one
213 logexception(e);
214 if (stopOnError) throw e;
215 }
216 }
217 }
218 }
219
220 return loaded;
221 }
222
223 int DesignCache::loadAllDesigns(bool stopOnError)
224 {
225 int loaded = 0;
226
227 std::string directory;
228 {
229 std::shared_lock readLock(m_cacheMutex);
230 directory = m_directory;
231 }
232
233 for (const auto& entry : directory_iterator(directory))
234 {
235 if (entry.is_regular_file())
236 {
237 if (ArchiveExtractor::IsArchiveTypeSupported(entry.path().filename()))
238 {
239 try
240 {
241 auto pDesign = GetDesign(entry.path().stem().string());
242 if (pDesign != nullptr)
243 {
244 loaded++;
245 }
246 }
247 catch (std::exception& e)
248 {
249 logexception(e);
250 if (stopOnError)
251 {
252 throw;
253 }
254 }
255 }
256 }
257 }
258
259 return loaded;
260 }
261
262 int DesignCache::loadFileArchives(const StringVector& names)
263 {
264 int loaded = 0;
265
266 for (const auto& name : names)
267 {
268 try
269 {
270 auto pFileArchive = GetFileArchive(name);
271 if (pFileArchive != nullptr)
272 {
273 loaded++;
274 }
275 }
276 catch (std::exception& e)
277 {
278 // continue on error
279 logexception(e);
280 }
281 }
282
283 return loaded;
284 }
285
286 int DesignCache::loadDesigns(const StringVector& names)
287 {
288 int loaded = 0;
289
290 for (const auto& name : names)
291 {
292 try
293 {
294 auto pDesign = GetDesign(name);
295 if (pDesign != nullptr)
296 {
297 loaded++;
298 }
299 }
300 catch (std::exception& e)
301 {
302 // continue on error
303 logexception(e);
304 }
305 }
306
307 return loaded;
308 }
309
310 void DesignCache::setDirectory(const std::string& directory)
311 {
312 std::unique_lock writeLock(m_cacheMutex);
313 m_directory = directory;
314 }
315
316 std::string DesignCache::getDirectory() const
317 {
318 std::shared_lock readLock(m_cacheMutex);
319 return m_directory;
320 }
321
322 void DesignCache::Clear()
323 {
324 std::unique_lock writeLock(m_cacheMutex);
325 m_fileArchivesByName.clear();
326 m_designsByName.clear();
327 }
328
329 std::shared_ptr<ProductModel::Design> DesignCache::LoadDesign(const std::string& designName)
330 {
331 // NOTE: This is a lock-free I/O helper. It does NOT modify the cache maps.
332 // Cache insertion is handled by GetDesign().
333 std::string directory;
334 {
335 std::shared_lock readLock(m_cacheMutex);
336 directory = m_directory;
337 }
338
339 for (const auto& entry : directory_iterator(directory))
340 {
341 if (entry.is_regular_file())
342 {
343 if (entry.path().stem() == designName)
344 {
345 // GetFileArchive is thread-safe, manages its own locking
346 auto pFileModel = GetFileArchive(designName);
347 if (pFileModel != nullptr)
348 {
349 auto pDesign = std::make_shared<ProductModel::Design>();
350 if (pDesign->Build(pFileModel))
351 {
352 return pDesign;
353 }
354 else
355 {
356 break;
357 }
358 }
359 else
360 {
361 break;
362 }
363 }
364 }
365 }
366
367 return nullptr;
368 }
369
370 std::shared_ptr<FileModel::Design::FileArchive> DesignCache::LoadFileArchive(const std::string& designName)
371 {
372 // NOTE: This is a lock-free I/O helper. It does NOT modify the cache maps.
373 // Cache insertion is handled by GetFileArchive().
374 auto fileFound = false;
375
376 std::string directory;
377 {
378 std::shared_lock readLock(m_cacheMutex);
379 directory = m_directory;
380 }
381
382 // skip inaccessible files and do not follow symlinks
383 const auto options = directory_options::skip_permission_denied;
384 for (const auto& entry : directory_iterator(directory, options))
385 {
386 if (entry.is_regular_file())
387 {
388 if (entry.path().stem() == designName)
389 {
390 fileFound = true;
391
392 loginfo("file found: [" + entry.path().string() + "], attempting to parse...");
393
394 auto pFileArchive = std::make_shared<FileModel::Design::FileArchive>(entry.path().string());
395 if (pFileArchive->ParseFileModel())
396 {
397 return pFileArchive;
398 }
399
400 // A matching file was found but failed to extract or parse. Treat this
401 // as a corrupt/unloadable design (error) rather than a missing one, so
402 // callers surface INTERNAL/500 instead of a misleading NOT_FOUND/404.
403 // (ParseFileModel already throws on parse failure; this covers the
404 // extraction / missing-root-dir paths that return false.)
405 // The file path is intentionally omitted here to avoid leaking server
406 // filesystem details to API clients (it is logged above for diagnostics).
407 throw std::runtime_error(
408 "Failed to load design \"" + designName + "\": could not extract or parse archive");
409 }
410 }
411 }
412
413 if (!fileFound)
414 {
415 logwarn("Failed to find file for design \"" + designName + "\"");
416
417 logdebug("Listing all files in directory: " + directory);
418 for (const auto& entry : directory_iterator(directory, options))
419 {
420 if (entry.is_regular_file())
421 {
422 logdebug("Found file: " + entry.path().filename().string() + " (stem: " + entry.path().stem().string() + ")");
423 }
424 }
425 }
426
427 return nullptr;
428 }
429
430 void DesignCache::ensureDirectoryExists() const
431 {
432 if (!std::filesystem::exists(m_directory))
433 {
434 // create directory
435 try
436 {
437 std::filesystem::create_directories(m_directory);
438 }
439 catch (const std::exception& e)
440 {
441 std::string msg = "Failed to create design cache directory: " + m_directory;
442 logexception_msg(e, msg);
443 throw e;
444 }
445 }
446 }
447}