Kekatsu-DS/source/database.c

380 lines
8.8 KiB
C

#include "database.h"
#include "config.h"
#include "networking.h"
#include "utils/filesystem.h"
#include "utils/strings.h"
#include <stdlib.h>
#include <string.h>
#define TEMP_DB_FILENAME "db.txt"
#define TEMP_DB_PATH CACHE_DIR "/" TEMP_DB_FILENAME
#define LAST_OPENED_DB_FILENAME "lastOpenedDb.txt"
#define LAST_OPENED_DB_PATH APPDATA_DIR "/" LAST_OPENED_DB_FILENAME
#define DATABASE_LIST_FILENAME "databases.txt"
#define DATABASE_LIST_PATH APPDATA_DIR "/" DATABASE_LIST_FILENAME
struct Database {
char* name;
char* value;
DatabaseType type;
char* path;
size_t size;
bool isInited;
FILE* fp;
char delimiter;
};
Database newDatabase(const char* name, const char* value)
{
Database d = malloc(sizeof(struct Database));
d->name = strdup(name);
d->value = strdup(value);
d->path = NULL;
d->size = 0;
d->isInited = false;
d->fp = NULL;
d->delimiter = '\0';
if ((strncmp(value, "http://", 7) == 0) || (strncmp(value, "https://", 8) == 0))
d->type = DATABASE_TYPE_HTTP;
else
d->type = DATABASE_TYPE_LOCAL;
return d;
}
void freeDatabase(Database d)
{
free(d->value);
free(d->path);
if (d->fp)
closeDatabase(d);
free(d);
}
char* getDatabaseName(Database d)
{
return d->name;
}
char* getDatabaseValue(Database d)
{
return d->value;
}
char* getDatabasePath(Database d)
{
return d->path;
}
size_t getDatabaseSize(Database d)
{
return d->size;
}
bool getDatabaseIsInited(Database d)
{
return d->isInited;
}
char** parseLine(char* line, char delimiter, size_t* fieldsCount)
{
if (line[0] == '\0') {
*fieldsCount = 0;
return NULL;
}
// Count fields in line
*fieldsCount = 1;
for (char* ptr = line; *ptr != '\0'; ++ptr) {
if (*ptr == delimiter)
(*fieldsCount)++;
}
// Allocate space for fields array
char** fields = (char**)malloc(*fieldsCount * sizeof(char*));
if (fields == NULL)
return NULL;
// Populate fields array with pointers to strings in line
size_t fieldIndex = 0;
char* start = line;
for (char* ptr = line;; ++ptr) {
if (*ptr == delimiter || *ptr == '\0') {
fields[fieldIndex] = start;
fieldIndex++;
if (*ptr == '\0') {
break;
}
*ptr = '\0'; // Replace delimiter with null terminator
start = ptr + 1;
}
}
return fields;
}
Database getLastOpenedDatabase(void)
{
FILE* fp = fopen(LAST_OPENED_DB_PATH, "r");
if (!fp)
return NULL;
char line[2048];
char name[1024];
char value[1024];
if (!fgets(line, sizeof(line), fp)
|| (sscanf(line, "%[^\t]\t%s", name, value) != 2
&& sscanf(line, "%[^=]=%s", name, value) != 2)) {
fclose(fp);
return NULL;
}
fclose(fp);
return newDatabase(name, value);
}
void saveLastOpenedDatabase(Database d)
{
FILE* fp = fopen(LAST_OPENED_DB_PATH, "w");
if (!fp)
return;
fprintf(fp, "%s\t%s\n", d->name, d->value);
fclose(fp);
}
DatabaseInitStatus openDatabase(Database d)
{
FILE* fp = fopen(d->path, "r");
if (!fp)
return DATABASE_OPEN_ERR_FILE_OPEN;
size_t dbSize = 0;
char line[1024];
if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
int dbVersion = atoi(line);
if (dbVersion != 1) { // 1 is the only supported version for now
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_VERSION;
}
if (!fgets(line, sizeof(line), fp)) {
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
char delimiter = line[0];
while (fgets(line, sizeof(line), fp)) {
size_t fieldsCount;
char** fields = parseLine(line, delimiter, &fieldsCount);
if (!fields || fieldsCount < 9) {
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
// Calculate the fields count of items to extract
// Items are couple of fields so the count has to be an even number
size_t extractItemsFieldsCount = fieldsCount - 9;
if (fieldsCount > 9 && (extractItemsFieldsCount & 1) != 0) {
free(fields);
fclose(fp);
return DATABASE_OPEN_ERR_INVALID_FORMAT;
}
dbSize++;
}
d->size = dbSize;
d->fp = fp;
d->delimiter = delimiter;
saveLastOpenedDatabase(d);
return DATABASE_OPEN_SUCCESS;
}
bool closeDatabase(Database d)
{
if (fclose(d->fp) != 0)
return false;
return true;
}
DatabaseInitStatus initDatabase(Database d)
{
bool usingCachedDb = false;
switch (d->type) {
case DATABASE_TYPE_HTTP:
char* dbFileName = strdup(d->value);
safeStr(dbFileName);
char dbFilePath[PATH_MAX];
joinPath(dbFilePath, CACHE_DIR, dbFileName);
free(dbFileName);
d->path = strdup(dbFilePath);
if (downloadFile(TEMP_DB_PATH, d->value, NULL) == DOWNLOAD_SUCCESS) {
if (!renamePath(TEMP_DB_PATH, dbFilePath))
return DATABASE_INIT_ERR_DOWNLOAD;
} else {
if (fileExists(dbFilePath))
usingCachedDb = true;
else
return DATABASE_INIT_ERR_DOWNLOAD;
}
break;
case DATABASE_TYPE_LOCAL:
d->path = strdup(d->value);
if (!pathExists(d->path))
return DATABASE_INIT_ERR_FILE_NOT_FOUND;
break;
}
d->isInited = true;
return (usingCachedDb ? DATABASE_INIT_SUCCESS_CACHE : DATABASE_INIT_SUCCESS);
}
void alignDatabase(Database d)
{
fseek(d->fp, 0, SEEK_SET);
// Advance 2 lines
char ch;
u8 lines = 2;
while (lines > 0 && (ch = fgetc(d->fp)) != EOF) {
if (ch == '\n') {
lines--;
}
}
}
Entry* searchDatabase(Database d, const char* searchTitle, size_t pageSize, size_t page, size_t* resultsCount)
{
*resultsCount = 0;
size_t count = 0;
size_t capacity = 10;
size_t startIndex = (page - 1) * pageSize;
size_t endIndex = startIndex + pageSize;
size_t currIndex = 0;
char line[1024];
Entry* results = malloc(capacity * sizeof(Entry));
alignDatabase(d);
while (fgets(line, sizeof(line), d->fp)) {
// Trim trailing newline
line[strcspn(line, "\r\n")] = '\0';
size_t fieldsCount;
char** fields = parseLine(line, d->delimiter, &fieldsCount);
if (searchTitle[0] != '\0') {
// Format titles in a comparable way
char tempEntryTitle[128];
char tempInputTitle[128];
strncpy(tempEntryTitle, fields[0], sizeof(tempEntryTitle) - 1);
removeAccentsStr(tempEntryTitle);
lowerStr(tempEntryTitle);
strncpy(tempInputTitle, searchTitle, sizeof(tempInputTitle) - 1);
lowerStr(tempInputTitle);
if (!strstr(tempEntryTitle, tempInputTitle)) {
free(fields);
continue;
}
}
if (currIndex < startIndex || currIndex >= endIndex) {
currIndex++;
continue;
}
char** extractItemsFields = &fields[9]; // Items to extract start from the ninth field
size_t extractItemsCount = (fieldsCount - 9) / 2;
struct EntryExtractItem extractItems[extractItemsCount];
for (size_t i = 0; i < extractItemsCount; i++) {
extractItems[i].outPath = extractItemsFields[i * 2];
extractItems[i].inPath = extractItemsFields[i * 2 + 1];
}
Entry e = newEntry(fields[0], fields[1], fields[2], fields[3], fields[4], fields[5], fields[6], strtoull(fields[7], NULL, 10), fields[8], extractItems, extractItemsCount);
// Increase the capacity if needed
if (count >= capacity) {
capacity *= 2;
results = realloc(results, capacity * sizeof(Entry));
}
results[count] = e;
count++;
currIndex++;
free(fields);
if (currIndex >= endIndex)
break;
}
*resultsCount = count;
return results;
}
Database* getDatabaseList(size_t* databasesCount)
{
*databasesCount = 0;
FILE* fp = fopen(DATABASE_LIST_PATH, "r");
if (!fp)
return NULL;
size_t count = 0;
size_t capacity = 8;
Database* databases = malloc(capacity * sizeof(Database));
char line[2048];
char name[1024];
char value[1024];
while (fgets(line, sizeof(line), fp)) {
if (sscanf(line, "%[^\t]\t%s", name, value) != 2
&& sscanf(line, "%[^=]=%s", name, value) != 2)
continue;
if (count >= capacity) {
capacity *= 2;
databases = realloc(databases, capacity * sizeof(Database));
}
databases[count++] = newDatabase(name, value);
}
fclose(fp);
*databasesCount = count;
return databases;
}