HuntnGather – Blame information for rev 10

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