From 3d7ea720fb561ac1f2ee2e0eb3bac645d23a5fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Mon, 14 Sep 2026 22:43:19 +0800 Subject: [PATCH] Logo (Image): improves caching logic; takes mtime into account --- src/logo/image/image.c | 104 +++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/src/logo/image/image.c b/src/logo/image/image.c index 21a46ae89..514a0bc88 100644 --- a/src/logo/image/image.c +++ b/src/logo/image/image.c @@ -351,6 +351,9 @@ static bool printImageKittyDirect(bool printError) { #define FF_CACHE_FILE_KITTY_COMPRESSED "kittyc" #define FF_CACHE_FILE_KITTY_UNCOMPRESSED "kittyu" #define FF_CACHE_FILE_CHAFA "chafa" + // Modification time of the image source the entry was produced from. Written last, so an + // entry that was interrupted mid-write is never mistaken for a complete one. + #define FF_CACHE_FILE_MTIME "mtime" #include #include @@ -401,20 +404,13 @@ static bool compressBlob(void** blob, size_t* length) { #endif // FF_HAVE_ZLIB -static void writeCacheStrbuf(FFLogoRequestData* requestData, const FFstrbuf* value, const char* cacheFileName) { +static void writeCacheData(FFLogoRequestData* requestData, const void* value, size_t len, const char* cacheFileName) { uint32_t cacheDirLength = requestData->cacheDir.length; ffStrbufAppendS(&requestData->cacheDir, cacheFileName); - ffWriteFileBuffer(requestData->cacheDir.chars, value); + ffWriteFileData(requestData->cacheDir.chars, len, value); ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength); } -static void writeCacheUint32(FFLogoRequestData* requestData, uint32_t value, const char* cacheFileName) { - FFstrbuf content; - content.chars = (char*) &value; - content.length = sizeof(value); - writeCacheStrbuf(requestData, &content, cacheFileName); -} - static void printImagePixels(FFLogoRequestData* requestData, const FFstrbuf* result, const char* cacheFileName) { const FFOptionsLogo* options = &instance.config.logo; // Calculate character dimensions @@ -422,14 +418,14 @@ static void printImagePixels(FFLogoRequestData* requestData, const FFstrbuf* res instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop - 1; // Write cache files - writeCacheStrbuf(requestData, result, cacheFileName); + writeCacheData(requestData, result->chars, result->length, cacheFileName); if (options->width == 0) { - writeCacheUint32(requestData, requestData->logoCharacterWidth, FF_CACHE_FILE_WIDTH); + writeCacheData(requestData, &requestData->logoCharacterWidth, sizeof(requestData->logoCharacterWidth), FF_CACHE_FILE_WIDTH); } if (options->height == 0) { - writeCacheUint32(requestData, requestData->logoCharacterHeight, FF_CACHE_FILE_HEIGHT); + writeCacheData(requestData, &requestData->logoCharacterHeight, sizeof(requestData->logoCharacterHeight), FF_CACHE_FILE_HEIGHT); } // Write result to stdout @@ -591,7 +587,7 @@ static bool printImageChafa(FFLogoRequestData* requestData, const FFImageBuffer* result.chars = str->str; ffLogoPrintChars(result.chars, false); - writeCacheStrbuf(requestData, &result, FF_CACHE_FILE_CHAFA); + writeCacheData(requestData, &result.chars, result.length, FF_CACHE_FILE_CHAFA); // FIXME: These functions must be imported from `libglib` dlls on Windows FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_string_free); @@ -691,28 +687,62 @@ static FFNativeFD getCacheFD(FFLogoRequestData* requestData, const char* fileNam return fd; } -static void readCachedStrbuf(FFLogoRequestData* requestData, FFstrbuf* result, const char* cacheFileName) { +static bool readCachedStrbuf(FFLogoRequestData* requestData, FFstrbuf* result, const char* cacheFileName) { uint32_t cacheDirLength = requestData->cacheDir.length; ffStrbufAppendS(&requestData->cacheDir, cacheFileName); - ffAppendFileBuffer(requestData->cacheDir.chars, result); + bool res = ffAppendFileBuffer(requestData->cacheDir.chars, result); ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength); + return res; +} + +static bool readCachedData(FFLogoRequestData* requestData, void* buffer, size_t bufferSize, const char* cacheFileName) { + uint32_t cacheDirLength = requestData->cacheDir.length; + ffStrbufAppendS(&requestData->cacheDir, cacheFileName); + bool res = ffReadFileData(requestData->cacheDir.chars, bufferSize, buffer) == (ssize_t) bufferSize; + ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength); + return res; } static uint32_t readCachedUint32(FFLogoRequestData* requestData, const char* cacheFileName) { - FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); - readCachedStrbuf(requestData, &content, cacheFileName); - uint32_t result = 0; - - if (content.length != sizeof(result)) { + if (!readCachedData(requestData, &result, sizeof(result), cacheFileName)) { return 0; } - memcpy(&result, content.chars, sizeof(result)); + return result; +} + +static uint64_t readCachedUint64(FFLogoRequestData* requestData, const char* cacheFileName) { + uint64_t result = 0; + if (!readCachedData(requestData, &result, sizeof(result), cacheFileName)) { + return 0; + } return result; } +// Drops everything a previous version of the source left in the entry directory. +// The directory is keyed on the source path and the pixel size only, so it is reused across +// edits; without this, a payload written for another logo type would be read back. +static void removeCachedFiles(FFLogoRequestData* requestData) { + static const char* const files[] = { + FF_CACHE_FILE_MTIME, + FF_CACHE_FILE_WIDTH, + FF_CACHE_FILE_HEIGHT, + FF_CACHE_FILE_SIXEL, + FF_CACHE_FILE_KITTY_COMPRESSED, + FF_CACHE_FILE_KITTY_UNCOMPRESSED, + FF_CACHE_FILE_CHAFA, + }; + + uint32_t cacheDirLength = requestData->cacheDir.length; + for (uint32_t i = 0; i < ARRAY_SIZE(files); ++i) { + ffStrbufAppendS(&requestData->cacheDir, files[i]); + ffRemoveFile(requestData->cacheDir.chars); + ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength); + } +} + static bool printCachedChars(FFLogoRequestData* requestData, const char* cacheFileName) { FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate(); readCachedStrbuf(requestData, &content, cacheFileName); @@ -866,21 +896,18 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) { ffStrbufRecalculateLength(&requestData.cacheDir); ffStrbufEnsureEndsWithC(&requestData.cacheDir, '/'); - // The cache is indexed by source path and pixel size only, so it has to be namespaced - // by backend: different backends (and different sixel encoders) produce different bytes - #ifdef _WIN32 - ffStrbufAppendS(&requestData.cacheDir, "wic/"); - #elif defined(__APPLE__) - ffStrbufAppendS(&requestData.cacheDir, "imageio/"); - #elif defined(FF_HAVE_IMAGEMAGICK7) - ffStrbufAppendS(&requestData.cacheDir, "im7/"); - #elif defined(FF_HAVE_IMAGEMAGICK6) - ffStrbufAppendS(&requestData.cacheDir, "im6/"); - #endif + ffStrbufAppendF(&requestData.cacheDir, "%ux%u/", requestData.logoPixelWidth, requestData.logoPixelHeight); - ffStrbufAppendF(&requestData.cacheDir, "%u*%u/", requestData.logoPixelWidth, requestData.logoPixelHeight); + // The cached payload is a rendering, not a bit-exact artefact: every backend produces a + // valid one for the same source and pixel size, so the backend is deliberately not part of + // the key. What the key does have to capture is the content of the source, which the path + // can not: the same file can be replaced in place. Hence the recorded mtime. + // 0 means the mtime could not be read, in which case the entry is never trusted. + const uint64_t sourceMtime = ffPathGetMtime(instance.config.logo.source.chars); - if (!instance.config.logo.recache) { + if (!instance.config.logo.recache && + sourceMtime != 0 && + readCachedUint64(&requestData, FF_CACHE_FILE_MTIME) == sourceMtime) { bool cacheValid = requestData.type == FF_LOGO_TYPE_IMAGE_CHAFA ? printCachedChars(&requestData, FF_CACHE_FILE_CHAFA) : printCachedPixel(&requestData); @@ -890,6 +917,10 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) { } } + // Cache miss. The entry directory is keyed on the source path and the pixel size only, so + // it is reused when the source is edited; drop what the previous version left behind. + removeCachedFiles(&requestData); + const char* error = nullptr; bool printSuccessful = false; @@ -916,6 +947,11 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) { } } + if (printSuccessful) { + // Written last: an entry only becomes usable once its payload is complete + writeCacheData(&requestData, &sourceMtime, sizeof(sourceMtime), FF_CACHE_FILE_MTIME); + } + ffStrbufDestroy(&requestData.cacheDir); if (printSuccessful) {