ohmu
·
2026-10-15
1#include "packages.h"
2#include "common/io.h"
3#include "common/processing.h"
4#include "common/strutil.h"
5
6static bool isValidNixPkg(FFstrbuf* pkg) {
7 if (!ffPathExists(pkg->chars, FF_PATHTYPE_DIRECTORY)) {
8 return false;
9 }
10
11 ffStrbufSubstrAfterLastC(pkg, '/');
12 if (
13 ffStrbufStartsWithS(pkg, "nixos-system-nixos-") ||
14 ffStrbufEndsWithS(pkg, "-doc") ||
15 ffStrbufEndsWithS(pkg, "-man") ||
16 ffStrbufEndsWithS(pkg, "-info") ||
17 ffStrbufEndsWithS(pkg, "-dev") ||
18 ffStrbufEndsWithS(pkg, "-bin")) {
19 return false;
20 }
21
22 enum { START,
23 DIGIT,
24 DOT,
25 MATCH } state = START;
26
27 for (uint32_t i = 0; i < pkg->length; i++) {
28 char c = pkg->chars[i];
29 switch (state) {
30 case START:
31 if (ffCharIsDigit(c)) {
32 state = DIGIT;
33 }
34 break;
35 case DIGIT:
36 if (ffCharIsDigit(c)) {
37 continue;
38 }
39 if (c == '.') {
40 state = DOT;
41 } else {
42 state = START;
43 }
44 break;
45 case DOT:
46 if (ffCharIsDigit(c)) {
47 state = MATCH;
48 } else {
49 state = START;
50 }
51 break;
52 case MATCH:
53 break;
54 }
55 }
56
57 return state == MATCH;
58}
59
60static bool checkNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t* count) {
61 if (!ffPathExists(cacheDir->chars, FF_PATHTYPE_FILE)) {
62 return false;
63 }
64
65 FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
66 if (!ffReadFileBuffer(cacheDir->chars, &cacheContent)) {
67 return false;
68 }
69
70 // Format: <hash>\n<count>
71 uint32_t split = ffStrbufFirstIndexC(&cacheContent, '\n');
72 if (split == cacheContent.length) {
73 return false;
74 }
75
76 ffStrbufSetNS(hash, split, cacheContent.chars);
77 *count = (uint32_t) atoi(cacheContent.chars + split + 1);
78
79 return true;
80}
81
82static bool writeNixCache(FFstrbuf* cacheDir, FFstrbuf* hash, uint32_t count) {
83 FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateCopy(hash);
84 ffStrbufAppendF(&cacheContent, "\n%u", count);
85 return ffWriteFileBuffer(cacheDir->chars, &cacheContent);
86}
87
88static uint32_t getNixPackagesImpl(char* path) {
89 // Nix detection is kinda slow, so we only do it if the dir exists
90 if (!ffPathExists(path, FF_PATHTYPE_DIRECTORY)) {
91 return 0;
92 }
93
94 FF_STRBUF_AUTO_DESTROY cacheDir = ffStrbufCreateCopy(&instance.state.platform.cacheDir);
95 ffStrbufEnsureEndsWithC(&cacheDir, '/');
96 ffStrbufAppendS(&cacheDir, "fastfetch/packages/nix");
97 ffStrbufAppendS(&cacheDir, path);
98
99 // Check the hash first to determine if we need to recompute the count
100 FF_STRBUF_AUTO_DESTROY hash = ffStrbufCreateA(64);
101 FF_STRBUF_AUTO_DESTROY cacheHash = ffStrbufCreateA(64);
102 uint32_t count = 0;
103
104 ffProcessAppendStdOut(&hash, (char* const[]) { "nix-store", "--query", "--hash", path, nullptr });
105
106 if (checkNixCache(&cacheDir, &cacheHash, &count) && ffStrbufEqual(&hash, &cacheHash)) {
107 return count;
108 }
109
110 // Cache is invalid, recompute the count
111 count = 0;
112
113 // Implementation based on bash script from here:
114 // https://github.com/fastfetch-cli/fastfetch/issues/195#issuecomment-1191748222
115
116 FF_STRBUF_AUTO_DESTROY output = ffStrbufCreateA(1024);
117
118 ffProcessAppendStdOut(&output, (char* const[]) { "nix-store", "--query", "--requisites", path, nullptr });
119
120 uint32_t lineLength = 0;
121 for (uint32_t i = 0; i < output.length; i++) {
122 if (output.chars[i] != '\n') {
123 lineLength++;
124 continue;
125 }
126
127 output.chars[i] = '\0';
128 FFstrbuf line = {
129 .allocated = 0,
130 .length = lineLength,
131 .chars = output.chars + i - lineLength
132 };
133 if (isValidNixPkg(&line)) {
134 count++;
135 }
136 lineLength = 0;
137 }
138
139 writeNixCache(&cacheDir, &hash, count);
140 return count;
141}
142
143uint32_t ffPackagesGetNix(FFstrbuf* baseDir, const char* dirname) {
144 uint32_t baseDirLength = baseDir->length;
145 ffStrbufAppendS(baseDir, dirname);
146 uint32_t num_elements = getNixPackagesImpl(baseDir->chars);
147 ffStrbufSubstrBefore(baseDir, baseDirLength);
148 return num_elements;
149}