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