HuntnGather – Rev 53
?pathlinks?
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Wizardry and Steamworks - License: MIT //
///////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#if !defined ___AmigaOS___
#include <dirent.h>
#include <sys/stat.h>
#include <signal.h>
#endif
#include <sys/types.h>
#include <sys/syslimits.h>
#if defined ___AmigaOS___
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/locale.h>
#include <clib/utility_protos.h>
#endif
#if defined ___AsyncIO___
#include <asyncio.h>
#endif
#if !defined ___HAVE_GETOPT___
#include "/shared/getopt.h"
#endif
#include "stack.h"
#include "/shared/utilities.h"
#define PROGRAM_VERSION "1.7.7"
#if defined ___AmigaOS___
/*************************************************************************/
/* Version string used for querrying the program version. */
/*************************************************************************/
TEXT version_string[] =
"\0$VER: Gather " PROGRAM_VERSION " "__DATE__" by Wizardry and Steamworks";
#endif
int PROGRAM_RUN = TRUE;
int PROGRAM_VERBOSE = TRUE;
int maxmem = MAX_MEM;
// Define global locale for string compare.
#if defined ___AmigaOS___
struct Locale *locale;
#endif
void SignalHandler(int sig) {
/* Toggle the run flag to stop execution. */
PROGRAM_RUN = FALSE;
}
/*
*
* Used for sorting database lines.
*/
int QsortCompare(const void *a, const void *b) {
#if defined ___AmigaOS___
return StrnCmp(
locale,
(STRPTR)(*(const char **)a),
(STRPTR)*((const char **)b),
-1,
SC_ASCII
);
#else
return strcmp(*(const char **)a, *(const char **)b);
#endif
}
/*
*
* Sorts a database file lexicographically.
*/
void SortDatabase(char *dbFile, int lines) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
#else
FILE *fp;
#endif
char **database;
dbEntry *entry;
dbLine *line;
char *rem;
int count;
int i;
// Open database file for reading.
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "r")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for reading.\n", dbFile);
return;
}
if((database = malloc(lines * sizeof(*database))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Reading lines from file '%s' to array...\n", dbFile);
}
count = 0;
while(PROGRAM_RUN && (line = ReadLine(fp)) != NULL) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
PROGRAM_RUN = FALSE;
continue;
}
#endif
if((entry = CreateDatabaseEntry(line)) == NULL) {
fprintf(stderr, "Unable to create database entry.\n");
free(line->string);
free(line);
line = NULL;
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
if((database[count] = malloc((strlen(entry->name) + strlen(entry->path) + 1 + 1) * sizeof(*database[count]))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
// Free database entry.
free(entry->name);
free(entry->path);
free(entry);
entry = NULL;
// Free the line.
free(line->string);
free(line);
line = NULL;
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
sprintf(database[count], "%s\t%s", entry->name, entry->path);
++count;
// Free the database entry.
free(entry->name);
free(entry->path);
free(entry);
entry = NULL;
// Free the line.
free(line->string);
free(line);
line = NULL;
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Sorting %d lines in '%s'...\n", count, dbFile);
}
// Sort the database.
qsort(database, (unsigned int)count, sizeof(char *), QsortCompare);
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Writing %d sorted lines to '%s'...\n", count, dbFile);
}
// Write the database lines back to the database.
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "w")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for writing.\n", dbFile);
return;
}
rem = NULL;
for(i = 0; PROGRAM_RUN && i < count; ++i) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
PROGRAM_RUN = FALSE;
continue;
}
#endif
if(rem != NULL) {
#if defined ___AmigaOS___
if(StrnCmp(locale, database[i], rem, -1, SC_ASCII) == 0) {
#else
if(strcmp(database[i], rem) == 0) {
#endif
continue;
}
}
#if defined ___AsyncIO___
WriteAsync(fp, database[i], (LONG)strlen(database[i]));
WriteAsync(fp, "\n", 1);
#else
fprintf(fp, "%s\n", database[i]);
#endif
if(rem != NULL) {
free(rem);
rem = NULL;
}
rem = malloc((strlen(database[i]) + 1) * sizeof(*rem));
sprintf(rem, "%s", database[i]);
}
if(rem != NULL) {
free(rem);
rem = NULL;
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Disposing %d lines of file '%s'...\n", count, dbFile);
}
// Free up database.
for(i = 0; i < count; ++i) {
free(database[i]);
database[i] = NULL;
}
free(database);
}
/*
*
* Updates a database file "dbFile".
*/
dbStats *CollectFiles(char *dbFile, VECTOR *paths) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
#else
FILE *fp;
#endif
#if defined ___AmigaOS___
struct FileInfoBlock *FIB;
BPTR lock;
#else
DIR *dir;
struct dirent *entry;
#endif
stack *stack;
dbStats *stats = NULL;
int i;
char *path;
char *sub;
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_APPEND, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "a")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for writing.\n", dbFile);
return NULL;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Collecting files...\r");
}
// Initialize metrics.
if((stats = malloc(sizeof(*stats))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
stats->dirs = 0;
stats->files = 0;
stats->lines = 0;
stats->size = 0;
// Push the first path onto the stack.
stack = stackCreate((unsigned int)paths->length);
for(i = 0; PROGRAM_RUN && i < paths->length; ++i) {
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Pushing path '%s'\n", (char *)paths->array[i]);
}
stackPush(stack, paths->array[i], (strlen(paths->array[i]) + 1) * sizeof(char));
}
while(PROGRAM_RUN && !stackIsEmpty(stack)) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
PROGRAM_RUN = FALSE;
continue;
}
#endif
if((path = (char *)stackPop(stack)) == NULL) {
break;
}
#if defined ___AmigaOS___
if((lock = Lock(path, ACCESS_READ)) == NULL) {
fprintf(stderr, "Could not lock path '%s' for reading.\n", path);
free(path);
path = NULL;
continue;
}
if((FIB = AllocDosObject(DOS_FIB, NULL)) == NULL) {
fprintf(stderr, "File information block for path '%s' could not be allocated.\n", path);
UnLock(lock);
free(path);
path = NULL;
continue;
}
if(Examine(lock, FIB) == FALSE) {
fprintf(stderr, "Path '%s' could not be examined.\n", path);
FreeDosObject(DOS_FIB, FIB);
FIB = NULL;
UnLock(lock);
free(path);
path = NULL;
continue;
}
while(PROGRAM_RUN && ExNext(lock, FIB)) {
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
PROGRAM_RUN = FALSE;
continue;
}
#else
if((dir = opendir(path)) == NULL) {
fprintf(stderr, "Directory '%s' could not be opened.\n", path);
free(path);
path = NULL;
continue;
}
while(PROGRAM_RUN && (entry = readdir(dir)) != NULL) {
#endif
switch(path[strlen(path) - 1]) {
case '/':
case ':': // This is a drive path.
#if defined ___AmigaOS___
if((sub = malloc(strlen(path) + strlen(FIB->fib_FileName) + 1)) == NULL) {
#else
if((sub = malloc(strlen(path) + strlen(entry->d_name) + 1)) == NULL) {
#endif
fprintf(stderr, "Memory allocation failure.\n");
#if defined ___AmigaOS___
FreeDosObject(DOS_FIB, FIB);
FIB = NULL;
UnLock(lock);
#else
closedir(dir);
#endif
free(path);
path = NULL;
stackDestroy(stack);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
#if defined ___AmigaOS___
sprintf(sub, "%s%s", path, FIB->fib_FileName);
#else
sprintf(sub, "%s%s", path, entry->d_name);
#endif
break;
default:
#if defined ___AmigaOS___
if((sub = malloc(strlen(path) + strlen(FIB->fib_FileName) + 1 + 1)) == NULL) {
#else
if((sub = malloc(strlen(path) + strlen(entry->d_name) + 1 + 1)) == NULL) {
#endif
fprintf(stderr, "Memory allocation failure.\n");
#if defined ___AmigaOS___
FreeDosObject(DOS_FIB, FIB);
FIB = NULL;
UnLock(lock);
#else
closedir(dir);
#endif
free(path);
path = NULL;
stackDestroy(stack);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
#if defined ___AmigaOS___
sprintf(sub, "%s/%s", path, FIB->fib_FileName);
#else
sprintf(sub, "%s/%s", path, entry->d_name);
#endif
break;
}
switch(GetFsType(sub)) {
case UNKNOWN:
free(sub);
sub = NULL;
continue;
case REGULAR:
++stats->files;
if(PROGRAM_VERBOSE) {
fprintf(stdout,
"Gathered '%d' directories and '%d' files.\r",
stats->dirs,
stats->files);
}
break;
case DIRECTORY:
stackPush(stack, sub, (strlen(sub) + 1) * sizeof(char));
++stats->dirs;
if(PROGRAM_VERBOSE) {
fprintf(stdout,
"Gathered '%d' directories and '%d' files.\r",
stats->dirs,
stats->files);
}
free(sub);
sub = NULL;
continue;
}
#if defined ___NOCASE_FS___
#if defined ___AmigaOS___
StrUpr(FIB->fib_FileName);
#else
StrUpr(entry->d_name);
#endif
#endif
// Write to database file.
#if defined ___AsyncIO___
#if defined ___AmigaOS___
WriteAsync(fp, FIB->fib_FileName, (LONG)strlen(FIB->fib_FileName));
stats->size = stats->size + strlen(FIB->fib_FileName);
#else
WriteAsync(fp, entry->d_name, (LONG)strlen(entry->d_name));
stats->size = stats->size + strlen(entry->d_name);
#endif
WriteAsync(fp, "\t", 1);
++stats->size;
WriteAsync(fp, sub, (LONG)strlen(sub));
stats->size = stats->size + strlen(sub);
WriteAsync(fp, "\n", 1);
++stats->size;
#else
#if defined ___AmigaOS___
fprintf(fp, "%s\t%s\n", FIB->fib_FileName, sub);
stats->size = stats->size + strlen(FIB->fib_FileName) + strlen(sub) + 1 + 1 + 1;
#else
fprintf(fp, "%s\t%s\n", entry->d_name, sub);
stats->size = stats->size + strlen(entry->d_name) + strlen(sub) + 1 + 1 + 1;
#endif
#endif
++stats->lines;
free(sub);
sub = NULL;
}
#if defined ___AmigaOS___
FreeDosObject(DOS_FIB, FIB);
FIB = NULL;
UnLock(lock);
#else
closedir(dir);
#endif
free(path);
path = NULL;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "\n");
}
stackDestroy(stack);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return stats;
}
/*
*
* Writes lines from the database "dbFile" to temporary filenames "tmpNames".
*/
void WriteTemporaryFiles(char *dbFile, VECTOR *tmpNames, int tmpLines, int total) {
#if defined ___AsyncIO___
struct AsyncFile *fp, *tp;
#else
FILE *fp, *tp;
#endif
int lines;
int write;
int files;
dbLine *line = NULL;
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "r")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for reading.\n", dbFile);
return;
}
files = tmpNames->length;
#if defined ___AsyncIO___
if((tp = OpenAsync(tmpNames->array[--files], MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
if((tp = fopen(tmpNames->array[--files], "w")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for writing.\n", (char *)tmpNames->array[files]);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Writing to temporary files...\r");
}
write = 0;
lines = 0;
while(PROGRAM_RUN && (line = ReadLine(fp)) != NULL) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
free(line->string);
free(line);
line = NULL;
PROGRAM_RUN = FALSE;
continue;
}
#endif
#if defined ___AsyncIO___
WriteAsync(tp, line->string, (LONG)line->length);
WriteAsync(tp, "\n", 1);
#else
fprintf(tp, "%s\n", line->string);
#endif
++write;
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Writing to temporary files: %d%%.\r", (int)(((float)write / total) * 100.0));
}
// Switch to the next temporary file.
if(++lines >= tmpLines) {
// If there are no temporary files left then run till the end.
if(files - 1 < 0) {
free(line->string);
free(line);
line = NULL;
continue;
}
// Close the previous temporary file and write to the next temporary file.
#if defined ___AsyncIO___
CloseAsync(tp);
if((tp = OpenAsync(tmpNames->array[--files], MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
fclose(tp);
if((tp = fopen(tmpNames->array[--files], "w")) == NULL) {
#endif
fprintf(stderr, "Could not open '%s' for writing.\n", (char *)tmpNames->array[files]);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
free(line->string);
free(line);
line = NULL;
return;
}
lines = 0;
}
free(line->string);
free(line);
line = NULL;
}
if(line != NULL) {
free(line->string);
free(line);
line = NULL;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "\n");
}
#if defined ___AsyncIO___
CloseAsync(tp);
CloseAsync(fp);
#else
fclose(tp);
fclose(fp);
#endif
}
/*
*
* Merges temporary files "tmpNames" into a database "dbFile".
*/
void MergeTemporaryFiles(char *dbFile, VECTOR *tmpNames, int lines) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
struct AsyncFile **tp;
#else
FILE *fp;
FILE **tp;
#endif
int i;
int j;
dbLine *tmp;
char *rem;
char *min;
int count;
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "w")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for writing.\n", dbFile);
return;
}
// Allocate as many file pointers as temporary files.
if((tp = malloc(tmpNames->length * sizeof(*tp))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
// Open all temporary files for reading.
for(i = 0; i < tmpNames->length; ++i) {
#if defined ___AsyncIO___
if((tp[i] = OpenAsync(tmpNames->array[i], MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((tp[i] = fopen(tmpNames->array[i], "r")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for reading.\n", (char *)tmpNames->array[i]);
// Close all temporary files.
while(--i > -1) {
#if defined ___AsyncIO___
CloseAsync(tp[i]);
#else
fclose(tp[i]);
#endif
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Merging all files...\r");
}
rem = NULL;
count = lines;
j = 0;
while(PROGRAM_RUN && --count > -1) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
PROGRAM_RUN = FALSE;
continue;
}
#endif
// Find the smallest line in all temporary files.
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Merging all files: %d%%.\r", 100 - (int)(((float)count / lines) * 100.0));
}
min = NULL;
for(i = 0; i < tmpNames->length; ++i) {
tmp = PeekLine(tp[i]);
if(tmp == NULL) {
continue;
}
#if defined ___AmigaOS___
if(min == NULL || StrnCmp(locale, tmp->string, min, -1, SC_ASCII) < 0) {
#else
if(min == NULL || strcmp(tmp->string, min) < 0) {
#endif
if(min != NULL) {
// Free previous instance.
free(min);
min = NULL;
}
if((min = malloc((strlen(tmp->string) + 1) * sizeof(*min))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
free(tmp->string);
free(tmp);
tmp = NULL;
if(min != NULL) {
free(min);
min = NULL;
}
if(rem != NULL) {
free(rem);
rem = NULL;
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
sprintf(min, "%s", tmp->string);
// Remember the index of the file where the smallest entry has been found.
j = i;
}
free(tmp->string);
free(tmp);
tmp = NULL;
}
// Forward the file where the smallest line was found.
SkipLine(tp[j]);
// Write the smallest line.
if(min != NULL) {
// If current minimum line is identical to previous minimum line then skip to remove duplicates.
if(rem != NULL) {
#if defined ___AmigaOS___
if(StrnCmp(locale, min, rem, -1, SC_ASCII) == 0) {
#else
if(strcmp(min, rem) == 0) {
#endif
free(min);
min = NULL;
continue;
}
}
#if defined ___AsyncIO___
WriteAsync(fp, min, (LONG)strlen(min));
WriteAsync(fp, "\n", 1);
#else
fprintf(fp, "%s\n", min);
#endif
if(rem != NULL) {
free(rem);
rem = NULL;
}
if((rem = malloc((strlen(min) + 1) * sizeof(*rem))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
free(min);
min = NULL;
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
// Remember the last minimal line.
sprintf(rem, "%s", min);
free(min);
min = NULL;
}
}
if(rem != NULL) {
free(rem);
rem = NULL;
}
// Write out any remaining contents from the temporary files.
for(i = 0; PROGRAM_RUN && i < tmpNames->length; ++i) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
PROGRAM_RUN = FALSE;
continue;
}
#endif
tmp = ReadLine(tp[i]);
if(tmp == NULL) {
continue;
}
#if defined ___AsyncIO___
WriteAsync(fp, tmp->string, (LONG)strlen(tmp->string));
WriteAsync(fp, "\n", 1);
#else
fprintf(fp, "%s\n", tmp->string);
#endif
free(tmp->string);
free(tmp);
tmp = NULL;
}
// Close all temporary files.
for(i = 0; i < tmpNames->length; ++i) {
#if defined ___AsyncIO___
CloseAsync(tp[i]);
#else
fclose(tp[i]);
#endif
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
if(PROGRAM_VERBOSE) {
fprintf(stdout, "\n");
}
}
/*
*
* Filter the paths inside the database with provided paths.
*/
void FilterDatabasePaths(char *dbFile, char *tmpName, VECTOR *paths) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
struct AsyncFile *tp;
#else
FILE *fp;
FILE *tp;
#endif
dbLine *line;
dbEntry *entry;
int lines;
int i;
// Open database file for reading.
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "r")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for reading.\n", dbFile);
return;
}
// Open temporary file for writing.
#if defined ___AsyncIO___
if((tp = OpenAsync(tmpName, MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
if((tp = fopen(tmpName, "w")) == NULL) {
#endif
fprintf(stderr, "Could not open file '%s' for writing.\n", tmpName);
// Close database file.
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Removing lines...\r");
}
lines = 0;
while(PROGRAM_RUN && (line = ReadLine(fp)) != NULL) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
PROGRAM_RUN = FALSE;
continue;
}
#endif
if((entry = CreateDatabaseEntry(line)) == NULL) {
fprintf(stderr, "Unable to create database entry.\n");
free(line->string);
free(line);
line = NULL;
continue;
}
for(i = 0; i < paths->length; ++i) {
if(PathCompare(entry->path, paths->array[i]) == TRUE) {
++lines;
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Removing lines: %d.\r", lines);
}
continue;
}
#if defined ___AsyncIO___
WriteAsync(tp, line->string, (LONG)strlen(line->string));
WriteAsync(tp, "\n", 1);
#else
fprintf(tp, "%s\n", line->string);
#endif
break;
}
// Free up database entry.
free(entry->name);
free(entry->path);
free(entry);
entry = NULL;
// Free up line.
free(line->string);
free(line);
line = NULL;
}
#if defined ___AsyncIO___
CloseAsync(fp);
CloseAsync(tp);
#else
fclose(fp);
fclose(tp);
#endif
if(PROGRAM_VERBOSE) {
fprintf(stdout, "\n");
}
}
/*
*
* Indexes paths and adds to a database file.
*/
void GatherDatabaseFiles(char *dbFile, VECTOR *paths) {
dbStats *stats;
VECTOR *tmpNames;
int tmpFiles;
int tmpLines;
int i;
int line;
int size;
// Generate the database file from the supplied paths.
if((stats = CollectFiles(dbFile, paths)) == NULL) {
fprintf(stderr, "Collecting files failed.\n");
return;
}
// The size and amount of lines are not necessarily what has been gathered now.
size = GetFileSize(dbFile);
line = CountFileLines(dbFile);
// Calculate the total number of temporary files required.
tmpFiles = size / maxmem;
/* In case no temporary files are required,
* just sort the database and terminate.
*/
if(tmpFiles < 2) {
SortDatabase(dbFile, line);
return;
}
// Calculate the number of lines per temporary file.
tmpLines = ceil(((double)line) / ((double)tmpFiles));
// Create temporary files.
if((tmpNames = CreateTemporaryFiles(tmpFiles)) == NULL) {
fprintf(stderr, "Unable to create temporary files.\n");
return;
}
// Write "tmpLines" to temporary files in "tmpNames" from "dbFile".
WriteTemporaryFiles(dbFile, tmpNames, tmpLines, line);
// Sort the temporary files.
for(i = 0; i < tmpNames->length; ++i) {
SortDatabase(tmpNames->array[i], tmpLines);
}
// Merge all the temporary files to the database file.
MergeTemporaryFiles(dbFile, tmpNames, line);
// Remove all temporary files.
RemoveFiles(tmpNames);
// Free temporary file names.
free(tmpNames);
tmpNames = NULL;
// Free statistics.
free(stats);
stats = NULL;
}
/*
*
* Indexes paths and creates a daabase file.
*/
void CreateDatabaseFiles(char *dbFile, VECTOR *paths) {
dbStats *stats;
VECTOR *tmpNames;
int tmpFiles;
int tmpLines;
int i;
// Generate the database file from the supplied paths.
if((stats = CollectFiles(dbFile, paths)) == NULL) {
fprintf(stderr, "Collecting files failed.\n");
return;
}
// Calculate the total number of temporary files required.
tmpFiles = stats->size / maxmem;
/* In case no temporary files are required,
* just sort the database and terminate.
*/
if(tmpFiles < 2) {
SortDatabase(dbFile, stats->lines);
return;
}
// Calculate the number of lines per temporary file.
tmpLines = ceil(((double)stats->lines) / ((double)tmpFiles));
// Create temporary files.
if((tmpNames = CreateTemporaryFiles(tmpFiles)) == NULL) {
fprintf(stderr, "Unable to create temporary files.\n");
return;
}
// Write "tmpLines" to temporary files in "tmpNames" from "dbFile".
WriteTemporaryFiles(dbFile, tmpNames, tmpLines, stats->lines);
// Sort the temporary files.
for(i = 0; i < tmpNames->length; ++i) {
SortDatabase(tmpNames->array[i], tmpLines);
}
// Merge all the temporary files to the database file.
MergeTemporaryFiles(dbFile, tmpNames, stats->lines);
// Remove all temporary files.
RemoveFiles(tmpNames);
// Free temporary file names.
free(tmpNames);
tmpNames = NULL;
// Free statistics.
free(stats);
stats = NULL;
}
void RemoveDatabaseFiles(char *dbFile, VECTOR *paths) {
char *tmpName;
// Create a temporary file to hold the changes.
if((tmpName = CreateTemporaryFile()) == NULL) {
fprintf(stderr, "Unable to create temporary file.\n");
return;
}
// Filter the database of the provided paths.
FilterDatabasePaths(dbFile, tmpName, paths);
// Overwrite the database file with the filtered paths.
CopyLines(tmpName, dbFile);
// Remove temporary file.
if(RemoveFile(tmpName) == FALSE) {
fprintf(stderr, "Temporary file could not be removed.\n");
return;
}
}
void usage(char *name) {
fprintf(stdout, "Hunt & Gather - %s, a file index generating tool. \n", name);
fprintf(stdout, "Version: %s \n", PROGRAM_VERSION);
fprintf(stdout, " \n");
fprintf(stdout, "SYNTAX: %s [-q] <-a|-r|-c> <PATH PATH PATH...> \n", name);
fprintf(stdout, " \n");
fprintf(stdout, "Required: \n");
fprintf(stdout, " -a [PATH...] Add files. \n");
fprintf(stdout, " -c [PATH...] Create from scratch. \n");
fprintf(stdout, " -r [PATH...] Remove files. \n");
fprintf(stdout, " \n");
fprintf(stdout, "Optional: \n");
fprintf(stdout, " -d [FIILE] Where to store the database. \n");
fprintf(stdout, " -m BYTES Memory to use (default: %d). \n", maxmem);
fprintf(stdout, " -q Do not print out any messages. \n");
fprintf(stdout, " \n");
fprintf(stdout, "DATABASE is a path to where the indexed results will be \n");
fprintf(stdout, "stored for searching with the Hunt tool. \n");
fprintf(stdout, " \n");
fprintf(stdout, "(c) 2021 Wizardry and Steamworks, MIT. \n");
}
/*
*
* Main entry point.
*/
int main(int argc, char **argv) {
int option;
int i;
char *dbFile;
char *path;
VECTOR *paths;
OPERATION operation = NONE;
// Bind handler to SIGINT.
#if !defined ___AmigaOS___
signal(SIGINT, SignalHandler);
#endif
dbFile = DEFAULT_DATABASE_FILE;
while((option = getopt(argc, argv, "hqdm:arc")) != -1) {
switch(option) {
case 'a':
operation = GATHER;
break;
case 'r':
operation = REMOVE;
break;
case 'c':
operation = CREATE;
break;
case 'm':
maxmem = strtoul(optarg, NULL, 10);
break;
case 'd':
dbFile = optarg;
break;
case 'q':
PROGRAM_VERBOSE = FALSE;
break;
case 'h':
usage(argv[0]);
return 0;
case '?':
fprintf(stderr, "Invalid option %ct.\n", optopt);
return 5;
}
}
if(operation == NONE) {
usage(argv[0]);
return 5;
}
if(optind >= argc) {
usage(argv[0]);
return 5;
}
// Build the path vector.
if((paths = malloc(1 * sizeof(*paths))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return 20;
}
// Go through all supplied arguments and add paths to search.
if((paths->array = malloc((argc - optind) * sizeof(*paths))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return 20;
}
for(i = optind, paths->length = 0; i < argc; ++i) {
if((path = PathToAbsolute(argv[i])) == NULL) {
fprintf(stderr, "Absolute path for '%s' failed to resolve.\n", argv[optind]);
continue;
}
switch(GetFsType(path)) {
case UNKNOWN:
case REGULAR:
fprintf(stderr, "Path '%s' is not a directory.\n", path);
free(path);
path = NULL;
continue;
case DIRECTORY:
break;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Will process path: '%s'\n", path);
}
// Add the path to the array of paths.
if((paths->array[paths->length] = malloc((strlen(path) + 1) * sizeof(*paths->array[paths->length]))) == NULL) {
fprintf(stderr, "Memory allocation failure.");
return 20;
}
sprintf(paths->array[paths->length], "%s", path);
++paths->length;
free(path);
path = NULL;
}
if(paths->length == 0) {
fprintf(stderr, "No valid paths are available.\n");
free(paths->array);
free(paths);
paths = NULL;
return 5;
}
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Gathering to: '%s'\n", dbFile);
}
#if defined ___AmigaOS___
locale = OpenLocale(NULL);
#endif
switch(operation) {
case CREATE:
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Removing '%s' and creating a new database.\n", dbFile);
}
RemoveFile(dbFile);
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Gathering files to database...\n");
}
CreateDatabaseFiles(dbFile, paths);
break;
case GATHER:
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Gathering files to database...\n");
}
GatherDatabaseFiles(dbFile, paths);
break;
case REMOVE:
if(PROGRAM_VERBOSE) {
fprintf(stdout, "Removing files from database...\n");
}
RemoveDatabaseFiles(dbFile, paths);
break;
default:
fprintf(stderr, "Unknown operation.\n");
#if defined ___AmigaOS___
CloseLocale(locale);
#endif
free(paths->array);
free(paths);
paths = NULL;
return 5;
}
#if defined ___AmigaOS___
CloseLocale(locale);
#endif
if(paths != NULL) {
free(paths->array);
free(paths);
paths = NULL;
}
return 0;
}