1#include <stdbool.h>
2#include <stdint.h>
3#include <stdio.h>
4#include <stdlib.h>
5#include <string.h>
6
7#include <dirent.h>
8#include <sys/stat.h>
9#include <unistd.h>
10
11#include "config.h"
12#include "scan.h"
13#include "log.h"
14#include "util.h"
15
16static uint64_t encode_fnv1a64(const char* s);
17static int library_push(struct library* l, const char* rel);
18static int scan_dir(struct library* l, const char* root, const char* rel);
19
20/**
21 * @brief Encode string using FNV1a64
22 *
23 * @param s Input string
24 *
25 * @return 64bit hash value
26 */
27static uint64_t encode_fnv1a64(const char* s)
28{
29 /* standard fnv1a64 encoding */
30 uint64_t h = 1469598103934665603ULL;
31
32 for (; *s; s++) {
33 h ^= (unsigned char)(*s);
34 h *= 1099511628211ULL;
35 }
36
37 return h;
38}
39
40/**
41 * @brief Append item path to library
42 *
43 * @param l Output library
44 * @param rel Relative item path
45 *
46 * @return 0=Success, -1=Failure
47 */
48static int library_push(struct library* l, const char* rel)
49{
50 if (l->len == l->cap) {
51 size_t ncap = l->cap ? (l->cap * 2) : 64;
52 struct item* ni = realloc(l->items, ncap * sizeof(*ni));
53 if (!ni) {
54 LOG(verbose_log, "SCAN", "realloc FAILED OOM");
55 return -1;
56 }
57 l->items = ni;
58 l->cap = ncap;
59 }
60
61 char* p = strdup(rel);
62 if (!p)
63 return -1;
64
65 l->items[l->len].id = encode_fnv1a64(rel);
66 l->items[l->len].path = p;
67 l->len++;
68
69 return 0;
70}
71
72/**
73 * @brief Recursively scan one directory subtree
74 *
75 * @param l Output library
76 * @param root Media root directory
77 * @param rel Relative path within root
78 *
79 * @return 0=Success, -1=Failure
80 */
81static int scan_dir(struct library* l, const char* root, const char* rel)
82{
83 char full[4096];
84 DIR* d;
85 struct dirent* e;
86
87 if (rel[0] == '\0') {
88 if (snprintf(full, sizeof(full), "%s", root) >= (int)sizeof(full))
89 return -1;
90 }
91 else {
92 if (join_path(full, sizeof(full), root, rel) < 0)
93 return -1;
94 }
95
96 d = opendir(full);
97 if (!d) {
98 LOG(verbose_log, "SCAN", "opendir FAILED %s", full);
99 return -1;
100 }
101
102 while ((e = readdir(d)) != NULL) {
103 if (strcmp(e->d_name, ".") == 0)
104 continue;
105 if (strcmp(e->d_name, "..") == 0)
106 continue;
107
108 char rel2[4096];
109 char full2[4096];
110
111 if (rel[0] == '\0') {
112 if (snprintf(rel2, sizeof(rel2), "%s", e->d_name) >= (int)sizeof(rel2))
113 goto fail;
114 }
115 else {
116 if (join_path(rel2, sizeof(rel2), rel, e->d_name) < 0)
117 goto fail;
118 }
119
120 if (join_path(full2, sizeof(full2), root, rel2) < 0)
121 goto fail;
122
123 struct stat st;
124 if (lstat(full2, &st) < 0)
125 continue;
126
127 if (S_ISDIR(st.st_mode)) {
128 if (scan_dir(l, root, rel2) < 0)
129 goto fail;
130 continue;
131 }
132
133 if (S_ISREG(st.st_mode)) {
134 if (library_push(l, rel2) < 0)
135 goto fail;
136 continue;
137 }
138 }
139
140 closedir(d);
141 return 0;
142
143fail:
144 closedir(d);
145 return -1;
146}
147
148int scan_library(struct library* l, const char* root)
149{
150 memset(l, 0, sizeof(*l));
151 return scan_dir(l, root, "");
152}
153
154void scan_library_free(struct library* l)
155{
156 if (!l)
157 return;
158
159 for (size_t i = 0; i < l->len; i++)
160 free(l->items[i].path);
161
162 free(l->items);
163
164 l->items = NULL;
165 l->len = 0;
166 l->cap = 0;
167}
168
169int scan_library_rescan(struct library* l, const char* root)
170{
171 struct library new;
172 struct library old;
173
174 if (!l || !root)
175 return -1;
176
177 memset(&new, 0, sizeof(new));
178
179 if (scan_library(&new, root) < 0) {
180 scan_library_free(&new);
181 return -1;
182 }
183
184 /* swap */
185 old = *l;
186 *l = new;
187
188 /* free old */
189 scan_library_free(&old);
190
191 return 0;
192}
193