48#define N_DISK_CACHE_SEED 0x4E444331u
51#define N_DISK_CACHE_PATH_MAX 4096
71static wchar_t* _n_dc_wide(
const char* path) {
72 int n = MultiByteToWideChar(CP_UTF8, 0, path, -1, NULL, 0);
75 if (n <= 0)
return NULL;
76 w = (
wchar_t*)malloc(
sizeof(
wchar_t) * (
size_t)n);
78 if (MultiByteToWideChar(CP_UTF8, 0, path, -1, w, n) <= 0) {
86static int64_t _n_dc_filetime(
const FILETIME* ft) {
87 uint64_t t = ((uint64_t)ft->dwHighDateTime << 32) | (uint64_t)ft->dwLowDateTime;
91 return (int64_t)(t / 10000000ULL) - 11644473600LL;
98 wchar_t* wp = _n_dc_wide(path);
99 wchar_t* wm = _n_dc_wide(
mode);
100 FILE* f = (wp && wm) ? _wfopen(wp, wm) : NULL;
106 return fopen(path,
mode);
113 wchar_t* wp = _n_dc_wide(path);
115 if (!wp)
return FALSE;
116 int ok = DeleteFileW(wp) || GetLastError() == ERROR_FILE_NOT_FOUND;
118 return ok ? TRUE : FALSE;
120 return (unlink(path) == 0 || errno == ENOENT) ? TRUE : FALSE;
127 wchar_t* wf = _n_dc_wide(from);
128 wchar_t* wt = _n_dc_wide(to);
129 int ok = (wf && wt && MoveFileExW(wf, wt, MOVEFILE_REPLACE_EXISTING)) ? TRUE : FALSE;
135 return rename(from, to) == 0 ? TRUE : FALSE;
142 wchar_t* wp = _n_dc_wide(path);
143 DWORD attr = wp ? GetFileAttributesW(wp) : INVALID_FILE_ATTRIBUTES;
146 return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY)) ? TRUE : FALSE;
150 return (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) ? TRUE : FALSE;
162 if (!((c >=
'0' && c <=
'9') || (c >=
'a' && c <=
'f')))
return FALSE;
169 int n = snprintf(out, out_len,
"%s/%s", cache->
dir, name);
171 return (n > 0 && (
size_t)n < out_len) ? TRUE : FALSE;
178 int count = 0, room = 0;
186 HANDLE h = INVALID_HANDLE_VALUE;
188 if (!
_n_dc_join(cache,
"*", pattern,
sizeof(pattern)))
return -1;
189 wp = _n_dc_wide(pattern);
191 h = FindFirstFileW(wp, &fd);
193 if (h == INVALID_HANDLE_VALUE)
return GetLastError() == ERROR_FILE_NOT_FOUND ? 0 : -1;
198 if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
continue;
201 if (WideCharToMultiByte(CP_UTF8, 0, fd.cFileName, -1, name, (
int)
sizeof(name), NULL, NULL) <= 0)
continue;
206 room = room ? room * 2 : 256;
207 grown = (
_N_DC_ENTRY*)realloc(list,
sizeof(*list) * (
size_t)room);
215 snprintf(list[count].name,
sizeof(list[count].name),
"%s", name);
216 list[count].
size = ((uint64_t)fd.nFileSizeHigh << 32) | (uint64_t)fd.nFileSizeLow;
217 used = _n_dc_filetime(&fd.ftLastAccessTime);
218 list[count].
used = used ? used : _n_dc_filetime(&fd.ftLastWriteTime);
220 }
while (FindNextFileW(h, &fd));
225 DIR* dp = opendir(cache->
dir);
226 const struct dirent* de = NULL;
229 while ((de = readdir(dp)) != NULL) {
235 name_len = strlen(de->d_name);
236 if (name_len >=
sizeof(list[0].name))
continue;
237 if (!
_n_dc_join(cache, de->d_name, path,
sizeof(path)))
continue;
239 if (stat(path, &st) != 0 || !S_ISREG(st.st_mode))
continue;
243 room = room ? room * 2 : 256;
244 grown = (
_N_DC_ENTRY*)realloc(list,
sizeof(*list) * (
size_t)room);
252 memcpy(list[count].name, de->d_name, name_len + 1);
253 list[count].
size = (uint64_t)st.st_size;
254 list[count].
used = (int64_t)(st.st_atime ? st.st_atime : st.st_mtime);
269 return (ua > ub) - (ua < ub);
279 while (len > 1 && (dir[len - 1] ==
'/' || dir[len - 1] ==
'\\')) len--;
291 memcpy(cache->
dir, dir, len);
292 cache->
dir[len] =
'\0';
303 if (!cache || !*cache)
return;
309 return cache ? cache->
dir :
"";
313 uint64_t hash[2] = {0, 0};
321 if (strchr(ext,
'/') || strchr(ext,
'\\') || strlen(ext) >=
sizeof(name) -
N_DISK_CACHE_NAME_LEN)
return FALSE;
323 n = snprintf(name,
sizeof(name),
"%016llx%016llx%s", (
unsigned long long)hash[0], (
unsigned long long)hash[1], ext);
324 if (n <= 0 || (
size_t)n >=
sizeof(name))
return FALSE;
335 if (!data && len)
return FALSE;
341 snprintf(tmp,
sizeof(tmp),
"%s.tmp", path);
347 if (len && fwrite(data, 1, len, f) != len) ok = FALSE;
348 if (fclose(f) != 0) ok = FALSE;
359 unsigned char* buf = NULL;
370 if (!f)
return FALSE;
371 if (fseek(f, 0, SEEK_END) != 0 || (size = ftell(f)) < 0 || (uint64_t)size > (uint64_t)max_len || fseek(f, 0, SEEK_SET) != 0) {
375 buf = (
unsigned char*)malloc((
size_t)size + 1);
380 if (size && fread(buf, 1, (
size_t)size, f) != (
size_t)size) {
402 int count = 0, i = 0;
408 for (i = 0; i < count; i++) *total += list[i].size;
416 int count = 0, i = 0, removed = 0;
420 if (count < 0)
return -1;
421 for (i = 0; i < count; i++) total += list[i].size;
422 if (total > max_bytes || max_bytes == 0) {
423 qsort(list, (
size_t)count,
sizeof(*list),
_n_dc_older);
424 for (i = 0; i < count && (total > max_bytes || max_bytes == 0); i++) {
427 if (!
_n_dc_join(cache, list[i].name, path,
sizeof(path)))
continue;
429 total -= list[i].
size;
435 if (removed)
n_log(
LOG_DEBUG,
"disk cache: %s, removed %d entries, %llu bytes left", cache->
dir, removed, (
unsigned long long)total);
454 size_t total_count = 0, room = 0;
456 int c = 0, removed = 0;
459 if (count <= 0)
return -1;
462 for (c = 0; c < count; c++) {
466 if (!caches[c])
continue;
472 if (total_count + (
size_t)n > room) {
475 room = total_count + (size_t)n + 256;
484 for (k = 0; k < n; k++) {
485 all[total_count].
entry = list[k];
486 all[total_count].
owner = c;
487 total += list[k].
size;
493 if (all && total_count && (total > max_bytes || max_bytes == 0)) {
497 for (i = 0; i < total_count && (total > max_bytes || max_bytes == 0); i++) {
500 if (!
_n_dc_join(caches[all[i].owner], all[i].entry.name, path,
sizeof(path)))
continue;
508 if (removed)
n_log(
LOG_DEBUG,
"disk cache: %d caches, removed %d entries, %llu bytes left", count, removed, (
unsigned long long)total);
515 int count = 0, i = 0, removed = 0;
518 if (max_age_seconds <= 0)
return 0;
520 if (count < 0)
return -1;
521 cutoff = (int64_t)time(NULL) - max_age_seconds;
522 for (i = 0; i < count; i++) {
525 if (list[i].used > cutoff)
continue;
529 if (removed)
n_log(
LOG_DEBUG,
"disk cache: %s, removed %d entries unused for %lld s", cache->
dir, removed, (
long long)max_age_seconds);
volatile char key[227]
TRUE while the key is held.
#define Malloc(__ptr, __struct, __size)
Malloc Handler to get errors and set to 0.
#define __n_assert(__ptr, __ret)
macro to assert things
#define Free(__ptr)
Free Handler to get errors.
void MurmurHash3_x64_128(const void *key, const size_t len, const uint64_t seed, void *out)
MurmurHash3 was written by Austin Appleby, and is placed in the public domain.
#define n_log(__LEVEL__,...)
Logging function wrapper to get line and func.
#define LOG_DEBUG
debug-level messages
#define LOG_ERR
error conditions
const char * n_disk_cache_dir(const N_DISK_CACHE *cache)
The cache's directory, as it was opened.
#define N_DISK_CACHE_NAME_LEN
length of an entry's name before its extension: 128 bits in hexadecimal
int n_disk_cache_get(const N_DISK_CACHE *cache, const void *key, size_t key_len, const char *ext, size_t max_len, unsigned char **data, size_t *len)
Read an entry.
int n_disk_cache_path(const N_DISK_CACHE *cache, const void *key, size_t key_len, const char *ext, char *out, size_t out_len)
The file an entry lives in, whether or not it exists.
void n_disk_cache_close(N_DISK_CACHE **cache)
Forget a cache.
int n_disk_cache_put(const N_DISK_CACHE *cache, const void *key, size_t key_len, const char *ext, const void *data, size_t len)
Store an entry, replacing any under the same key and extension.
int n_disk_cache_prune_group(const N_DISK_CACHE *const *caches, int count, uint64_t max_bytes)
n_disk_cache_prune() over several caches that share one budget.
int n_disk_cache_prune(const N_DISK_CACHE *cache, uint64_t max_bytes)
Remove least recently used entries until the rest fit a budget.
int n_disk_cache_remove(const N_DISK_CACHE *cache, const void *key, size_t key_len, const char *ext)
Remove one entry, for a caller that found it unreadable.
int n_disk_cache_expire(const N_DISK_CACHE *cache, int64_t max_age_seconds)
Remove every entry unused for longer than an age.
int n_disk_cache_size(const N_DISK_CACHE *cache, uint64_t *total)
How many bytes the cache's entries take.
N_DISK_CACHE * n_disk_cache_open(const char *dir)
Use a directory as a cache.
#define N_DISK_CACHE_SEED
seed of the key hash.
char * dir
the directory, without a trailing separator
int owner
which of the caches
static int _n_dc_join(const N_DISK_CACHE *cache, const char *name, char *out, size_t out_len)
helper: join the directory and a file name
static int _n_dc_owned(const char *name)
helper: is name one this module could have made: 32 lowercase hex digits, then the end or a '.
static int _n_dc_remove(const char *path)
helper: remove a UTF-8 path.
_N_DC_ENTRY entry
the entry
static int _n_dc_replace(const char *from, const char *to)
helper: rename over an existing target
#define N_DISK_CACHE_PATH_MAX
longest path this module builds
int64_t used
last used: access time, else write time
uint64_t size
size in bytes
static FILE * _n_dc_fopen(const char *path, const char *mode)
helper: fopen a UTF-8 path
static int _n_dc_group_older(const void *a, const void *b)
qsort order for a group: least recently used first
char name[32+64]
file name, an entry's by construction
static int _n_dc_is_dir(const char *path)
helper: is path a directory
static int _n_dc_walk(const N_DISK_CACHE *cache, _N_DC_ENTRY **out)
helper: every entry of the cache, in a new array the caller frees.
static int _n_dc_older(const void *a, const void *b)
qsort order: least recently used first
one entry found by a walk of the directory
one entry of a group prune: the entry and the cache it is in
a cache: only where it is
A bounded cache of files in one directory, keyed by content.
Hash functions and table.