HuntnGather – Blame information for rev 2

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