HuntnGather – Blame information for rev 7

Subversion Repositories:
Rev:
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>
13  
14 #include <proto/dos.h>
15 #include <proto/exec.h>
16  
17 #include "StringStack.h"
18  
5 office 19 #if !defined ___HAVE_GETOPT___
1 office 20 #include "getopt.h"
21 #endif
22  
5 office 23 #if defined ___AmigaOS___
24 #include <dos/dos.h>
25 #include <dos/rdargs.h>
26 #define TEMPLATE "Q=Quiet/S,D=Database/K,P=Path/A/F"
27 #define OPT_QUIET 0
28 #define OPT_DATABASE 1
29 #define OPT_PATH 2
30 #define OPT_COUNT 3
31 LONG result[OPT_COUNT];
32 /*************************************************************************/
33 /* Version string used for querrying the program version. */
34 /*************************************************************************/
35 TEXT version_string[] =
36 "\0$VER: Gather 1.4 "__DATE__" by Wizardry and Steamworks";
37 #endif
1 office 38  
39 #if !defined TRUE
40 #define TRUE 1;
41 #endif
42  
43 #if !defined FALSE
44 #define FALSE 0;
45 #endif
46  
2 office 47 #define MAX_MEM 262144
48 #define DEFAULT_DATABASE_FILE "S:gather.db"
1 office 49  
50 typedef struct {
51 unsigned int dirs;
52 unsigned int files;
53 } stats;
54  
55 int run = TRUE;
56 int verbose = TRUE;
57  
58 void SignalHandler(int sig) {
59 // Toggle the run flag to stop execution.
60 run = FALSE;
61 }
62  
63 int compare(const void *a, const void *b) {
64 const char **p = (const char **)a;
65 const char **q = (const char **)b;
66 return strcmp(*p, *q);
67 }
68  
69 /*
70 *
71 * Sorts a database file lexicographically.
72 */
73 void SortDatabase(char *dbFile) {
74 FILE *fp;
75 char *name = NULL;
76 char *path = NULL;
77 char **database;
78 char c;
79 int i;
80 int side;
81 unsigned int line;
82  
83 // Open database file for reading.
84 if((fp = fopen(dbFile, "r")) == NULL) {
85 fprintf(stderr, "Unable to open gather database for reading.\n");
86 return;
87 }
88  
89 database = (char **) malloc(sizeof(char *));
90 name = (char *) malloc(sizeof(char));
91 path = (char *) malloc(sizeof(char));
92 line = 0;
93 side = 0;
94 i = 0;
95  
96 if(verbose) {
97 fprintf(stdout, "Sorting database: '%s'\n", dbFile);
98 }
99  
100 while(run && fscanf(fp, "%c", &c) == 1) {
101 #if defined ___AmigaOS___
102 // Check if CTRL+C was pressed and abort the program.
103 if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
104 run = FALSE;
105 continue;
106 }
107 #endif
108 switch(c) {
109 case '\n':
110 // Load up the name and path into the database variable.
111 database = (char **) realloc(database, (line + 1) * sizeof(char *));
112 database[line] = (char *) malloc((strlen(name) + strlen(path) + 1 + 1) * sizeof(char));
113 sprintf(database[line], "%s\t%s", name, path);
114 ++line;
115  
116 free(name);
117 name = (char *) malloc(sizeof(char));
118 --side;
119 i = 0;
120  
121 break;
122 case '\t':
123 free(path);
124 path = (char *) malloc(sizeof(char));
125 ++side;
126 i = 0;
127 break;
128 default:
129 switch(side) {
130 case 0:
131 name = (char *) realloc(name, (i + 1 + 1) * sizeof(char));
132 name[i] = c;
133 name[i + 1] = '\0';
134 break;
135 case 1:
136 path = (char *) realloc(path, (i + 1 + 1) * sizeof(char));
137 path[i] = c;
138 path[i + 1] = '\0';
139 break;
140 default:
141 fprintf(stderr, "Database corrupted.\n");
142 break;
143 }
144 ++i;
145 break;
146 }
147 }
148  
149 fclose(fp);
150  
151 // Sort the database.
152 qsort(database, line, sizeof(char *), compare);
153  
154 // Write the database lines back to the database.
155 if((fp = fopen(dbFile, "w+")) == NULL) {
156 fprintf(stderr, "Unable to open gather database for writing.\n");
157 return;
158 }
159  
160 for(i = 0; i < line; ++i) {
161 fprintf(fp, "%s\n", database[i]);
162 }
163  
164 free(database);
165 fclose(fp);
166 }
167  
168 /*
169 *
170 * Updates a database file "dbFile".
171 */
172 void UpdateDatabase(char *dbFile, stringStack *dirStack, stats *stats) {
173 FILE *fp;
174 DIR *dir;
175 struct dirent *dirEntry;
176 struct stat dirStat;
177 unsigned int size;
178 char *path;
179 char *subPath;
180  
181 if((fp = fopen(dbFile, "w+")) == NULL) {
182 fprintf(stderr, "Unable to open gather database for writing.\n");
183 return;
184 }
185  
186 while(run && !stringStackIsEmpty(dirStack)) {
187 #if defined ___AmigaOS___
188 // Check if CTRL+C was pressed and abort the program.
189 if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
190 run = FALSE;
191 }
192 #endif
193 if((path = stringStackPop(dirStack)) == NULL) {
194 return;
195 }
196  
197 if((dir = opendir(path)) == NULL) {
198 return;
199 }
200  
201 while(run && (dirEntry = readdir(dir)) != NULL) {
202 #if defined ___AmigaOS___
203 // Check if CTRL+C was pressed and abort the program.
204 if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
205 run = FALSE;
206 }
207 #endif
208 size = sizeof(path) + sizeof(dirEntry->d_name) + 1;
209 switch(path[strlen(path) - 1]) {
210 case '/':
211 case ':': // This is a drive path.
212 subPath = (char *) malloc(size);
213 sprintf(subPath, "%s%s", path, dirEntry->d_name);
214 break;
215 default:
216 subPath = (char *) malloc(size + 1);
217 sprintf(subPath, "%s/%s", path, dirEntry->d_name);
218 break;
219 }
220 stat(subPath, &dirStat);
221 if(S_ISDIR(dirStat.st_mode)) {
222 stringStackPush(dirStack, subPath);
223  
224 ++stats->dirs;
225  
226 if(verbose) {
227 fprintf(stdout,
228 "Gathered %d directories and %d files.\r",
229 stats->dirs,
230 stats->files);
231 }
232  
233 free(subPath);
234 continue;
235 }
236  
237 // Write to database file.
238 fprintf(fp, "%s\t%s\n", dirEntry->d_name, subPath);
239  
240 ++stats->files;
241  
242 if(verbose) {
243 fprintf(stdout,
244 "Gathered %d directories and %d files.\r",
245 stats->dirs,
246 stats->files);
247 }
248  
249 free(subPath);
250 }
251  
252 closedir(dir);
253 free(path);
254 }
255  
256 if(verbose) {
257 fprintf(stdout, "\n");
258 }
259  
260 fclose(fp);
261  
262 }
263  
264 /*
265 *
266 * Gets the size of a database "dbFle".
267 */
268 int GetDatabaseSize(char *dbFile) {
269 FILE *fp;
270 int size;
271  
272 if((fp = fopen(dbFile, "r")) == NULL) {
273 fprintf(stderr, "Unable to open gather database for reading.\n");
274 fclose(fp);
275 return 0;
276 }
277  
278 fseek(fp, 0L, SEEK_END);
279 size = ftell(fp);
280  
281 fclose(fp);
282 return size;
283 }
284  
285 /*
286 *
287 * Counts the lines in a database file "dbFile".
288 */
289 int CountDatabaseLines(char *dbFile) {
290 FILE *fp;
291 int lines;
292 char c;
293  
294 if((fp = fopen(dbFile, "r")) == NULL) {
295 fprintf(stderr, "Unable to open gather database for reading.\n");
296 fclose(fp);
297 return 0;
298 }
299  
300 lines = 0;
301 while(fscanf(fp, "%c", &c) == 1) {
302 switch(c) {
303 case '\n':
304 ++lines;
305 break;
306 }
307 }
308  
309 fclose(fp);
310  
311 return lines;
312 }
313  
314 /*
315 *
316 * Creates "files" temporary filenames.
317 */
318 char **CreateTempFiles(int files) {
319 char **tmpNames;
320 int count;
321  
322 tmpNames = (char **) malloc(files * sizeof(char *));
323  
324 if(verbose) {
325 fprintf(stdout, "Creating temporary files.\r");
326 }
327  
328 count = files;
329 while(--count > -1) {
330 #if defined ___AmigaOS___
331 // Check if CTRL+C was pressed and abort the program.
332 if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
333 run = FALSE;
334 }
335 #endif
336 tmpNames[count] = tmpnam(NULL);
337  
338 if(verbose) {
339 fprintf(stdout, "Creating temporary files: %d%%\r", 100 - (int)(((float)count / files) * 100.0));
340 }
341 }
342  
343 if(verbose) {
344 fprintf(stdout, "\n");
345 }
346  
347 return tmpNames;
348 }
349  
350 /*
351 *
352 * Writes lines from the database "dbFile" to temporary filenames "tmpNames".
353 */
354 void WriteTempFiles(char *dbFile, char **tmpNames, int tmpFiles, int tmpLines, int total) {
355 FILE *fp, *tp;
356 char c;
357 int lines;
358 int linesWritten;
359  
360 if((fp = fopen(dbFile, "r")) == NULL) {
361 fprintf(stderr, "Unable to open gather database for reading.\n");
362 return;
363 }
364  
365 if((tp = fopen(tmpNames[--tmpFiles], "w+")) == NULL) {
366 fprintf(stderr, "Unable to open temporary file '%s' for writing.\n", tmpNames[tmpFiles]);
367 fclose(fp);
368 return;
369 }
370  
371 if(verbose) {
372 fprintf(stdout, "Writing to temporary files.\r");
373 }
374  
375 linesWritten = 0;
376 lines = 0;
377 while(run && fscanf(fp, "%c", &c) == 1) {
378 #if defined ___AmigaOS___
379 // Check if CTRL+C was pressed and abort the program.
380 if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
381 run = FALSE;
382 }
383 #endif
384 switch(c) {
385 case '\n':
386 // Increment the total written lines.
387 ++linesWritten;
388  
389 if(verbose) {
390 fprintf(stdout, "Writing to temporary files: %d%%.\r", (int)(((float)linesWritten / total) * 100.0));
391 }
392  
393 // Write the newline character back.
394 if(fprintf(tp, "%c", c) != 1) {
395 fprintf(stderr, "Unable to write to temporary file '%s'.\n", tmpNames[tmpFiles]);
396 fclose(tp);
397 fclose(fp);
398 return;
399 }
400 // Switch to the next temporary file.
401 if(++lines >= tmpLines) {
402 // If there are no temporary files left then run till the end.
403 if(tmpFiles - 1 < 0) {
404 break;
405 }
406  
407 // Close the previous temporary file and write to the next temporary file.
408 fclose(tp);
409 if((tp = fopen(tmpNames[--tmpFiles], "w+")) == NULL) {
410 fprintf(stderr, "Unable to open temporary file '%s' for writing.\n", tmpNames[tmpFiles]);
411 fclose(tp);
412 fclose(fp);
413 }
414 lines = 0;
415 break;
416 }
417 break;
418 default:
419 if(fprintf(tp, "%c", c) != 1) {
420 fprintf(stderr, "Unable to write to temporary file '%s'.\n", tmpNames[tmpFiles]);
421 fclose(tp);
422 fclose(fp);
423 return;
424 }
425 break;
426 }
427 }
428  
429 fprintf(stdout, "\n");
430  
431 fclose(tp);
432 fclose(fp);
433 }
434  
435 /*
436 *
437 * Skips a line in a database file "fp".
438 */
439 void SkipDatabaseLine(FILE *fp) {
440 char c;
441  
442 while(fscanf(fp, "%c", &c) == 1) {
443 if(c == '\n') {
444 break;
445 }
446 }
447  
448 return;
449 }
450  
451 /*
452 *
453 * Reads a line from the database file "fp".
454 */
455 char *ReadDatabaseLine(FILE *fp) {
456 char c;
457 char *line;
458 int chars;
459  
460 line = (char *) malloc(sizeof(char));
461  
462 chars = 0;
463 while(run && fscanf(fp, "%c", &c) == 1) {
464 #if defined ___AmigaOS___
465 // Check if CTRL+C was pressed and abort the program.
466 if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
467 run = FALSE;
468 }
469 #endif
470 switch(c) {
471 case '\n':
472 // Rewind the file by the number of read characters.
473 fseek(fp, -(chars + 1), SEEK_CUR);
474 return line;
475 default:
476 line = (char *) realloc(line, (chars + 1 + 1) * sizeof(char));
477 line[chars] = c;
478 line[chars + 1] = '\0';
479 break;
480 }
481 ++chars;
482 }
483  
484 return NULL;
485 }
486  
487 /*
488 *
489 * Merges temporary files "tmpNames" into a database "dbFile".
490 */
491 void MergeDatabase(char *dbFile, char **tmpNames, int files, int lines) {
492 FILE *fp;
493 FILE **tp;
494 int i;
495 char *tmp;
496 char *tmpMin;
497 int idxMin;
498 int count;
499  
500 if((fp = fopen(dbFile, "w+")) == NULL) {
501 fprintf(stderr, "Unable to open gather database for writing.\n");
502 return;
503 }
504  
505 // Allocate as many file pointers as temporary files.
506 tp = (FILE **) malloc(files * sizeof(FILE *));
507  
508 // Open all temporary files for reading.
509 for(i = 0; i < files; ++i) {
510 if((tp[i] = fopen(tmpNames[i], "r")) == NULL) {
511 fprintf(stderr, "Unable to open temporary file '%s' for reading.\n", tmpNames[i]);
512 // Close all temporary files.
513 --i;
514 while(i >= 0) {
515 fclose(tp[i]);
516 }
517 return;
518 }
519 }
520  
521 if(verbose) {
522 fprintf(stdout, "Merging all database lines in temporary files.\r");
523 }
524  
525 count = lines;
526 idxMin = 0;
527 while(run && --count > -1) {
528 #if defined ___AmigaOS___
529 // Check if CTRL+C was pressed and abort the program.
530 if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
531 run = FALSE;
532 }
533 #endif
534 // Find the smallest line in all temporary files.
535 if(verbose) {
536 fprintf(stdout, "Merging all database lines in temporary files: %d%%.\r", 100 - (int)(((float)count / lines) * 100.0));
537 }
538  
539 tmpMin = NULL;
540 for(i = 0; i < files; ++i) {
541 tmp = ReadDatabaseLine(tp[i]);
542 if(tmp == NULL) {
543 free(tmp);
544 continue;
545 }
546 if(tmpMin == NULL || strcmp(tmp, tmpMin) < 0) {
2 office 547 if(tmpMin != NULL) {
548 // Free previous instance.
549 free(tmpMin);
550 }
1 office 551 tmpMin = (char *) malloc((strlen(tmp) + 1) * sizeof(char));
552 sprintf(tmpMin, "%s", tmp);
553 // Remember the index of the file where the smallest entry has been found.
554 idxMin = i;
555 free(tmp);
556 continue;
557 }
558 free(tmp);
559 }
560  
561 // Forward the file where the smallest line was found.
562 SkipDatabaseLine(tp[idxMin]);
563  
564 // Write the smallest line.
565 if(tmpMin != NULL) {
566 fprintf(fp, "%s\n", tmpMin);
567 free(tmpMin);
568 }
569 }
570  
571 // Write out any remaining contents from the temporary files.
572 for(i = 0; i < files; ++i) {
573 tmp = ReadDatabaseLine(tp[i]);
574 if(tmp == NULL) {
575 continue;
576 }
577 fprintf(fp, "%s\n", tmp);
578 }
579  
580 // Close and delete all temporary files.
581 for(i = 0; i < files; ++i) {
582 fclose(tp[i]);
583 // Delete temporary file.
584 remove(tmpNames[i]);
585 }
586  
587 if(verbose) {
588 fprintf(stdout, "\n");
589 }
590  
591 fclose(fp);
592 }
593  
594 /*
595 *
596 * Indexes a "path" by creating a database "dbFile".
597 */
598 void Gather(char *dbFile, char *path) {
599 stringStack *stack = stringStackCreate(1);
600 stats *stats = malloc(sizeof(stats));
601 char **tmpNames;
602 int dbSize, dbLines, tmpFiles, tmpLines;
603 int i;
604  
605 // Initialize metrics.
606 stats->dirs = 0;
607 stats->files = 0;
608  
609 // Push the first path onto the stack.
610 stringStackPush(stack, path);
611  
612 // Generate the database file.
613 UpdateDatabase(dbFile, stack, stats);
614  
615 // Get the database metrics.
616 dbSize = GetDatabaseSize(dbFile);
617 dbLines = CountDatabaseLines(dbFile);
618  
619 // Compute the amount of temporary files needed.
620 tmpFiles = dbSize / MAX_MEM;
621  
622 // In case no temporary files are required,
623 // just sort the database and terminate.
2 office 624 if(tmpFiles <= 1) {
1 office 625 SortDatabase(dbFile);
626 return;
627 }
628  
629 tmpLines = dbLines / tmpFiles;
630  
631 // Create temporary files.
632 if((tmpNames = CreateTempFiles(tmpFiles)) == NULL) {
633 fprintf(stderr, "Unable to create temporary files.\n");
634 return;
635 }
636  
637 // Write "tmpLines" to temporary files in "tmpFiles" from "dbFile".
638 WriteTempFiles(dbFile, tmpNames, tmpFiles, tmpLines, dbLines);
639  
640 // Sort the temporary files.
641 for(i = 0; i < tmpFiles; ++i) {
642 SortDatabase(tmpNames[i]);
643 }
644  
645 MergeDatabase(dbFile, tmpNames, tmpFiles, dbLines);
646 }
647  
648 /*
649 *
650 * Main entry point.
651 */
652 int main(int argc, char **argv) {
5 office 653 #if defined ___AmigaOS___
654 struct RDArgs *rdargs;
655 struct stat statPath;
656 UBYTE *dbFile;
7 office 657 UBYTE *argPattern;
5 office 658  
659 dbFile = DEFAULT_DATABASE_FILE;
7 office 660 argPattern = NULL;
5 office 661 if(rdargs = (struct RDArgs *) AllocDosObject(DOS_RDARGS, NULL)) {
662 if(ReadArgs(TEMPLATE, result, rdargs) == NULL) {
663 FreeArgs(rdargs);
664 FreeDosObject(DOS_RDARGS, rdargs);
665 return 1;
666 }
667  
668 if(result[OPT_DATABASE] != NULL) {
669 dbFile = (UBYTE *) result[OPT_DATABASE];
670 }
671  
7 office 672 argPattern = (UBYTE *) result[OPT_PATH];
5 office 673  
674 FreeArgs(rdargs);
675 FreeDosObject(DOS_RDARGS, rdargs);
676 }
677  
7 office 678 stat(dbFile, &statPath);
679 if(!S_ISREG(statPath.st_mode)) {
680 fprintf(stderr, "Database file '%s' is not a file.\n", dbFile);
5 office 681 return 1;
682 }
683  
7 office 684 Gather(dbFile, argPattern);
5 office 685 #else
1 office 686 int option;
2 office 687 char *dbFile;
1 office 688 struct stat path;
689  
690 // Bind handler to SIGINT.
691 signal(SIGINT, SignalHandler);
692  
2 office 693 dbFile = DEFAULT_DATABASE_FILE;
694 while((option = getopt(argc, argv, "hqd:")) != -1) {
1 office 695 switch(option) {
2 office 696 case 'd':
697 dbFile = optarg;
698 break;
1 office 699 case 'q':
700 verbose = FALSE;
701 break;
702 case 'h':
2 office 703 fprintf(stdout, "SYNTAX: %s [-q] [-d DATABASE] DIRECTORY\n", argv[0]);
704 return 0;
1 office 705 case '?':
706 fprintf(stderr, "Invalid option %ct.\n", optopt);
2 office 707 fprintf(stdout, "SYNTAX: %s [-q] [-d DATABASE] DIRECTORY\n", argv[0]);
1 office 708 return 1;
709 }
710 }
711  
712 if(optind > argc) {
2 office 713 fprintf(stdout, "SYNTAX: %s [-q] [-d DATABASE] DIRECTORY\n", argv[0]);
1 office 714 return 1;
715 }
716  
717 stat(argv[optind], &path);
718 if(!S_ISDIR(path.st_mode)) {
5 office 719 fprintf(stderr, "Path '%s' is not a directory.\n", argv[optind]);
1 office 720 return 1;
721 }
722  
2 office 723 if(verbose) {
724 fprintf(stdout, "Gathering to database file: %s\n", dbFile);
725 }
726  
1 office 727 // Gather.
2 office 728 Gather(dbFile, argv[optind]);
5 office 729 #endif
1 office 730  
731 return 0;
732 }