HuntnGather – Rev 32
?pathlinks?
///////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Wizardry and Steamworks - License: MIT //
///////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <string.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>
#endif
#if defined ___AsyncIO___
#include <asyncio.h>
#endif
#if !defined ___HAVE_GETOPT___
#include "getopt.h"
#endif
#include "StringStack.h"
#define PROGRAM_VERSION "1.7.4"
#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
#if !defined TRUE
#define TRUE 1;
#endif
#if !defined FALSE
#define FALSE 0;
#endif
#define ASYNC_BUF 8192
#define MAX_MEM 262144
#define LINE_BUF 256
#define DEFAULT_DATABASE_FILE "S:gather.db"
typedef struct {
unsigned int dirs;
unsigned int files;
} stats;
typedef struct {
char *name;
char *path;
} dbEntry;
typedef struct {
char **database;
unsigned int count;
} dbArray;
enum MODE {
NONE,
GATHER,
REMOVE,
CREATE
} operation;
unsigned int run = TRUE;
unsigned int verbose = TRUE;
unsigned 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.
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
}
/*
*
* Gets the absolute path to file by name.
*/
char *PathToAbsolute(char *path) {
char *abs;
#if defined ___AmigaOS___
BPTR lock;
#endif
#if defined ___AmigaOS___
if((abs = malloc(PATH_MAX * sizeof(*abs))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return NULL;
}
if((lock = Lock(path, SHARED_LOCK)) == 0) {
fprintf(stderr, "Lock on %s failed.\n", path);
return NULL;
}
if(NameFromLock(lock, abs, PATH_MAX) == FALSE) {
fprintf(stderr, "Lock on %s failed.\n", path);
UnLock(lock);
return NULL;
}
UnLock(lock);
#else
//abs = realpath(path, NULL);
if((abs = malloc((strlen(path) + 1) * sizeof(*abs))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return NULL;
}
sprintf(abs, "%s", path);
#endif
return abs;
}
/*
*
* Compares path parts for equality.
*/
#if defined ___AmigaOS___
BOOL PathCompare(char *path, char *look) {
#else
int PathCompare(char *path, char *look) {
#endif
char *a;
char *b;
for(a = path, b = look; *a != '\0' && *b != '\0'; ++a, ++b) {
if(*b != '\0' && *a != *b) {
return FALSE;
}
}
return *b == '\0';
}
/*
*
* Gets the size of a file by name.
*/
int GetFileSize(char *dbFile) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
LONG size;
#else
FILE *fp;
int size;
#endif
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "r")) == NULL) {
#endif
fprintf(stderr, "Unable to open '%s' for reading.\n", dbFile);
return -1;
}
#if defined ___AsyncIO___
if(SeekAsync(fp, 0, MODE_END) == -1) {
#else
if(fseek(fp, 0L, SEEK_END) != 0) {
#endif
fprintf(stderr, "Seek in file %s failed.\n", dbFile);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return -1;
}
#if defined ___AsyncIO___
if((size = SeekAsync(fp, 0, MODE_CURRENT)) == -1) {
fprintf(stderr, "Seek in file %s failed.\n", dbFile);
CloseAsync(fp);
return -1;
}
#else
size = ftell(fp);
#endif
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return size;
}
/*
*
* Counts the lines of a file.
*/
int CountFileLines(char *dbFile) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
LONG c;
#else
FILE *fp;
char c;
#endif
int lines;
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "r")) == NULL) {
#endif
fprintf(stderr, "Unable to open '%s' for reading.\n", dbFile);
return -1;
}
lines = 0;
if(verbose) {
fprintf(stdout, "Lines in '%s' so far: %d\r", dbFile, lines);
}
#if defined ___AsyncIO___
while(run && (c = ReadCharAsync(fp)) != -1) {
#else
while(run && fscanf(fp, "%c", &c) == 1) {
#endif
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
switch(c) {
case '\n':
++lines;
if(verbose) {
fprintf(stdout, "Lines in '%s' so far: %d\r", dbFile, lines);
}
break;
}
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
if(verbose) {
fprintf(stdout, "\n");
}
return lines;
}
/*
*
* Creates a temporary file and returns its name.
*/
char *CreateTemporaryFile(void) {
char *name;
name = tmpnam(NULL);
return name;
}
/*
*
* Create multiple temporary files and return their names.
*/
char **CreateTemporaryFiles(int files) {
char **tmpNames;
int count;
if((tmpNames = malloc(files * sizeof(*tmpNames))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return NULL;
}
if(verbose) {
fprintf(stdout, "Creating temporary files...\r");
}
count = files;
while(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) {
run = FALSE;
continue;
}
#endif
tmpNames[count] = CreateTemporaryFile();
if(verbose) {
fprintf(stdout, "Creating temporary files: %d%%\r", 100 - (int)(((float)count / files) * 100.0));
}
}
if(verbose) {
fprintf(stdout, "\n");
}
return tmpNames;
}
/*
*
* Skips a line in a file.
*/
#if defined ___AsyncIO___
void SkipLine(struct AsyncFile *fp) {
LONG c;
while(run && (c = ReadCharAsync(fp)) != -1) {
#else
void SkipLine(FILE *fp) {
char c;
while(run && fscanf(fp, "%c", &c) == 1) {
#endif
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
switch(c) {
case '\n':
return;
}
}
}
/*
*
* Peeks at a line from a file.
*/
#if defined ___AsyncIO___
char *PeekLine(struct AsyncFile *fp) {
LONG c;
#else
char *PeekLine(FILE *fp) {
char c;
#endif
char *line = NULL;
char *real = NULL;
unsigned int size;
int i;
size = LINE_BUF;
if((line = malloc(size * sizeof(*line))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return NULL;
}
i = 0;
#if defined ___AsyncIO___
while(run && (c = ReadCharAsync(fp)) != -1) {
#else
while(run && fscanf(fp, "%c", &c) == 1) {
#endif
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
switch(c) {
case '\n':
// Rewind the file by the number of read characters.
#if defined ___AsyncIO___
if(SeekAsync(fp, -(i + 1), MODE_CURRENT) == -1) {
fprintf(stderr, "Could not seek in file.\n");
free(line);
return NULL;
}
#else
if(fseek(fp, -(i + 1), SEEK_CUR) != 0) {
fprintf(stderr, "Could not seek in file.\n");
free(line);
return NULL;
}
#endif
return line;
default:
if(strlen(line) == size) {
size = size * 1.5;
real = realloc(line, size * sizeof(*line));
if(real == NULL) {
fprintf(stderr, "Memory reallocation failure.\n");
free(line);
return NULL;
}
line = real;
}
line[i] = c;
line[i + 1] = '\0';
break;
}
++i;
}
if(line != NULL) {
free(line);
}
return NULL;
}
/*
*
* Read a line from a file.
*/
#if defined ___AsyncIO___
char *ReadLine(struct AsyncFile *fp) {
LONG c;
#else
char *ReadLine(FILE *fp) {
char c;
#endif
char *line = NULL;
char *real = NULL;
unsigned int size;
unsigned int i;
size = LINE_BUF;
if((line = malloc(size * sizeof(*line))) == NULL) {
fprintf(stderr, "Memory allication failure.\n");
return NULL;
}
i = 0;
#if defined ___AsyncIO___
while(run && (c = ReadCharAsync(fp)) != -1) {
#else
while(run && fscanf(fp, "%c", &c) == 1) {
#endif
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
switch(c) {
case '\n':
return line;
default:
if(strlen(line) == size) {
size = size * 1.5;
real = realloc(line, size * sizeof(*line));
if(real == NULL) {
fprintf(stderr, "Memory reallocation failure.\n");
free(line);
return NULL;
}
line = real;
}
line[i] = c;
line[i + 1] = '\0';
break;
}
++i;
}
if(line != NULL) {
free(line);
}
return NULL;
}
/*
*
* Delete a file.
*/
#if defined ___AmigaOS___
BOOL RemoveFile(char *name) {
return DeleteFile(name);
#else
int RemoveFile(char *name) {
return remove(name) == 0;
#endif
}
/*
*
* Deletes files.
*/
void RemoveFiles(char **names, int count) {
unsigned int i;
for(i = 0; i < count; ++i) {
if(RemoveFile(names[i]) == FALSE) {
fprintf(stderr, "Unable to remove %s...\n", names[i]);
continue;
}
fprintf(stderr, "Removing file: %s\n", names[i]);
}
}
/*
*
* Copies a file to another file by name.
*/
void CopyFile(char *a, char *b) {
#if defined ___AsyncIO___
struct AsyncFile *ap;
struct AsyncFile *bp;
LONG c;
#else
FILE *ap;
FILE *bp;
char c;
#endif
// Open database file for writing.
#if defined ___AsyncIO___
if((ap = OpenAsync(a, MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((ap = fopen(a, "r")) == NULL) {
#endif
fprintf(stderr, "Unable to open '%s' for reading.\n", a);
return;
}
// Open temporary file for reading.
#if defined ___AsyncIO___
if((bp = OpenAsync(b, MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
if((bp = fopen(b, "w")) == NULL) {
#endif
fprintf(stderr, "Unable to open file '%s' for writing.\n", b);
// Close database file.
#if defined ___AsyncIO___
CloseAsync(ap);
#else
fclose(ap);
#endif
return;
}
#if defined ___AsyncIO___
while(run && (c = ReadCharAsync(ap)) != -1) {
#else
while(run && fscanf(ap, "%c", &c) == 1) {
#endif
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
#if defined ___AsyncIO___
if(WriteCharAsync(bp, (UBYTE)c) != 1) {
#else
if(fprintf(bp, "%c", c) != 1) {
#endif
fprintf(stderr, "Unable to write to '%s'.\n", b);
break;
}
}
#if defined ___AsyncIO___
CloseAsync(ap);
CloseAsync(bp);
#else
fclose(ap);
fclose(bp);
#endif
}
/*
*
* Write lines to a file.
*/
void WriteLinesToFile(char *dbFile, char **lines, unsigned int count) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
#else
FILE *fp;
#endif
unsigned int i;
char *rem;
// 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, "Unable to open '%s' for writing.\n", dbFile);
return;
}
rem = NULL;
for(i = 0; 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) {
run = FALSE;
continue;
}
#endif
if(rem != NULL) {
#if defined ___AmigaOS___
if(StrnCmp(locale, lines[i], rem, -1, SC_ASCII) == 0) {
#else
if(strcmp(lines[i], rem) == 0) {
#endif
continue;
}
}
#if defined ___AsyncIO___
WriteAsync(fp, lines[i], (LONG)strlen(lines[i]));
WriteAsync(fp, "\n", 1);
#else
fprintf(fp, "%s\n", lines[i]);
#endif
if(rem != NULL) {
free(rem);
}
rem = malloc(strlen(lines[i]) + 1);
sprintf(rem, "%s", lines[i]);
}
if(rem != NULL) {
free(rem);
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
}
/*
*
* Create a database entry from a line of text.
*/
dbEntry* CreateDatabaseEntry(char *line) {
dbEntry *entry;
char *ptr;
unsigned int side;
unsigned int i;
unsigned int j;
if((entry = malloc(1 * sizeof(*entry))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return NULL;
}
if((entry->name = malloc((strlen(line) + 1) * sizeof(*entry->name))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return NULL;
}
if((entry->path = malloc((strlen(line) + 1) * sizeof(*entry->path))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return NULL;
}
for(ptr = line, side = 0, i = 0, j = 0; run && *ptr != '\0'; ++ptr) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
switch(*ptr) {
case '\t':
entry->name[i] = '\0';
++side;
break;
case '\n':
entry->path[j] = '\0';
return entry;
default:
switch(side) {
case 0:
entry->name[i++] = *ptr;
break;
case 1:
entry->path[j++] = *ptr;
break;
}
break;
}
}
return entry;
}
/*
*
*
*/
dbArray *GetDatabaseArray(char *dbFile) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
#else
FILE *fp;
#endif
dbArray *array;
char **real = NULL;
dbEntry *entry;
char *line = NULL;
unsigned int count;
// 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, "Unable to open '%s' for reading.\n", dbFile);
return NULL;
}
if((array = malloc(1 * sizeof(*array))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
count = 0;
while(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) {
run = FALSE;
continue;
}
#endif
if((entry = CreateDatabaseEntry(line)) == NULL) {
fprintf(stderr, "Unable to create database entry.\n");
free(line);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
// Load up the name and path into the database variable.
real = realloc(array->database, (count + 1) * sizeof(*array->database));
if(real == NULL) {
fprintf(stderr, "Memory reallocation failure.\n");
free(entry->name);
free(entry->path);
free(entry);
free(line);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
array->database = real;
if((array->database[count] = malloc((strlen(entry->name) + strlen(entry->path) + 1 + 1) * sizeof(*array->database[count]))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
free(entry->name);
free(entry->path);
free(entry);
free(line);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
sprintf(array->database[count], "%s\t%s", entry->name, entry->path);
++count;
// Free the database entry.
free(entry->name);
free(entry->path);
free(entry);
free(line);
}
if(line != NULL) {
free(line);
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
array->count = count;
return array;
}
/*
*
* Sorts a database file lexicographically.
*/
void SortDatabase(char *dbFile) {
dbArray *array;
int i;
if(verbose) {
fprintf(stdout, "Sorting '%s'...\n", dbFile);
}
// Retrieve the database as an array.
if((array = GetDatabaseArray(dbFile)) == NULL) {
fprintf(stderr, "Unable to read '%s' as a database file.\n", dbFile);
return;
}
// Sort the database.
qsort(array->database, array->count, sizeof(char *), QsortCompare);
// Write back the database to the database file.
WriteLinesToFile(dbFile, array->database, array->count);
// Deallocate all the lines.
for(i = 0; i < array->count; ++i) {
free(array->database[i]);
}
free(array);
}
/*
*
* Updates a database file "dbFile".
*/
stats *CollectFiles(char *dbFile, char **paths, unsigned int count) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
#else
FILE *fp;
#endif
#if defined ___AmigaOS___
struct FileInfoBlock *FIBp, *FIBq;
BPTR lockp, lockq;
#else
DIR *dir;
struct dirent *entry;
struct stat dirStat;
#endif
stringStack *stack;
stats *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, "Unable to open '%s' for writing.\n", dbFile);
return NULL;
}
if(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;
// Push the first path onto the stack.
stack = stringStackCreate(count);
for(i = 0; run && i < count; ++i) {
stringStackPush(stack, paths[i]);
}
while(run && !stringStackIsEmpty(stack)) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
if((path = stringStackPop(stack)) == NULL) {
break;
}
#if defined ___AmigaOS___
if((lockp = Lock(path, ACCESS_READ)) == NULL) {
fprintf(stderr, "Could not lock path '%s' for reading.\n", path);
free(path);
continue;
}
if((FIBp = AllocDosObject(DOS_FIB, NULL)) == NULL) {
fprintf(stderr, "Path '%s' info block allocation failure.\n", path);
UnLock(lockp);
free(path);
continue;
}
if(Examine(lockp, FIBp) == FALSE) {
fprintf(stderr, "Path '%s' could not be examined.\n", path);
FreeDosObject(DOS_FIB, FIBp);
UnLock(lockp);
free(path);
continue;
}
while(run && ExNext(lockp, FIBp)) {
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#else
if((dir = opendir(path)) == NULL) {
fprintf(stderr, "Unable to open '%s' for reading.\n", path);
free(path);
continue;
}
while(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(FIBp->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, FIBp);
UnLock(lockp);
#else
closedir(dir);
#endif
free(path);
stringStackDestroy(stack);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
#if defined ___AmigaOS___
sprintf(sub, "%s%s", path, FIBp->fib_FileName);
#else
sprintf(sub, "%s%s", path, entry->d_name);
#endif
break;
default:
#if defined ___AmigaOS___
if((sub = malloc(strlen(path) + strlen(FIBp->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, FIBp);
UnLock(lockp);
#else
closedir(dir);
#endif
free(path);
stringStackDestroy(stack);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return NULL;
}
#if defined ___AmigaOS___
sprintf(sub, "%s/%s", path, FIBp->fib_FileName);
#else
sprintf(sub, "%s/%s", path, entry->d_name);
#endif
break;
}
#if defined ___AmigaOS___
if((lockq = Lock(sub, ACCESS_READ)) == NULL) {
fprintf(stderr, "Could not lock path '%s' for reading.\n", sub);
free(sub);
continue;
}
if((FIBq = AllocDosObject(DOS_FIB, NULL)) == NULL) {
fprintf(stderr, "Path '%s' info block allocation failure.\n", sub);
UnLock(lockq);
free(sub);
continue;
}
if(Examine(lockq, FIBq) == FALSE) {
fprintf(stderr, "Path '%s' could not be examined.\n", sub);
FreeDosObject(DOS_FIB, FIBq);
UnLock(lockq);
free(sub);
continue;
}
if(FIBq->fib_DirEntryType > 0) {
#else
stat(sub, &dirStat);
if(S_ISDIR(dirStat.st_mode)) {
#endif
stringStackPush(stack, sub);
++stats->dirs;
if(verbose) {
fprintf(stdout,
"Gathered %d directories and %d files.\r",
stats->dirs,
stats->files);
}
#if defined ___AmigaOS___
FreeDosObject(DOS_FIB, FIBq);
UnLock(lockq);
#endif
free(sub);
continue;
}
#if defined ___AmigaOS___
FreeDosObject(DOS_FIB, FIBq);
UnLock(lockq);
#endif
++stats->files;
if(verbose) {
fprintf(stdout,
"Gathered %d directories and %d files.\r",
stats->dirs,
stats->files);
}
#if defined ___NOCASE_FS___
#if defined ___AmigaOS___
strupr(FIBp->fib_FileName);
#else
strupr(entry->d_name);
#endif
#endif
// Write to database file.
#if defined ___AsyncIO___
#if defined ___AmigaOS___
WriteAsync(fp, FIBp->fib_FileName, (LONG)strlen(FIBp->fib_FileName));
#else
WriteAsync(fp, entry->d_name, (LONG)strlen(entry->d_name));
#endif
WriteAsync(fp, "\t", 1);
WriteAsync(fp, sub, (LONG)strlen(sub));
WriteAsync(fp, "\n", 1);
#else
#if defined ___AmigaOS___
fprintf(fp, "%s\t%s\n", FIBp->fib_FileName, sub);
#else
fprintf(fp, "%s\t%s\n", entry->d_name, sub);
#endif
#endif
free(sub);
}
#if defined ___AmigaOS___
FreeDosObject(DOS_FIB, FIBp);
UnLock(lockp);
#else
closedir(dir);
#endif
free(path);
}
if(verbose) {
fprintf(stdout, "\n");
}
stringStackDestroy(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, char **tmpNames, int tmpFiles, int tmpLines, int total) {
#if defined ___AsyncIO___
struct AsyncFile *fp, *tp;
LONG c;
#else
FILE *fp, *tp;
char c;
#endif
int lines;
int write;
#if defined ___AsyncIO___
if((fp = OpenAsync(dbFile, MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((fp = fopen(dbFile, "r")) == NULL) {
#endif
fprintf(stderr, "Unable to open '%s' for reading.\n", dbFile);
return;
}
#if defined ___AsyncIO___
if((tp = OpenAsync(tmpNames[--tmpFiles], MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
if((tp = fopen(tmpNames[--tmpFiles], "w")) == NULL) {
#endif
fprintf(stderr, "Unable to open '%s' for writing.\n", tmpNames[tmpFiles]);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
if(verbose) {
fprintf(stdout, "Writing to temporary files...\r");
}
write = 0;
lines = 0;
#if defined ___AsyncIO___
while(run && (c = ReadCharAsync(fp)) != -1) {
#else
while(run && fscanf(fp, "%c", &c) == 1) {
#endif
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
switch(c) {
case '\n':
// Increment the total written lines.
++write;
if(verbose) {
fprintf(stdout, "Writing to temporary files: %d%%.\r", (int)(((float)write / total) * 100.0));
}
// Write the newline character back.
#if defined ___AsyncIO___
if(WriteCharAsync(tp, (UBYTE)c) != 1) {
#else
if(fprintf(tp, "%c", c) != 1) {
#endif
fprintf(stderr, "Unable to write to '%s'.\n", tmpNames[tmpFiles]);
#if defined ___AsyncIO___
CloseAsync(tp);
CloseAsync(fp);
#else
fclose(tp);
fclose(fp);
#endif
return;
}
// Switch to the next temporary file.
if(++lines >= tmpLines) {
// If there are no temporary files left then run till the end.
if(tmpFiles - 1 < 0) {
break;
}
// Close the previous temporary file and write to the next temporary file.
#if defined ___AsyncIO___
CloseAsync(tp);
if((tp = OpenAsync(tmpNames[--tmpFiles], MODE_WRITE, ASYNC_BUF)) == NULL) {
#else
fclose(tp);
if((tp = fopen(tmpNames[--tmpFiles], "w")) == NULL) {
#endif
fprintf(stderr, "Unable to open '%s' for writing.\n", tmpNames[tmpFiles]);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
lines = 0;
break;
}
break;
default:
#if defined ___AsyncIO___
if(WriteCharAsync(tp, (UBYTE)c) != 1) {
#else
if(fprintf(tp, "%c", c) != 1) {
#endif
fprintf(stderr, "Unable to write to '%s'.\n", tmpNames[tmpFiles]);
#if defined ___AsyncIO___
CloseAsync(tp);
CloseAsync(fp);
#else
fclose(tp);
fclose(fp);
#endif
return;
}
break;
}
}
if(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, char **tmpNames, int files, int lines) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
struct AsyncFile **tp;
#else
FILE *fp;
FILE **tp;
#endif
unsigned int i;
unsigned int j;
char *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, "Unable to open '%s' for writing.\n", dbFile);
return;
}
// Allocate as many file pointers as temporary files.
if((tp = malloc(files * 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 < files; ++i) {
#if defined ___AsyncIO___
if((tp[i] = OpenAsync(tmpNames[i], MODE_READ, ASYNC_BUF)) == NULL) {
#else
if((tp[i] = fopen(tmpNames[i], "r")) == NULL) {
#endif
fprintf(stderr, "Unable to open '%s' for reading.\n", tmpNames[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(verbose) {
fprintf(stdout, "Merging all files...\r");
}
rem = NULL;
count = lines;
j = 0;
while(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) {
run = FALSE;
continue;
}
#endif
// Find the smallest line in all temporary files.
if(verbose) {
fprintf(stdout, "Merging all files: %d%%.\r", 100 - (int)(((float)count / lines) * 100.0));
}
min = NULL;
for(i = 0; i < files; ++i) {
tmp = PeekLine(tp[i]);
if(tmp == NULL) {
continue;
}
#if defined ___AmigaOS___
if(min == NULL || StrnCmp(locale, tmp, min, -1, SC_ASCII) < 0) {
#else
if(min == NULL || strcmp(tmp, min) < 0) {
#endif
if(min != NULL) {
// Free previous instance.
free(min);
}
if((min = malloc((strlen(tmp) + 1) * sizeof(*min))) == NULL) {
fprintf(stderr, "Memory allication failure.\n");
free(tmp);
if(min != NULL) {
free(min);
}
if(rem != NULL) {
free(rem);
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
sprintf(min, "%s", tmp);
// Remember the index of the file where the smallest entry has been found.
j = i;
}
free(tmp);
}
// 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);
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);
}
if((rem = malloc((strlen(min) + 1) * sizeof(*rem))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
free(min);
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
sprintf(rem, "%s", min);
free(min);
}
}
if(rem != NULL) {
free(rem);
}
// Write out any remaining contents from the temporary files.
for(i = 0; run && i < files; ++i) {
#if defined ___AmigaOS___
// Check if CTRL+C was pressed and abort the program.
if(SetSignal(0L, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C) {
run = FALSE;
continue;
}
#endif
tmp = ReadLine(tp[i]);
if(tmp == NULL) {
continue;
}
#if defined ___AsyncIO___
WriteAsync(fp, tmp, (LONG)strlen(tmp));
WriteAsync(fp, "\n", 1);
#else
fprintf(fp, "%s\n", tmp);
#endif
free(tmp);
}
// Close all temporary files.
for(i = 0; i < files; ++i) {
#if defined ___AsyncIO___
CloseAsync(tp[i]);
#else
fclose(tp[i]);
#endif
}
if(verbose) {
fprintf(stdout, "\n");
}
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
}
/*
*
* Filter the paths inside the database with provided paths.
*/
void FilterDatabasePaths(char *dbFile, char *tmpName, char **paths, unsigned int count) {
#if defined ___AsyncIO___
struct AsyncFile *fp;
struct AsyncFile *tp;
#else
FILE *fp;
FILE *tp;
#endif
char *line;
unsigned int lines;
int i;
dbEntry *entry;
// 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, "Unable to open '%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, "Unable to open '%s' for writing.\n", tmpName);
// Close database file.
#if defined ___AsyncIO___
CloseAsync(fp);
#else
fclose(fp);
#endif
return;
}
if(verbose) {
fprintf(stdout, "Removing lines...\r");
}
lines = 0;
while(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) {
run = FALSE;
continue;
}
#endif
if((entry = CreateDatabaseEntry(line)) == NULL) {
fprintf(stderr, "Unable to create database entry.\n");
free(line);
continue;
}
for(i = 0; i < count; ++i) {
if(PathCompare(entry->path, paths[i]) == TRUE) {
++lines;
if(verbose) {
fprintf(stdout, "Removing lines: %d\r", lines);
}
continue;
}
#if defined ___AsyncIO___
WriteAsync(tp, line, (LONG)strlen(line));
WriteAsync(tp, "\n", 1);
#else
fprintf(tp, "%s\n", line);
#endif
break;
}
free(entry->name);
free(entry->path);
free(entry);
free(line);
}
if(verbose) {
fprintf(stdout, "\n");
}
#if defined ___AsyncIO___
CloseAsync(fp);
CloseAsync(tp);
#else
fclose(fp);
fclose(tp);
#endif
}
/*
*
* Indexes a "path" by creating a database "dbFile".
*/
void GatherDatabaseFiles(char *dbFile, char **paths, unsigned int count) {
stats *stats;
char **tmpNames;
int dbSize;
int dbLines;
int tmpFiles;
int tmpLines;
int i;
// Generate the database file from the supplied paths.
if((stats = CollectFiles(dbFile, paths, count)) == NULL) {
fprintf(stderr, "Collecting files failed.\n");
return;
}
free(stats);
// Compute the amount of temporary files needed.
dbSize = GetFileSize(dbFile);
if(dbSize == -1) {
fprintf(stderr, "File size for '%s' failed.\n", dbFile);
return;
}
tmpFiles = dbSize / maxmem;
/* In case no temporary files are required,
* just sort the database and terminate.
*/
if(tmpFiles <= 1) {
SortDatabase(dbFile);
return;
}
// Get the database metrics.
dbLines = CountFileLines(dbFile);
if(dbLines == -1) {
fprintf(stderr, "Counting lines of '%s' failed.\n", dbFile);
}
tmpLines = dbLines / 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, tmpFiles, tmpLines, dbLines);
// Sort the temporary files.
for(i = 0; i < tmpFiles; ++i) {
SortDatabase(tmpNames[i]);
}
// Merge all the temporary files to the database file.
MergeTemporaryFiles(dbFile, tmpNames, tmpFiles, dbLines);
// Remove all temporary files.
RemoveFiles(tmpNames, tmpFiles);
// Free temporary file names.
free(tmpNames);
}
void RemoveDatabaseFiles(char *dbFile, char **paths, unsigned int count) {
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, count);
// Overwrite the database file with the filtered paths.
CopyFile(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) {
#if defined ___AmigaOS___
struct FileInfoBlock *FIB;
BPTR lock;
#else
struct stat dirStat;
#endif
int option;
unsigned int i;
unsigned int count;
char *dbFile;
char *path;
char **paths;
// 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':
verbose = FALSE;
break;
case 'h':
usage(argv[0]);
return 0;
case '?':
fprintf(stderr, "Invalid option %ct.\n", optopt);
return 1;
}
}
if(operation == NONE) {
usage(argv[0]);
return 1;
}
if(optind >= argc) {
usage(argv[0]);
return 1;
}
// Go through all supplied arguments and add paths to search.
if((paths = malloc((argc - optind) * sizeof(*paths))) == NULL) {
fprintf(stderr, "Memory allocation failure.\n");
return 1;
}
count = 0;
for(i = optind, count = 0; i < argc; ++i) {
if((path = PathToAbsolute(argv[i])) == NULL) {
fprintf(stderr, "Absolute path for '%s' failed to resolve.\n", argv[optind]);
continue;
}
// Check that the path is a directory.
#if defined ___AmigaOS___
if((lock = Lock(path, ACCESS_READ)) == NULL) {
fprintf(stderr, "Path '%s' is not accessible.\n", path);
free(path);
continue;
}
if((FIB = AllocDosObject(DOS_FIB, NULL)) == NULL) {
fprintf(stderr, "Path '%s' file information block not accessible.\n", path);
UnLock(lock);
free(path);
continue;
}
if(Examine(lock, FIB) == FALSE) {
fprintf(stderr, "Path '%s' information unexaminable.\n", path);
UnLock(lock);
FreeDosObject(DOS_FIB, FIB);
free(path);
continue;
}
if(FIB->fib_DirEntryType < 0) {
#else
stat(path, &dirStat);
if(!S_ISDIR(dirStat.st_mode)) {
#endif
fprintf(stderr, "Path '%s' is not a directory.\n", argv[optind]);
#if defined ___AmigaOS___
UnLock(lock);
FreeDosObject(DOS_FIB, FIB);
#endif
free(path);
return 1;
}
if(verbose) {
fprintf(stdout, "Will process path: '%s'\n", path);
}
// Add the path to the array of paths.
if((paths[count] = malloc((strlen(path) + 1) * sizeof(*paths[count]))) == NULL) {
fprintf(stderr, "Memory allocation failure.");
return 1;
}
sprintf(paths[count], "%s", path);
++count;
#if defined ___AmigaOS___
UnLock(lock);
FreeDosObject(DOS_FIB, FIB);
#endif
free(path);
}
if(count == 0) {
fprintf(stderr, "No valid paths are available.\n");
free(paths);
return 1;
}
if(verbose) {
fprintf(stdout, "Gathering to: '%s'\n", dbFile);
}
#if defined ___AmigaOS___
locale = OpenLocale(NULL);
#endif
switch(operation) {
case CREATE:
if(verbose) {
fprintf(stdout, "Removing '%s' and creating a new database.\n", dbFile);
}
if(RemoveFile(dbFile) == FALSE) {
fprintf(stderr, "File '%s' could not be removed.\n", dbFile);
break;
}
case GATHER:
if(verbose) {
fprintf(stdout, "Gathering files to database...\n");
}
GatherDatabaseFiles(dbFile, paths, count);
break;
case REMOVE:
if(verbose) {
fprintf(stdout, "Removing files from database...\n");
}
RemoveDatabaseFiles(dbFile, paths, count);
break;
default:
fprintf(stderr, "Unknown operation.\n");
break;
}
#if defined ___AmigaOS___
CloseLocale(locale);
#endif
free(paths);
#if defined MWDEBUG
/* Generate a memory usage report */
MWReport("At end of main()", MWR_FULL);
#endif
return 0;
}