HuntnGather – Blame information for rev 1

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