diff --git a/src/detection/packages/packages_linux.c b/src/detection/packages/packages_linux.c index 9f5b3c3074..e451a2a6dd 100644 --- a/src/detection/packages/packages_linux.c +++ b/src/detection/packages/packages_linux.c @@ -118,28 +118,152 @@ static uint32_t countFilesRecursive(FFstrbuf* baseDir, const char* dirname, cons return sum; } +static bool isValidNixPkg(FFstrbuf* pkg) +{ + if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY)) + return false; + + ffStrbufSubstrAfterLastC(pkg, '/'); + if ( + ffStrbufStartsWithS(pkg, "nixos-system-nixos-") || + ffStrbufEndsWithS(pkg, "-doc") || + ffStrbufEndsWithS(pkg, "-man") || + ffStrbufEndsWithS(pkg, "-info") || + ffStrbufEndsWithS(pkg, "-dev") || + ffStrbufEndsWithS(pkg, "-bin") + ) return false; + + enum { START, DIGIT, DOT, MATCH } state = START; + + for (uint32_t i = 0; i < pkg->length; i++) + { + char c = pkg->chars[i]; + switch (state) + { + case START: + if (c >= '0' && c <= '9') + state = DIGIT; + break; + case DIGIT: + if (c >= '0' && c <= '9') + continue; + if (c == '.') + state = DOT; + else + state = START; + break; + case DOT: + if (c >= '0' && c <= '9') + state = MATCH; + else + state = START; + break; + case MATCH: + break; + } + } + + return state == MATCH; +} + +static bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count) +{ + if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE)) + return false; + + FF_STRBUF_AUTO_DESTROY cacheContent; + ffStrbufInit(&cacheContent); + if (!ffReadFileBuffer(cacheDir->chars, &cacheContent)) + return false; + + // Format: \n + uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\n'); + if (split == cacheContent.length) + return false; + + ffStrbufSetNS(hash, split, cacheContent.chars); + *count = (uint32_t)atoi(cacheContent.chars + split + 1); + + return true; +} + +static bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count) +{ + FF_STRBUF_AUTO_DESTROY cacheContent; + ffStrbufInit(&cacheContent); + ffStrbufAppend(&cacheContent, hash); + ffStrbufAppendC(&cacheContent, '\n'); + ffStrbufAppendF(&cacheContent, "%u", count); + return ffWriteFileBuffer(cacheDir->chars, &cacheContent); +} + static uint32_t getNixPackagesImpl(char* path) { //Nix detection is kinda slow, so we only do it if the dir exists if(!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) return 0; - FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(128); + FF_STRBUF_AUTO_DESTROY cacheDir; + ffStrbufInit(&cacheDir); + ffStrbufAppend(&cacheDir, &instance.state.platform.cacheDir); + ffStrbufEnsureEndsWithC(&cacheDir, '/'); + ffStrbufAppendS(&cacheDir, "fastfetch/packages/nix"); + ffStrbufAppendS(&cacheDir, path); + + //Check the hash first to determine if we need to recompute the count + FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64); + FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64); + uint32_t count = 0; + + ffProcessAppendStdOut(&hash, (char* const[]) { + "nix-store", + "--query", + "--hash", + path, + NULL + }); + + if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash)) + return count; + //Cache is invalid, recompute the count + count = 0; + + //Implementation based on bash script from here: //https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222 - FF_STRBUF_AUTO_DESTROY command = ffStrbufCreateA(255); - ffStrbufAppendS(&command, "for x in $(nix-store --query --requisites "); - ffStrbufAppendS(&command, path); - ffStrbufAppendS(&command, "); do if [ -d $x ]; then echo $x ; fi ; done | cut -d- -f2- | egrep '([0-9]{1,}\\.)+[0-9]{1,}' | egrep -v '\\-doc$|\\-man$|\\-info$|\\-dev$|\\-bin$|^nixos-system-nixos-' | uniq | wc -l"); + + FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024); ffProcessAppendStdOut(&output, (char* const[]) { - "sh", - "-c", - command.chars, + "nix-store", + "--query", + "--requisites", + path, NULL }); - return (uint32_t) strtol(output.chars, NULL, 10); + uint32_t lineLength = 0; + for (uint32_t i = 0; i < output.length; i++) + { + if (output.chars[i] != '\n') + { + lineLength++; + continue; + } + + output.chars[i] = '\0'; + FFstrbuf line = { + .allocated = 0, + .length = lineLength, + .chars = output.chars + i - lineLength + }; + if (isValidNixPkg(&line)) + count++; + lineLength = 0; + } + + writeNixCache(&cacheDir, &hash, count); + return count; } static uint32_t getNixPackages(FFstrbuf* baseDir, const char* dirname)