Skip to content

Commit 3d7ea72

Browse files
committed
Logo (Image): improves caching logic; takes mtime into account
1 parent a90ae02 commit 3d7ea72

1 file changed

Lines changed: 70 additions & 34 deletions

File tree

src/logo/image/image.c

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,9 @@ static bool printImageKittyDirect(bool printError) {
351351
#define FF_CACHE_FILE_KITTY_COMPRESSED "kittyc"
352352
#define FF_CACHE_FILE_KITTY_UNCOMPRESSED "kittyu"
353353
#define FF_CACHE_FILE_CHAFA "chafa"
354+
// Modification time of the image source the entry was produced from. Written last, so an
355+
// entry that was interrupted mid-write is never mistaken for a complete one.
356+
#define FF_CACHE_FILE_MTIME "mtime"
354357

355358
#include <stdlib.h>
356359
#include <string.h>
@@ -401,35 +404,28 @@ static bool compressBlob(void** blob, size_t* length) {
401404

402405
#endif // FF_HAVE_ZLIB
403406

404-
static void writeCacheStrbuf(FFLogoRequestData* requestData, const FFstrbuf* value, const char* cacheFileName) {
407+
static void writeCacheData(FFLogoRequestData* requestData, const void* value, size_t len, const char* cacheFileName) {
405408
uint32_t cacheDirLength = requestData->cacheDir.length;
406409
ffStrbufAppendS(&requestData->cacheDir, cacheFileName);
407-
ffWriteFileBuffer(requestData->cacheDir.chars, value);
410+
ffWriteFileData(requestData->cacheDir.chars, len, value);
408411
ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);
409412
}
410413

411-
static void writeCacheUint32(FFLogoRequestData* requestData, uint32_t value, const char* cacheFileName) {
412-
FFstrbuf content;
413-
content.chars = (char*) &value;
414-
content.length = sizeof(value);
415-
writeCacheStrbuf(requestData, &content, cacheFileName);
416-
}
417-
418414
static void printImagePixels(FFLogoRequestData* requestData, const FFstrbuf* result, const char* cacheFileName) {
419415
const FFOptionsLogo* options = &instance.config.logo;
420416
// Calculate character dimensions
421417
instance.state.logoWidth = requestData->logoCharacterWidth + options->paddingLeft + options->paddingRight;
422418
instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop - 1;
423419

424420
// Write cache files
425-
writeCacheStrbuf(requestData, result, cacheFileName);
421+
writeCacheData(requestData, result->chars, result->length, cacheFileName);
426422

427423
if (options->width == 0) {
428-
writeCacheUint32(requestData, requestData->logoCharacterWidth, FF_CACHE_FILE_WIDTH);
424+
writeCacheData(requestData, &requestData->logoCharacterWidth, sizeof(requestData->logoCharacterWidth), FF_CACHE_FILE_WIDTH);
429425
}
430426

431427
if (options->height == 0) {
432-
writeCacheUint32(requestData, requestData->logoCharacterHeight, FF_CACHE_FILE_HEIGHT);
428+
writeCacheData(requestData, &requestData->logoCharacterHeight, sizeof(requestData->logoCharacterHeight), FF_CACHE_FILE_HEIGHT);
433429
}
434430

435431
// Write result to stdout
@@ -591,7 +587,7 @@ static bool printImageChafa(FFLogoRequestData* requestData, const FFImageBuffer*
591587
result.chars = str->str;
592588

593589
ffLogoPrintChars(result.chars, false);
594-
writeCacheStrbuf(requestData, &result, FF_CACHE_FILE_CHAFA);
590+
writeCacheData(requestData, &result.chars, result.length, FF_CACHE_FILE_CHAFA);
595591

596592
// FIXME: These functions must be imported from `libglib` dlls on Windows
597593
FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_string_free);
@@ -691,28 +687,62 @@ static FFNativeFD getCacheFD(FFLogoRequestData* requestData, const char* fileNam
691687
return fd;
692688
}
693689

694-
static void readCachedStrbuf(FFLogoRequestData* requestData, FFstrbuf* result, const char* cacheFileName) {
690+
static bool readCachedStrbuf(FFLogoRequestData* requestData, FFstrbuf* result, const char* cacheFileName) {
695691
uint32_t cacheDirLength = requestData->cacheDir.length;
696692
ffStrbufAppendS(&requestData->cacheDir, cacheFileName);
697-
ffAppendFileBuffer(requestData->cacheDir.chars, result);
693+
bool res = ffAppendFileBuffer(requestData->cacheDir.chars, result);
698694
ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);
695+
return res;
699696
}
700697

701-
static uint32_t readCachedUint32(FFLogoRequestData* requestData, const char* cacheFileName) {
702-
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
703-
readCachedStrbuf(requestData, &content, cacheFileName);
698+
static bool readCachedData(FFLogoRequestData* requestData, void* buffer, size_t bufferSize, const char* cacheFileName) {
699+
uint32_t cacheDirLength = requestData->cacheDir.length;
700+
ffStrbufAppendS(&requestData->cacheDir, cacheFileName);
701+
bool res = ffReadFileData(requestData->cacheDir.chars, bufferSize, buffer) == (ssize_t) bufferSize;
702+
ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);
703+
return res;
704+
}
704705

706+
static uint32_t readCachedUint32(FFLogoRequestData* requestData, const char* cacheFileName) {
705707
uint32_t result = 0;
706-
707-
if (content.length != sizeof(result)) {
708+
if (!readCachedData(requestData, &result, sizeof(result), cacheFileName)) {
708709
return 0;
709710
}
710711

711-
memcpy(&result, content.chars, sizeof(result));
712+
return result;
713+
}
714+
715+
static uint64_t readCachedUint64(FFLogoRequestData* requestData, const char* cacheFileName) {
716+
uint64_t result = 0;
717+
if (!readCachedData(requestData, &result, sizeof(result), cacheFileName)) {
718+
return 0;
719+
}
712720

713721
return result;
714722
}
715723

724+
// Drops everything a previous version of the source left in the entry directory.
725+
// The directory is keyed on the source path and the pixel size only, so it is reused across
726+
// edits; without this, a payload written for another logo type would be read back.
727+
static void removeCachedFiles(FFLogoRequestData* requestData) {
728+
static const char* const files[] = {
729+
FF_CACHE_FILE_MTIME,
730+
FF_CACHE_FILE_WIDTH,
731+
FF_CACHE_FILE_HEIGHT,
732+
FF_CACHE_FILE_SIXEL,
733+
FF_CACHE_FILE_KITTY_COMPRESSED,
734+
FF_CACHE_FILE_KITTY_UNCOMPRESSED,
735+
FF_CACHE_FILE_CHAFA,
736+
};
737+
738+
uint32_t cacheDirLength = requestData->cacheDir.length;
739+
for (uint32_t i = 0; i < ARRAY_SIZE(files); ++i) {
740+
ffStrbufAppendS(&requestData->cacheDir, files[i]);
741+
ffRemoveFile(requestData->cacheDir.chars);
742+
ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);
743+
}
744+
}
745+
716746
static bool printCachedChars(FFLogoRequestData* requestData, const char* cacheFileName) {
717747
FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
718748
readCachedStrbuf(requestData, &content, cacheFileName);
@@ -866,21 +896,18 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) {
866896
ffStrbufRecalculateLength(&requestData.cacheDir);
867897
ffStrbufEnsureEndsWithC(&requestData.cacheDir, '/');
868898

869-
// The cache is indexed by source path and pixel size only, so it has to be namespaced
870-
// by backend: different backends (and different sixel encoders) produce different bytes
871-
#ifdef _WIN32
872-
ffStrbufAppendS(&requestData.cacheDir, "wic/");
873-
#elif defined(__APPLE__)
874-
ffStrbufAppendS(&requestData.cacheDir, "imageio/");
875-
#elif defined(FF_HAVE_IMAGEMAGICK7)
876-
ffStrbufAppendS(&requestData.cacheDir, "im7/");
877-
#elif defined(FF_HAVE_IMAGEMAGICK6)
878-
ffStrbufAppendS(&requestData.cacheDir, "im6/");
879-
#endif
899+
ffStrbufAppendF(&requestData.cacheDir, "%ux%u/", requestData.logoPixelWidth, requestData.logoPixelHeight);
880900

881-
ffStrbufAppendF(&requestData.cacheDir, "%u*%u/", requestData.logoPixelWidth, requestData.logoPixelHeight);
901+
// The cached payload is a rendering, not a bit-exact artefact: every backend produces a
902+
// valid one for the same source and pixel size, so the backend is deliberately not part of
903+
// the key. What the key does have to capture is the content of the source, which the path
904+
// can not: the same file can be replaced in place. Hence the recorded mtime.
905+
// 0 means the mtime could not be read, in which case the entry is never trusted.
906+
const uint64_t sourceMtime = ffPathGetMtime(instance.config.logo.source.chars);
882907

883-
if (!instance.config.logo.recache) {
908+
if (!instance.config.logo.recache &&
909+
sourceMtime != 0 &&
910+
readCachedUint64(&requestData, FF_CACHE_FILE_MTIME) == sourceMtime) {
884911
bool cacheValid = requestData.type == FF_LOGO_TYPE_IMAGE_CHAFA
885912
? printCachedChars(&requestData, FF_CACHE_FILE_CHAFA)
886913
: printCachedPixel(&requestData);
@@ -890,6 +917,10 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) {
890917
}
891918
}
892919

920+
// Cache miss. The entry directory is keyed on the source path and the pixel size only, so
921+
// it is reused when the source is edited; drop what the previous version left behind.
922+
removeCachedFiles(&requestData);
923+
893924
const char* error = nullptr;
894925
bool printSuccessful = false;
895926

@@ -916,6 +947,11 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) {
916947
}
917948
}
918949

950+
if (printSuccessful) {
951+
// Written last: an entry only becomes usable once its payload is complete
952+
writeCacheData(&requestData, &sourceMtime, sizeof(sourceMtime), FF_CACHE_FILE_MTIME);
953+
}
954+
919955
ffStrbufDestroy(&requestData.cacheDir);
920956

921957
if (printSuccessful) {

0 commit comments

Comments
 (0)