HuntnGather – Blame information for rev 22
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /////////////////////////////////////////////////////////////////////////// |
2 | // Copyright (C) 2021 Wizardry and Steamworks - License: MIT // |
||
3 | /////////////////////////////////////////////////////////////////////////// |
||
4 | |||
5 | #include <stdio.h> |
||
6 | #include <stdlib.h> |
||
7 | #include <string.h> |
||
8 | #include <dirent.h> |
||
9 | #include <signal.h> |
||
10 | |||
11 | #include <sys/types.h> |
||
12 | #include <sys/stat.h> |
||
19 | office | 13 | #include <sys/syslimits.h> |
1 | office | 14 | |
15 | #include <proto/dos.h> |
||
16 | #include <proto/exec.h> |
||
17 | |||
22 | office | 18 | #if defined ___AsyncIO___ |
19 | #include <asyncio.h> |
||
20 | #endif |
||
21 | |||
1 | office | 22 | #include "StringStack.h" |
23 | |||
5 | office | 24 | #if !defined ___HAVE_GETOPT___ |
1 | office | 25 | #include "getopt.h" |
26 | #endif |
||
27 | |||
22 | office | 28 | #define PROGRAM_VERSION "1.7.3" |
19 | office | 29 | |
5 | office | 30 | #if defined ___AmigaOS___ |
31 | /*************************************************************************/ |
||
32 | /* Version string used for querrying the program version. */ |
||
33 | /*************************************************************************/ |
||
34 | TEXT version_string[] = |
||
19 | office | 35 | "\0$VER: Gather " PROGRAM_VERSION " "__DATE__" by Wizardry and Steamworks"; |
5 | office | 36 | #endif |
1 | office | 37 | |
38 | #if !defined TRUE |
||
39 | #define TRUE 1; |
||
40 | #endif |
||
41 | |||
42 | #if !defined FALSE |
||
43 | #define FALSE 0; |
||
44 | #endif |
||
45 | |||
22 | office | 46 | #define ASYNC_BUF 8192 |
2 | office | 47 | #define MAX_MEM 262144 |
11 | office | 48 | #define NAME_BUF 32 |
49 | #define PATH_BUF 128 |
||
50 | #define LINE_BUF 256 |
||
2 | office | 51 | #define DEFAULT_DATABASE_FILE "S:gather.db" |
1 | office | 52 | |
53 | typedef struct { |
||
54 | unsigned int dirs; |
||
55 | unsigned int files; |
||
56 | } stats; |
||
57 | |||
58 | int run = TRUE; |
||
59 | int verbose = TRUE; |
||
60 | |||
61 | void SignalHandler(int sig) { |
||
62 | // Toggle the run flag to stop execution. |
||
63 | run = FALSE; |
||
64 | } |
||
65 | |||
66 | int compare(const void *a, const void *b) { |
||
67 | const char **p = (const char **)a; |
||
68 | const char **q = (const char **)b; |
||
13 | office | 69 | return strncmp(*p, *q, strlen(*p)); |
1 | office | 70 | } |
71 | |||
72 | /* |
||
73 | * |
||
74 | * Sorts a database file lexicographically. |
||
75 | */ |
||
76 | void SortDatabase(char *dbFile) { |
||
22 | office | 77 | #if defined ___AsyncIO___ |
78 | struct AsyncFile *fp; |
||
79 | LONG c; |
||
80 | #else |
||
1 | office | 81 | FILE *fp; |
22 | office | 82 | char c; |
83 | #endif |
||
1 | office | 84 | char *name = NULL; |
85 | char *path = NULL; |
||
86 | char **database; |
||
87 | int i; |
||
88 | int side; |
||
89 | unsigned int line; |
||
11 | office | 90 | int name_size; |
91 | int path_size; |
||
1 | office | 92 | |
93 | // Open database file for reading. |
||
22 | office | 94 | #if defined ___AsyncIO___ |
95 | if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) { |
||
96 | #else |
||
1 | office | 97 | if((fp = fopen(dbFile, "r")) == NULL) { |
22 | office | 98 | #endif |
1 | office | 99 | fprintf(stderr, "Unable to open gather database for reading.\n"); |
100 | return; |
||
101 | } |
||
102 | |||
19 | office | 103 | database = malloc(sizeof(*database)); |
11 | office | 104 | name_size = NAME_BUF; |
19 | office | 105 | name = malloc(name_size * sizeof(*name)); |
11 | office | 106 | path_size = PATH_BUF; |
19 | office | 107 | path = malloc(path_size * sizeof(*path)); |
22 | office | 108 | |
1 | office | 109 | line = 0; |
110 | side = 0; |
||
111 | i = 0; |
||
112 | |||
113 | if(verbose) { |
||
114 | fprintf(stdout, "Sorting database: '%s'\n", dbFile); |
||
115 | } |
||
22 | office | 116 | #if defined ___AsyncIO___ |
117 | while(run && (c = ReadCharAsync(fp)) != -1) { |
||
118 | #else |
||
1 | office | 119 | while(run && fscanf(fp, "%c", &c) == 1) { |
22 | office | 120 | #endif |
1 | office | 121 | #if defined ___AmigaOS___ |
122 | // Check if CTRL+C was pressed and abort the program. |
||
123 | if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) { |
||
124 | run = FALSE; |
||
125 | continue; |
||
126 | } |
||
127 | #endif |
||
128 | switch(c) { |
||
129 | case '\n': |
||
130 | // Load up the name and path into the database variable. |
||
19 | office | 131 | database = realloc(database, (line + 1) * sizeof(*database)); |
132 | database[line] = malloc((strlen(name) + strlen(path) + 1 + 1) * sizeof(*database[line])); |
||
1 | office | 133 | sprintf(database[line], "%s\t%s", name, path); |
134 | ++line; |
||
135 | |||
136 | free(name); |
||
11 | office | 137 | name_size = NAME_BUF; |
19 | office | 138 | name = malloc(name_size * sizeof(*name)); |
1 | office | 139 | --side; |
140 | i = 0; |
||
141 | |||
142 | break; |
||
143 | case '\t': |
||
144 | free(path); |
||
11 | office | 145 | path_size = PATH_BUF; |
19 | office | 146 | path = malloc(path_size * sizeof(*path)); |
1 | office | 147 | ++side; |
148 | i = 0; |
||
149 | break; |
||
150 | default: |
||
151 | switch(side) { |
||
152 | case 0: |
||
11 | office | 153 | if(strlen(name) == name_size) { |
154 | name_size = name_size * 1.5; |
||
19 | office | 155 | name = realloc(name, name_size * sizeof(*name)); |
11 | office | 156 | } |
157 | //name = realloc(name, (i + 1 + 1) * sizeof(char)); |
||
1 | office | 158 | name[i] = c; |
159 | name[i + 1] = '\0'; |
||
160 | break; |
||
161 | case 1: |
||
11 | office | 162 | if(strlen(path) == path_size) { |
163 | path_size = path_size * 1.5; |
||
19 | office | 164 | path = realloc(path, path_size * sizeof(*path)); |
11 | office | 165 | } |
166 | //path = realloc(path, (i + 1 + 1) * sizeof(char)); |
||
1 | office | 167 | path[i] = c; |
168 | path[i + 1] = '\0'; |
||
169 | break; |
||
170 | default: |
||
22 | office | 171 | fprintf(stderr, "Database corrupted: %d\n", side); |
1 | office | 172 | break; |
173 | } |
||
174 | ++i; |
||
175 | break; |
||
176 | } |
||
177 | } |
||
178 | |||
22 | office | 179 | #if defined ___AsyncIO___ |
180 | CloseAsync(fp); |
||
181 | #else |
||
1 | office | 182 | fclose(fp); |
22 | office | 183 | #endif |
1 | office | 184 | |
185 | // Sort the database. |
||
186 | qsort(database, line, sizeof(char *), compare); |
||
187 | |||
188 | // Write the database lines back to the database. |
||
22 | office | 189 | #if defined ___AsyncIO___ |
190 | if((fp = OpenAsync(dbFile, MODE_READ|MODE_WRITE, ASYNC_BUF)) == NULL) { |
||
191 | #else |
||
1 | office | 192 | if((fp = fopen(dbFile, "w+")) == NULL) { |
22 | office | 193 | #endif |
1 | office | 194 | fprintf(stderr, "Unable to open gather database for writing.\n"); |
195 | return; |
||
196 | } |
||
197 | |||
198 | for(i = 0; i < line; ++i) { |
||
22 | office | 199 | #if defined ___AsyncIO___ |
200 | WriteAsync(fp, database[i], (LONG)(strlen(database[i]) * sizeof(char))); |
||
201 | WriteAsync(fp, "\n", 1 * sizeof(char)); |
||
202 | #else |
||
1 | office | 203 | fprintf(fp, "%s\n", database[i]); |
22 | office | 204 | #endif |
1 | office | 205 | } |
206 | |||
22 | office | 207 | #if defined ___AsyncIO___ |
208 | CloseAsync(fp); |
||
209 | #else |
||
210 | fclose(fp); |
||
211 | #endif |
||
212 | |||
1 | office | 213 | free(database); |
214 | } |
||
215 | |||
216 | /* |
||
217 | * |
||
218 | * Updates a database file "dbFile". |
||
219 | */ |
||
220 | void UpdateDatabase(char *dbFile, stringStack *dirStack, stats *stats) { |
||
22 | office | 221 | #if defined ___AsyncIO___ |
222 | struct AsyncFile *fp; |
||
223 | #else |
||
1 | office | 224 | FILE *fp; |
22 | office | 225 | #endif |
1 | office | 226 | DIR *dir; |
227 | struct dirent *dirEntry; |
||
228 | struct stat dirStat; |
||
229 | unsigned int size; |
||
230 | char *path; |
||
231 | char *subPath; |
||
232 | |||
22 | office | 233 | #if defined ___AsyncIO___ |
234 | if((fp = OpenAsync(dbFile, MODE_READ|MODE_WRITE, ASYNC_BUF)) == NULL) { |
||
235 | #else |
||
1 | office | 236 | if((fp = fopen(dbFile, "w+")) == NULL) { |
22 | office | 237 | #endif |
1 | office | 238 | fprintf(stderr, "Unable to open gather database for writing.\n"); |
239 | return; |
||
240 | } |
||
241 | |||
242 | while(run && !stringStackIsEmpty(dirStack)) { |
||
243 | #if defined ___AmigaOS___ |
||
244 | // Check if CTRL+C was pressed and abort the program. |
||
245 | if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) { |
||
246 | run = FALSE; |
||
247 | } |
||
248 | #endif |
||
19 | office | 249 | if((path = stringStackPop(dirStack)) == NULL) { |
1 | office | 250 | return; |
251 | } |
||
252 | |||
253 | if((dir = opendir(path)) == NULL) { |
||
254 | return; |
||
255 | } |
||
256 | |||
257 | while(run && (dirEntry = readdir(dir)) != NULL) { |
||
258 | #if defined ___AmigaOS___ |
||
259 | // Check if CTRL+C was pressed and abort the program. |
||
260 | if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) { |
||
261 | run = FALSE; |
||
262 | } |
||
263 | #endif |
||
264 | size = sizeof(path) + sizeof(dirEntry->d_name) + 1; |
||
265 | switch(path[strlen(path) - 1]) { |
||
266 | case '/': |
||
267 | case ':': // This is a drive path. |
||
11 | office | 268 | subPath = malloc(size); |
1 | office | 269 | sprintf(subPath, "%s%s", path, dirEntry->d_name); |
270 | break; |
||
271 | default: |
||
11 | office | 272 | subPath = malloc(size + 1); |
1 | office | 273 | sprintf(subPath, "%s/%s", path, dirEntry->d_name); |
274 | break; |
||
275 | } |
||
276 | stat(subPath, &dirStat); |
||
277 | if(S_ISDIR(dirStat.st_mode)) { |
||
278 | stringStackPush(dirStack, subPath); |
||
279 | |||
280 | ++stats->dirs; |
||
281 | |||
282 | if(verbose) { |
||
283 | fprintf(stdout, |
||
284 | "Gathered %d directories and %d files.\r", |
||
285 | stats->dirs, |
||
286 | stats->files); |
||
287 | } |
||
288 | |||
289 | free(subPath); |
||
290 | continue; |
||
291 | } |
||
292 | |||
293 | // Write to database file. |
||
15 | office | 294 | |
295 | #if defined ___NOCASE_FS___ |
||
296 | strupr(dirEntry->d_name); |
||
297 | #endif |
||
298 | |||
22 | office | 299 | #if defined ___AsyncIO___ |
300 | WriteAsync(fp, dirEntry->d_name, (LONG)(strlen(dirEntry->d_name) * sizeof(char))); |
||
301 | WriteAsync(fp, "\t", 1 * sizeof(char)); |
||
302 | WriteAsync(fp, subPath, (LONG)(strlen(subPath) * sizeof(char))); |
||
303 | WriteAsync(fp, "\n", 1 * sizeof(char)); |
||
304 | #else |
||
1 | office | 305 | fprintf(fp, "%s\t%s\n", dirEntry->d_name, subPath); |
22 | office | 306 | #endif |
1 | office | 307 | ++stats->files; |
308 | |||
309 | if(verbose) { |
||
310 | fprintf(stdout, |
||
311 | "Gathered %d directories and %d files.\r", |
||
312 | stats->dirs, |
||
313 | stats->files); |
||
314 | } |
||
315 | |||
316 | free(subPath); |
||
317 | } |
||
318 | |||
319 | closedir(dir); |
||
320 | free(path); |
||
321 | } |
||
322 | |||
323 | if(verbose) { |
||
324 | fprintf(stdout, "\n"); |
||
325 | } |
||
326 | |||
22 | office | 327 | #if defined ___AsyncIO___ |
328 | CloseAsync(fp); |
||
329 | #else |
||
1 | office | 330 | fclose(fp); |
22 | office | 331 | #endif |
1 | office | 332 | |
333 | } |
||
334 | |||
335 | /* |
||
336 | * |
||
337 | * Gets the size of a database "dbFle". |
||
338 | */ |
||
339 | int GetDatabaseSize(char *dbFile) { |
||
340 | FILE *fp; |
||
341 | int size; |
||
342 | |||
343 | if((fp = fopen(dbFile, "r")) == NULL) { |
||
344 | fprintf(stderr, "Unable to open gather database for reading.\n"); |
||
19 | office | 345 | fclose(fp); |
1 | office | 346 | return 0; |
347 | } |
||
348 | |||
349 | fseek(fp, 0L, SEEK_END); |
||
350 | size = ftell(fp); |
||
351 | |||
352 | fclose(fp); |
||
353 | return size; |
||
354 | } |
||
355 | |||
356 | /* |
||
357 | * |
||
358 | * Counts the lines in a database file "dbFile". |
||
359 | */ |
||
360 | int CountDatabaseLines(char *dbFile) { |
||
22 | office | 361 | #if defined ___AsyncIO___ |
362 | struct AsyncFile *fp; |
||
363 | LONG c; |
||
364 | #else |
||
1 | office | 365 | FILE *fp; |
22 | office | 366 | char c; |
367 | #endif |
||
1 | office | 368 | int lines; |
369 | |||
22 | office | 370 | #if defined ___AsyncIO___ |
371 | if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) { |
||
372 | #else |
||
1 | office | 373 | if((fp = fopen(dbFile, "r")) == NULL) { |
22 | office | 374 | #endif |
1 | office | 375 | fprintf(stderr, "Unable to open gather database for reading.\n"); |
376 | return 0; |
||
377 | } |
||
378 | |||
379 | lines = 0; |
||
22 | office | 380 | #if defined ___AsyncIO___ |
381 | while((c = ReadCharAsync(fp)) != -1) { |
||
382 | #else |
||
1 | office | 383 | while(fscanf(fp, "%c", &c) == 1) { |
22 | office | 384 | #endif |
1 | office | 385 | switch(c) { |
386 | case '\n': |
||
387 | ++lines; |
||
388 | break; |
||
389 | } |
||
390 | } |
||
391 | |||
22 | office | 392 | #if defined ___AsyncIO___ |
393 | CloseAsync(fp); |
||
394 | #else |
||
1 | office | 395 | fclose(fp); |
22 | office | 396 | #endif |
1 | office | 397 | |
398 | return lines; |
||
399 | } |
||
400 | |||
401 | /* |
||
402 | * |
||
403 | * Creates "files" temporary filenames. |
||
404 | */ |
||
405 | char **CreateTempFiles(int files) { |
||
406 | char **tmpNames; |
||
407 | int count; |
||
408 | |||
11 | office | 409 | tmpNames = malloc(files * sizeof(char *)); |
1 | office | 410 | |
411 | if(verbose) { |
||
412 | fprintf(stdout, "Creating temporary files.\r"); |
||
413 | } |
||
414 | |||
415 | count = files; |
||
416 | while(--count > -1) { |
||
417 | #if defined ___AmigaOS___ |
||
418 | // Check if CTRL+C was pressed and abort the program. |
||
419 | if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) { |
||
420 | run = FALSE; |
||
421 | } |
||
422 | #endif |
||
423 | tmpNames[count] = tmpnam(NULL); |
||
424 | |||
425 | if(verbose) { |
||
426 | fprintf(stdout, "Creating temporary files: %d%%\r", 100 - (int)(((float)count / files) * 100.0)); |
||
427 | } |
||
428 | } |
||
429 | |||
430 | if(verbose) { |
||
431 | fprintf(stdout, "\n"); |
||
432 | } |
||
433 | |||
434 | return tmpNames; |
||
435 | } |
||
436 | |||
437 | /* |
||
438 | * |
||
439 | * Writes lines from the database "dbFile" to temporary filenames "tmpNames". |
||
440 | */ |
||
441 | void WriteTempFiles(char *dbFile, char **tmpNames, int tmpFiles, int tmpLines, int total) { |
||
22 | office | 442 | #if defined ___AsyncIO___ |
443 | struct AsyncFile *fp, *tp; |
||
444 | LONG c; |
||
445 | #else |
||
1 | office | 446 | FILE *fp, *tp; |
447 | char c; |
||
22 | office | 448 | #endif |
1 | office | 449 | int lines; |
450 | int linesWritten; |
||
451 | |||
22 | office | 452 | #if defined ___AsyncIO___ |
453 | if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) { |
||
454 | #else |
||
1 | office | 455 | if((fp = fopen(dbFile, "r")) == NULL) { |
22 | office | 456 | #endif |
1 | office | 457 | fprintf(stderr, "Unable to open gather database for reading.\n"); |
458 | return; |
||
459 | } |
||
460 | |||
22 | office | 461 | #if defined ___AsyncIO___ |
462 | if((tp = OpenAsync(tmpNames[--tmpFiles], MODE_READ|MODE_WRITE, ASYNC_BUF)) == NULL) { |
||
463 | #else |
||
1 | office | 464 | if((tp = fopen(tmpNames[--tmpFiles], "w+")) == NULL) { |
22 | office | 465 | #endif |
1 | office | 466 | fprintf(stderr, "Unable to open temporary file '%s' for writing.\n", tmpNames[tmpFiles]); |
467 | return; |
||
468 | } |
||
469 | |||
470 | if(verbose) { |
||
471 | fprintf(stdout, "Writing to temporary files.\r"); |
||
472 | } |
||
473 | |||
474 | linesWritten = 0; |
||
475 | lines = 0; |
||
22 | office | 476 | #if defined ___AsyncIO___ |
477 | while(run && (c = ReadCharAsync(fp)) != -1) { |
||
478 | #else |
||
1 | office | 479 | while(run && fscanf(fp, "%c", &c) == 1) { |
22 | office | 480 | #endif |
1 | office | 481 | #if defined ___AmigaOS___ |
482 | // Check if CTRL+C was pressed and abort the program. |
||
483 | if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) { |
||
484 | run = FALSE; |
||
485 | } |
||
486 | #endif |
||
487 | switch(c) { |
||
488 | case '\n': |
||
489 | // Increment the total written lines. |
||
490 | ++linesWritten; |
||
491 | |||
492 | if(verbose) { |
||
493 | fprintf(stdout, "Writing to temporary files: %d%%.\r", (int)(((float)linesWritten / total) * 100.0)); |
||
494 | } |
||
495 | |||
496 | // Write the newline character back. |
||
22 | office | 497 | #if defined ___AsyncIO___ |
498 | if(WriteCharAsync(tp, (UBYTE)c) != 1) { |
||
499 | #else |
||
1 | office | 500 | if(fprintf(tp, "%c", c) != 1) { |
22 | office | 501 | #endif |
1 | office | 502 | fprintf(stderr, "Unable to write to temporary file '%s'.\n", tmpNames[tmpFiles]); |
22 | office | 503 | #if defined ___AsyncIO___ |
504 | CloseAsync(tp); |
||
505 | CloseAsync(fp); |
||
506 | #else |
||
19 | office | 507 | fclose(tp); |
1 | office | 508 | fclose(fp); |
22 | office | 509 | #endif |
1 | office | 510 | return; |
511 | } |
||
512 | // Switch to the next temporary file. |
||
513 | if(++lines >= tmpLines) { |
||
514 | // If there are no temporary files left then run till the end. |
||
515 | if(tmpFiles - 1 < 0) { |
||
516 | break; |
||
517 | } |
||
518 | |||
519 | // Close the previous temporary file and write to the next temporary file. |
||
22 | office | 520 | #if defined ___AsyncIO___ |
521 | CloseAsync(tp); |
||
522 | if((tp = OpenAsync(tmpNames[--tmpFiles], MODE_READ|MODE_WRITE, ASYNC_BUF)) == NULL) { |
||
523 | #else |
||
1 | office | 524 | fclose(tp); |
525 | if((tp = fopen(tmpNames[--tmpFiles], "w+")) == NULL) { |
||
22 | office | 526 | #endif |
1 | office | 527 | fprintf(stderr, "Unable to open temporary file '%s' for writing.\n", tmpNames[tmpFiles]); |
22 | office | 528 | #if defined ___AsyncIO___ |
529 | CloseAsync(tp); |
||
530 | CloseAsync(fp); |
||
531 | #else |
||
19 | office | 532 | fclose(tp); |
1 | office | 533 | fclose(fp); |
22 | office | 534 | #endif |
1 | office | 535 | } |
536 | lines = 0; |
||
537 | break; |
||
538 | } |
||
539 | break; |
||
540 | default: |
||
22 | office | 541 | #if defined ___AsyncIO___ |
542 | if(WriteCharAsync(tp, (UBYTE)c) != 1) { |
||
543 | #else |
||
1 | office | 544 | if(fprintf(tp, "%c", c) != 1) { |
22 | office | 545 | #endif |
1 | office | 546 | fprintf(stderr, "Unable to write to temporary file '%s'.\n", tmpNames[tmpFiles]); |
22 | office | 547 | #if defined ___AsyncIO___ |
548 | CloseAsync(tp); |
||
549 | CloseAsync(fp); |
||
550 | #else |
||
1 | office | 551 | fclose(tp); |
552 | fclose(fp); |
||
22 | office | 553 | #endif |
1 | office | 554 | return; |
555 | } |
||
556 | break; |
||
557 | } |
||
558 | } |
||
559 | |||
560 | fprintf(stdout, "\n"); |
||
561 | |||
22 | office | 562 | #if defined ___AsyncIO___ |
563 | CloseAsync(tp); |
||
564 | CloseAsync(fp); |
||
565 | #else |
||
1 | office | 566 | fclose(tp); |
567 | fclose(fp); |
||
22 | office | 568 | #endif |
1 | office | 569 | } |
570 | |||
571 | /* |
||
572 | * |
||
573 | * Skips a line in a database file "fp". |
||
574 | */ |
||
22 | office | 575 | |
576 | #if defined ___AsyncIO___ |
||
577 | void SkipDatabaseLine(struct AsyncFile *fp) { |
||
578 | LONG c; |
||
579 | while((c = ReadCharAsync(fp)) != -1) { |
||
580 | #else |
||
1 | office | 581 | void SkipDatabaseLine(FILE *fp) { |
582 | char c; |
||
583 | while(fscanf(fp, "%c", &c) == 1) { |
||
22 | office | 584 | #endif |
585 | switch(c) { |
||
586 | case '\n': |
||
587 | return; |
||
1 | office | 588 | } |
589 | } |
||
590 | } |
||
591 | |||
592 | /* |
||
593 | * |
||
594 | * Reads a line from the database file "fp". |
||
595 | */ |
||
22 | office | 596 | #if defined ___AsyncIO___ |
597 | char *ReadDatabaseLine(struct AsyncFile *fp) { |
||
598 | LONG c; |
||
599 | #else |
||
1 | office | 600 | char *ReadDatabaseLine(FILE *fp) { |
601 | char c; |
||
22 | office | 602 | #endif |
1 | office | 603 | char *line; |
11 | office | 604 | int line_size; |
605 | int i; |
||
1 | office | 606 | |
11 | office | 607 | line_size = LINE_BUF; |
19 | office | 608 | line = malloc(line_size * sizeof(*line)); |
1 | office | 609 | |
11 | office | 610 | i = 0; |
22 | office | 611 | #if defined ___AsyncIO___ |
612 | while(run && (c = ReadCharAsync(fp)) != -1) { |
||
613 | #else |
||
1 | office | 614 | while(run && fscanf(fp, "%c", &c) == 1) { |
22 | office | 615 | #endif |
1 | office | 616 | #if defined ___AmigaOS___ |
617 | // Check if CTRL+C was pressed and abort the program. |
||
618 | if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) { |
||
619 | run = FALSE; |
||
620 | } |
||
621 | #endif |
||
622 | switch(c) { |
||
623 | case '\n': |
||
624 | // Rewind the file by the number of read characters. |
||
22 | office | 625 | #if defined ___AsyncIO___ |
626 | if(SeekAsync(fp, -(i + 1), MODE_CURRENT) == -1) { |
||
627 | fprintf(stderr, "Could not seek in file.\n"); |
||
628 | return NULL; |
||
629 | } |
||
630 | #else |
||
11 | office | 631 | fseek(fp, -(i + 1), SEEK_CUR); |
22 | office | 632 | #endif |
1 | office | 633 | return line; |
634 | default: |
||
11 | office | 635 | if(strlen(line) == line_size) { |
636 | line_size = line_size * 1.5; |
||
19 | office | 637 | line = realloc(line, line_size * sizeof(*line)); |
11 | office | 638 | } |
639 | //line = realloc(line, (chars + 1 + 1) * sizeof(char)); |
||
640 | line[i] = c; |
||
641 | line[i + 1] = '\0'; |
||
1 | office | 642 | break; |
643 | } |
||
11 | office | 644 | ++i; |
1 | office | 645 | } |
646 | |||
647 | return NULL; |
||
648 | } |
||
649 | |||
650 | /* |
||
651 | * |
||
652 | * Merges temporary files "tmpNames" into a database "dbFile". |
||
653 | */ |
||
654 | void MergeDatabase(char *dbFile, char **tmpNames, int files, int lines) { |
||
22 | office | 655 | #if defined ___AsyncIO___ |
656 | struct AsyncFile *fp; |
||
657 | struct AsyncFile **tp; |
||
658 | #else |
||
1 | office | 659 | FILE *fp; |
660 | FILE **tp; |
||
22 | office | 661 | #endif |
1 | office | 662 | int i; |
14 | office | 663 | int j; |
1 | office | 664 | char *tmp; |
14 | office | 665 | char *min; |
1 | office | 666 | int count; |
667 | |||
22 | office | 668 | #if defined ___AsyncIO___ |
669 | if((fp = OpenAsync(dbFile, MODE_READ|MODE_WRITE, ASYNC_BUF)) == NULL) { |
||
670 | #else |
||
1 | office | 671 | if((fp = fopen(dbFile, "w+")) == NULL) { |
22 | office | 672 | #endif |
1 | office | 673 | fprintf(stderr, "Unable to open gather database for writing.\n"); |
674 | return; |
||
675 | } |
||
676 | |||
677 | // Allocate as many file pointers as temporary files. |
||
19 | office | 678 | tp = malloc(files * sizeof(*tp)); |
1 | office | 679 | |
680 | // Open all temporary files for reading. |
||
681 | for(i = 0; i < files; ++i) { |
||
22 | office | 682 | #if defined ___AsyncIO___ |
683 | if((tp[i] = OpenAsync(tmpNames[i], MODE_READ, ASYNC_BUF)) == NULL) { |
||
684 | #else |
||
1 | office | 685 | if((tp[i] = fopen(tmpNames[i], "r")) == NULL) { |
22 | office | 686 | #endif |
1 | office | 687 | fprintf(stderr, "Unable to open temporary file '%s' for reading.\n", tmpNames[i]); |
688 | // Close all temporary files. |
||
689 | --i; |
||
690 | while(i >= 0) { |
||
22 | office | 691 | #if defined ___AsyncIO___ |
692 | CloseAsync(tp[i]); |
||
693 | #else |
||
1 | office | 694 | fclose(tp[i]); |
22 | office | 695 | #endif |
1 | office | 696 | } |
697 | return; |
||
698 | } |
||
699 | } |
||
700 | |||
701 | if(verbose) { |
||
702 | fprintf(stdout, "Merging all database lines in temporary files.\r"); |
||
703 | } |
||
704 | |||
705 | count = lines; |
||
14 | office | 706 | j = 0; |
1 | office | 707 | while(run && --count > -1) { |
708 | #if defined ___AmigaOS___ |
||
709 | // Check if CTRL+C was pressed and abort the program. |
||
710 | if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) { |
||
711 | run = FALSE; |
||
712 | } |
||
713 | #endif |
||
714 | // Find the smallest line in all temporary files. |
||
715 | if(verbose) { |
||
716 | fprintf(stdout, "Merging all database lines in temporary files: %d%%.\r", 100 - (int)(((float)count / lines) * 100.0)); |
||
717 | } |
||
718 | |||
14 | office | 719 | min = NULL; |
1 | office | 720 | for(i = 0; i < files; ++i) { |
721 | tmp = ReadDatabaseLine(tp[i]); |
||
722 | if(tmp == NULL) { |
||
723 | continue; |
||
724 | } |
||
14 | office | 725 | if(min == NULL || strncmp(tmp, min, strlen(tmp)) < 0) { |
726 | if(min != NULL) { |
||
2 | office | 727 | // Free previous instance. |
14 | office | 728 | free(min); |
2 | office | 729 | } |
19 | office | 730 | min = malloc((strlen(tmp) + 1) * sizeof(*min)); |
14 | office | 731 | sprintf(min, "%s", tmp); |
1 | office | 732 | // Remember the index of the file where the smallest entry has been found. |
14 | office | 733 | j = i; |
1 | office | 734 | free(tmp); |
735 | continue; |
||
736 | } |
||
737 | free(tmp); |
||
738 | } |
||
739 | |||
740 | // Forward the file where the smallest line was found. |
||
14 | office | 741 | SkipDatabaseLine(tp[j]); |
1 | office | 742 | |
743 | // Write the smallest line. |
||
14 | office | 744 | if(min != NULL) { |
22 | office | 745 | #if defined ___AsyncIO___ |
746 | WriteAsync(fp, min, (LONG)(strlen(min) * sizeof(char))); |
||
747 | WriteAsync(fp, "\n", 1 * sizeof(char)); |
||
748 | #else |
||
14 | office | 749 | fprintf(fp, "%s\n", min); |
22 | office | 750 | #endif |
14 | office | 751 | free(min); |
1 | office | 752 | } |
753 | } |
||
754 | |||
755 | // Write out any remaining contents from the temporary files. |
||
756 | for(i = 0; i < files; ++i) { |
||
757 | tmp = ReadDatabaseLine(tp[i]); |
||
758 | if(tmp == NULL) { |
||
759 | continue; |
||
760 | } |
||
22 | office | 761 | #if defined ___AsyncIO___ |
762 | WriteAsync(fp, tmp, (LONG)(strlen(tmp) * sizeof(char))); |
||
763 | WriteAsync(fp, "\n", 1 * sizeof(char)); |
||
764 | #else |
||
1 | office | 765 | fprintf(fp, "%s\n", tmp); |
22 | office | 766 | #endif |
14 | office | 767 | free(tmp); |
1 | office | 768 | } |
769 | |||
770 | // Close and delete all temporary files. |
||
771 | for(i = 0; i < files; ++i) { |
||
22 | office | 772 | #if defined ___AsyncIO___ |
773 | CloseAsync(tp[i]); |
||
774 | #else |
||
1 | office | 775 | fclose(tp[i]); |
22 | office | 776 | #endif |
1 | office | 777 | // Delete temporary file. |
778 | remove(tmpNames[i]); |
||
779 | } |
||
780 | |||
781 | if(verbose) { |
||
782 | fprintf(stdout, "\n"); |
||
783 | } |
||
784 | |||
22 | office | 785 | #if defined ___AsyncIO___ |
786 | CloseAsync(fp); |
||
787 | #else |
||
1 | office | 788 | fclose(fp); |
22 | office | 789 | #endif |
1 | office | 790 | } |
791 | |||
792 | /* |
||
793 | * |
||
794 | * Indexes a "path" by creating a database "dbFile". |
||
795 | */ |
||
796 | void Gather(char *dbFile, char *path) { |
||
797 | stringStack *stack = stringStackCreate(1); |
||
798 | stats *stats = malloc(sizeof(stats)); |
||
799 | char **tmpNames; |
||
800 | int dbSize, dbLines, tmpFiles, tmpLines; |
||
801 | int i; |
||
802 | |||
803 | // Initialize metrics. |
||
804 | stats->dirs = 0; |
||
805 | stats->files = 0; |
||
806 | |||
807 | // Push the first path onto the stack. |
||
808 | stringStackPush(stack, path); |
||
809 | |||
810 | // Generate the database file. |
||
811 | UpdateDatabase(dbFile, stack, stats); |
||
812 | |||
813 | // Get the database metrics. |
||
814 | dbSize = GetDatabaseSize(dbFile); |
||
815 | dbLines = CountDatabaseLines(dbFile); |
||
816 | |||
817 | // Compute the amount of temporary files needed. |
||
818 | tmpFiles = dbSize / MAX_MEM; |
||
819 | |||
820 | // In case no temporary files are required, |
||
821 | // just sort the database and terminate. |
||
2 | office | 822 | if(tmpFiles <= 1) { |
1 | office | 823 | SortDatabase(dbFile); |
824 | return; |
||
825 | } |
||
826 | |||
827 | tmpLines = dbLines / tmpFiles; |
||
828 | |||
829 | // Create temporary files. |
||
830 | if((tmpNames = CreateTempFiles(tmpFiles)) == NULL) { |
||
831 | fprintf(stderr, "Unable to create temporary files.\n"); |
||
832 | return; |
||
833 | } |
||
834 | |||
835 | // Write "tmpLines" to temporary files in "tmpFiles" from "dbFile". |
||
836 | WriteTempFiles(dbFile, tmpNames, tmpFiles, tmpLines, dbLines); |
||
837 | |||
838 | // Sort the temporary files. |
||
839 | for(i = 0; i < tmpFiles; ++i) { |
||
840 | SortDatabase(tmpNames[i]); |
||
841 | } |
||
842 | |||
843 | MergeDatabase(dbFile, tmpNames, tmpFiles, dbLines); |
||
844 | } |
||
845 | |||
11 | office | 846 | void usage(char *name) { |
847 | fprintf(stdout, "Hunt & Gather - %s, a file index generating tool. \n", name); |
||
19 | office | 848 | fprintf(stdout, "Version: %s \n", PROGRAM_VERSION); |
11 | office | 849 | fprintf(stdout, " \n"); |
850 | fprintf(stdout, "SYNTAX: %s [-q] DATABASE \n", name); |
||
851 | fprintf(stdout, " \n"); |
||
852 | fprintf(stdout, " -q Do not print out any messages. \n"); |
||
853 | fprintf(stdout, " \n"); |
||
854 | fprintf(stdout, "DATABASE is a path to where the indexed results will be \n"); |
||
855 | fprintf(stdout, "stored for searching with the Hunt tool. \n"); |
||
856 | fprintf(stdout, " \n"); |
||
857 | fprintf(stdout, "(c) 2021 Wizardry and Steamworks, MIT. \n"); |
||
858 | } |
||
859 | |||
1 | office | 860 | /* |
861 | * |
||
862 | * Main entry point. |
||
863 | */ |
||
864 | int main(int argc, char **argv) { |
||
865 | int option; |
||
2 | office | 866 | char *dbFile; |
19 | office | 867 | char *path; |
10 | office | 868 | struct stat dirStat; |
19 | office | 869 | #if defined ___AmigaOS___ |
870 | BPTR lock; |
||
871 | #endif |
||
1 | office | 872 | |
873 | // Bind handler to SIGINT. |
||
874 | signal(SIGINT, SignalHandler); |
||
875 | |||
2 | office | 876 | dbFile = DEFAULT_DATABASE_FILE; |
877 | while((option = getopt(argc, argv, "hqd:")) != -1) { |
||
1 | office | 878 | switch(option) { |
2 | office | 879 | case 'd': |
880 | dbFile = optarg; |
||
881 | break; |
||
1 | office | 882 | case 'q': |
883 | verbose = FALSE; |
||
884 | break; |
||
885 | case 'h': |
||
11 | office | 886 | usage(argv[0]); |
2 | office | 887 | return 0; |
1 | office | 888 | case '?': |
889 | fprintf(stderr, "Invalid option %ct.\n", optopt); |
||
890 | return 1; |
||
891 | } |
||
892 | } |
||
893 | |||
10 | office | 894 | |
895 | if(optind >= argc) { |
||
11 | office | 896 | usage(argv[0]); |
1 | office | 897 | return 1; |
898 | } |
||
899 | |||
19 | office | 900 | #if defined ___AmigaOS___ |
901 | path = malloc(PATH_MAX * sizeof(*path)); |
||
902 | lock = Lock(argv[optind], SHARED_LOCK); |
||
903 | NameFromLock(lock, path, PATH_MAX); |
||
904 | UnLock(lock); |
||
905 | #else |
||
906 | path = realpath(argv[optind], NULL); |
||
907 | #endif |
||
908 | |||
909 | stat(path, &dirStat); |
||
10 | office | 910 | if(!S_ISDIR(dirStat.st_mode)) { |
5 | office | 911 | fprintf(stderr, "Path '%s' is not a directory.\n", argv[optind]); |
1 | office | 912 | return 1; |
913 | } |
||
914 | |||
2 | office | 915 | if(verbose) { |
916 | fprintf(stdout, "Gathering to database file: %s\n", dbFile); |
||
917 | } |
||
918 | |||
1 | office | 919 | // Gather. |
19 | office | 920 | Gather(dbFile, path); |
1 | office | 921 | |
19 | office | 922 | free(path); |
923 | |||
1 | office | 924 | return 0; |
925 | } |