@@ -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-
418414static 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+
716746static 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