Grok 20.3.2
TLMFile.h
Go to the documentation of this file.
1/*
2 * Copyright (C) 2016-2026 Grok Image Compression Inc.
3 *
4 * This source code is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Affero General Public License, version 3,
6 * as published by the Free Software Foundation.
7 *
8 * This source code is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Affero General Public License for more details.
12 *
13 * You should have received a copy of the GNU Affero General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17#pragma once
18
19#include <vector>
20#include <string>
21#include <optional>
22#include <filesystem>
23#include <fstream>
24#include <algorithm>
25#include <ctime>
26#include <functional>
27
28#if defined(_WIN32)
29#include <windows.h>
30#include <io.h> // for SetFilePointer, etc. if needed
31#else
32#include <sys/file.h>
33#include <sys/stat.h>
34#include <fcntl.h>
35#include <unistd.h>
36#endif
37
38namespace grk
39{
40
41namespace fs = std::filesystem;
42
43template<typename T>
45{
46public:
47 static bool store(const std::vector<T>& data, const std::string& path)
48 {
49 static_assert(std::is_standard_layout_v<T> && std::is_trivial_v<T>,
50 "T must be a POD type for binary serialization.");
51
52 auto time = getLastModified(path);
53 if(!time)
54 return false;
55 auto filename = generateFilename(path, time.value());
56
57 // Check if file already exists in any search path
58 auto searchPaths = getSearchPaths();
59 for(const auto& dir : searchPaths)
60 {
61 fs::path fullPath = fs::path(dir) / filename;
62 if(fs::exists(fullPath))
63 return true;
64 }
65
66 auto cacheDir = getCacheDir();
67
68 // Try cache dir first
69 fs::path dirPath(cacheDir);
70 try
71 {
72 fs::create_directories(dirPath);
73 fs::path fullPath = dirPath / filename;
74 if(writeToFileWithLock(fullPath, data))
75 return true;
76 }
77 catch(...)
78 {
79 // Fallback to temp dir
80 }
81
82 std::string tempDir = getTempDir();
83 fs::path tempDirPath(tempDir);
84 try
85 {
86 fs::create_directories(tempDirPath);
87 fs::path fullPath = tempDirPath / filename;
88 return writeToFileWithLock(fullPath, data);
89 }
90 catch(...)
91 {
92 return false;
93 }
94 }
95
96 static std::optional<std::vector<T>> load(const std::string& path)
97 {
98 static_assert(std::is_standard_layout_v<T> && std::is_trivial_v<T>,
99 "T must be a POD type for binary serialization.");
100 auto time = getLastModified(path);
101 if(!time)
102 return std::nullopt;
103 auto filename = generateFilename(path, time.value());
104
105 auto searchPaths = getSearchPaths();
106 for(const auto& dir : searchPaths)
107 {
108 fs::path fullPath = fs::path(dir) / filename;
109 if(!fs::exists(fullPath))
110 continue;
111
112 auto data = readFromFileWithLock(fullPath);
113 if(data)
114 return data;
115 }
116 return std::nullopt;
117 }
118
119private:
120 static bool writeToFileWithLock(const fs::path& fullPath, const std::vector<T>& data)
121 {
122#if defined(_WIN32)
123 HANDLE hFile = CreateFileA(fullPath.string().c_str(), GENERIC_READ | GENERIC_WRITE,
124 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,
125 FILE_ATTRIBUTE_NORMAL, NULL);
126 if(hFile == INVALID_HANDLE_VALUE)
127 return false;
128
129 OVERLAPPED overlapped = {};
130 BOOL locked = LockFileEx(hFile, LOCKFILE_EXCLUSIVE_LOCK, 0, MAXDWORD, 0, &overlapped);
131 if(!locked)
132 {
133 CloseHandle(hFile);
134 return false;
135 }
136
137 LARGE_INTEGER fileSize = {};
138 GetFileSizeEx(hFile, &fileSize);
139 if(fileSize.QuadPart >= sizeof(size_t))
140 {
141 UnlockFileEx(hFile, 0, MAXDWORD, 0, &overlapped);
142 CloseHandle(hFile);
143 return true;
144 }
145
146 // Write from beginning
147 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
148 DWORD bytesWritten;
149 size_t vecSize = data.size();
150 WriteFile(hFile, &vecSize, sizeof(vecSize), &bytesWritten, NULL);
151 if(!data.empty())
152 {
153 WriteFile(hFile, data.data(), static_cast<DWORD>(sizeof(T) * vecSize), &bytesWritten, NULL);
154 }
155 SetEndOfFile(hFile);
156
157 UnlockFileEx(hFile, 0, MAXDWORD, 0, &overlapped);
158 CloseHandle(hFile);
159 return true;
160#else
161 int fd = ::open(fullPath.string().c_str(), O_RDWR | O_CREAT, 0666);
162 if(fd == -1)
163 return false;
164
165 if(::flock(fd, LOCK_EX) == -1)
166 {
167 ::close(fd);
168 return false;
169 }
170
171 struct stat st;
172 if(::fstat(fd, &st) == -1)
173 {
174 ::flock(fd, LOCK_UN);
175 ::close(fd);
176 return false;
177 }
178
179 if(st.st_size >= sizeof(size_t))
180 {
181 ::flock(fd, LOCK_UN);
182 ::close(fd);
183 return true;
184 }
185
186 ::lseek(fd, 0, SEEK_SET);
187 size_t vecSize = data.size();
188 if(::write(fd, &vecSize, sizeof(vecSize)) != sizeof(vecSize))
189 {
190 ::flock(fd, LOCK_UN);
191 ::close(fd);
192 return false;
193 }
194 if(!data.empty())
195 {
196 if(::write(fd, data.data(), sizeof(T) * vecSize) != static_cast<ssize_t>(sizeof(T) * vecSize))
197 {
198 ::flock(fd, LOCK_UN);
199 ::close(fd);
200 return false;
201 }
202 }
203 ::ftruncate(fd, sizeof(vecSize) + sizeof(T) * vecSize);
204
205 ::flock(fd, LOCK_UN);
206 ::close(fd);
207 return true;
208#endif
209 }
210
211 static std::optional<std::vector<T>> readFromFileWithLock(const fs::path& fullPath)
212 {
213#if defined(_WIN32)
214 HANDLE hFile =
215 CreateFileA(fullPath.string().c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
216 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
217 if(hFile == INVALID_HANDLE_VALUE)
218 return std::nullopt;
219
220 OVERLAPPED overlapped = {};
221 BOOL locked = LockFileEx(hFile, 0, 0, MAXDWORD, 0, &overlapped);
222 if(!locked)
223 {
224 CloseHandle(hFile);
225 return std::nullopt;
226 }
227
228 SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
229 size_t vecSize;
230 DWORD bytesRead;
231 if(!ReadFile(hFile, &vecSize, sizeof(vecSize), &bytesRead, NULL) ||
232 bytesRead != sizeof(vecSize))
233 {
234 UnlockFileEx(hFile, 0, MAXDWORD, 0, &overlapped);
235 CloseHandle(hFile);
236 return std::nullopt;
237 }
238
239 std::vector<T> data(vecSize);
240 if(vecSize > 0)
241 {
242 if(!ReadFile(hFile, data.data(), static_cast<DWORD>(sizeof(T) * vecSize), &bytesRead, NULL) ||
243 bytesRead != static_cast<DWORD>(sizeof(T) * vecSize))
244 {
245 UnlockFileEx(hFile, 0, MAXDWORD, 0, &overlapped);
246 CloseHandle(hFile);
247 return std::nullopt;
248 }
249 }
250
251 UnlockFileEx(hFile, 0, MAXDWORD, 0, &overlapped);
252 CloseHandle(hFile);
253 return data;
254#else
255 int fd = ::open(fullPath.string().c_str(), O_RDONLY);
256 if(fd == -1)
257 return std::nullopt;
258
259 if(::flock(fd, LOCK_SH) == -1)
260 {
261 ::close(fd);
262 return std::nullopt;
263 }
264
265 size_t vecSize;
266 ssize_t readn = ::read(fd, &vecSize, sizeof(vecSize));
267 if(readn != sizeof(vecSize))
268 {
269 ::flock(fd, LOCK_UN);
270 ::close(fd);
271 return std::nullopt;
272 }
273
274 std::vector<T> data(vecSize);
275 if(vecSize > 0)
276 {
277 readn = ::read(fd, data.data(), sizeof(T) * vecSize);
278 if(readn != static_cast<ssize_t>(sizeof(T) * vecSize))
279 {
280 ::flock(fd, LOCK_UN);
281 ::close(fd);
282 return std::nullopt;
283 }
284 }
285
286 ::flock(fd, LOCK_UN);
287 ::close(fd);
288 return data;
289#endif
290 }
291
292 static std::optional<time_t> getLastModified(const std::string& path)
293 {
294 try
295 {
296 auto ftime = fs::last_write_time(path);
297 auto sctp = std::chrono::system_clock::time_point(
298 std::chrono::duration_cast<std::chrono::system_clock::duration>(
299 ftime.time_since_epoch()));
300 return std::chrono::system_clock::to_time_t(sctp);
301 }
302 catch(...)
303 {
304 return std::nullopt;
305 }
306 }
307
308 static std::string getCacheDir()
309 {
310#if defined(_WIN32)
311 char* localAppData = getenv("LOCALAPPDATA");
312 if(localAppData)
313 {
314 return std::string(localAppData) + "\\TLMCache";
315 }
316 return getTempDir();
317#elif defined(__APPLE__)
318 char* home = getenv("HOME");
319 if(home)
320 {
321 return std::string(home) + "/Library/Caches/TLMCache";
322 }
323 return getTempDir();
324#else // Linux/Unix
325 char* xdgCache = getenv("XDG_CACHE_HOME");
326 if(xdgCache)
327 {
328 return std::string(xdgCache) + "/TLMCache";
329 }
330 char* home = getenv("HOME");
331 if(home)
332 {
333 return std::string(home) + "/.cache/TLMCache";
334 }
335 return getTempDir();
336#endif
337 }
338
339 static std::string getTempDir()
340 {
341#if defined(_WIN32)
342 char* temp = getenv("TEMP");
343 if(temp)
344 return temp;
345 temp = getenv("TMP");
346 if(temp)
347 return temp;
348 return "C:\\Temp";
349#else
350 return "/tmp";
351#endif
352 }
353
354 static std::string generateFilename(const std::string& path, time_t time)
355 {
356 std::hash<std::string> hasher;
357 size_t hashValue = hasher(path);
358 return std::to_string(hashValue) + "_" + std::to_string(time);
359 }
360
361 static std::vector<std::string> getSearchPaths()
362 {
363 return {getCacheDir(), getTempDir()};
364 }
365};
366
367} // namespace grk
Definition TLMFile.h:45
static std::optional< std::vector< T > > readFromFileWithLock(const fs::path &fullPath)
Definition TLMFile.h:211
static bool store(const std::vector< T > &data, const std::string &path)
Definition TLMFile.h:47
static std::string generateFilename(const std::string &path, time_t time)
Definition TLMFile.h:354
static std::vector< std::string > getSearchPaths()
Definition TLMFile.h:361
static std::string getCacheDir()
Definition TLMFile.h:308
static std::optional< time_t > getLastModified(const std::string &path)
Definition TLMFile.h:292
static bool writeToFileWithLock(const fs::path &fullPath, const std::vector< T > &data)
Definition TLMFile.h:120
static std::string getTempDir()
Definition TLMFile.h:339
static std::optional< std::vector< T > > load(const std::string &path)
Definition TLMFile.h:96
ResWindow.
Definition CompressedChunkCache.h:36
void write(const void *p_src_data, void *p_dest_data, uint64_t nb_elem)
Definition CodeStream.h:90