cinera: Lock database and config files

This commit aims to help Cinera instances coordinate themselves by
throwing an error upon being instructed to use a database or config
file currently in use by another instance.
This commit is contained in:
Matt Mascarenhas 2022-09-16 16:10:19 +01:00
parent 1cf703e346
commit f60cf3087f
2 changed files with 570 additions and 472 deletions

View File

@ -23,15 +23,22 @@ typedef struct
version CINERA_APP_VERSION = { version CINERA_APP_VERSION = {
.Major = 0, .Major = 0,
.Minor = 10, .Minor = 10,
.Patch = 12 .Patch = 13
}; };
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
#include <fcntl.h> // NOTE(matt): open()
#undef __USE_XOPEN2K8
#include <stdarg.h> // NOTE(matt): varargs #include <stdarg.h> // NOTE(matt): varargs
#define __USE_POSIX // NOTE(matt): fileno()
#include <stdio.h> // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror #include <stdio.h> // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror
#undef __USE_POSIX
#include <stdlib.h> // NOTE(matt): calloc, malloc, free #include <stdlib.h> // NOTE(matt): calloc, malloc, free
#include <getopt.h> // NOTE(matt): getopts #include <getopt.h> // NOTE(matt): getopts
#include <curl/curl.h> #include <curl/curl.h>
#include <time.h> #include <time.h>
#include <sys/file.h> // NOTE(matt): flock
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <dirent.h> #include <dirent.h>
@ -41,10 +48,6 @@ version CINERA_APP_VERSION = {
#include <sys/ioctl.h> // NOTE(matt): ioctl and TIOCGWINSZ #include <sys/ioctl.h> // NOTE(matt): ioctl and TIOCGWINSZ
#include <wordexp.h> #include <wordexp.h>
#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
#include <fcntl.h> // NOTE(matt): open()
#undef __USE_XOPEN2K8
#define __USE_XOPEN2K // NOTE(matt): readlink() #define __USE_XOPEN2K // NOTE(matt): readlink()
#include <unistd.h> // NOTE(matt): sleep() #include <unistd.h> // NOTE(matt): sleep()
#undef __USE_XOPEN2K #undef __USE_XOPEN2K
@ -67,6 +70,7 @@ version CINERA_APP_VERSION = {
typedef uint64_t bool; typedef uint64_t bool;
#define TRUE 1 #define TRUE 1
#define FALSE 0 #define FALSE 0
#define NA 0
#define SECONDS_PER_HOUR 3600 #define SECONDS_PER_HOUR 3600
#define SECONDS_PER_MINUTE 60 #define SECONDS_PER_MINUTE 60
@ -279,6 +283,7 @@ typedef enum
RC_ERROR_DIRECTORY, RC_ERROR_DIRECTORY,
RC_ERROR_FATAL, RC_ERROR_FATAL,
RC_ERROR_FILE, RC_ERROR_FILE,
RC_ERROR_FILE_LOCKED,
RC_ERROR_HMML, RC_ERROR_HMML,
RC_ERROR_MAX_REFS, RC_ERROR_MAX_REFS,
RC_ERROR_MEMORY, RC_ERROR_MEMORY,
@ -464,6 +469,7 @@ typedef struct
buffer Buffer; buffer Buffer;
char *Path; char *Path;
FILE *Handle; FILE *Handle;
bool Locking;
} file; } file;
typedef struct typedef struct
@ -1064,7 +1070,7 @@ ExtendString0(char **Dest, string Src)
} }
file file
InitFile(string *Directory, string *Filename, extension_id Extension) InitFile(string *Directory, string *Filename, extension_id Extension, bool Locking)
{ {
file Result = {}; file Result = {};
if(Directory) if(Directory)
@ -1079,6 +1085,36 @@ InitFile(string *Directory, string *Filename, extension_id Extension)
{ {
ExtendString0(&Result.Path, ExtensionStrings[Extension]); ExtendString0(&Result.Path, ExtensionStrings[Extension]);
} }
Result.Locking = Locking;
return Result;
}
void
CloseFile(file *F, bool CloseLocking)
{
if(F->Handle)
{
fclose(F->Handle);
F->Handle = 0;
if(F->Locking && !CloseLocking)
{
F->Handle = fopen(F->Path, "r");
flock(fileno(F->Handle), LOCK_EX | LOCK_NB);
}
}
}
bool
TryLock(file *F) // USAGE: File shall already be open, if possible
{
bool Result = TRUE;
if(F->Locking && F->Handle)
{
if(flock(fileno(F->Handle), LOCK_EX | LOCK_NB) == -1)
{
Result = FALSE;
}
}
return Result; return Result;
} }
@ -1088,7 +1124,9 @@ ReadFileIntoBuffer(file *F)
rc Result = RC_ERROR_FILE; rc Result = RC_ERROR_FILE;
if(F->Path) if(F->Path)
{ {
if((F->Handle = fopen(F->Path, "r"))) if(F->Handle || (F->Handle = fopen(F->Path, "r")))
{
if(TryLock(F))
{ {
fseek(F->Handle, 0, SEEK_END); fseek(F->Handle, 0, SEEK_END);
F->Buffer.Size = ftell(F->Handle); F->Buffer.Size = ftell(F->Handle);
@ -1096,13 +1134,13 @@ ReadFileIntoBuffer(file *F)
F->Buffer.Ptr = F->Buffer.Location; F->Buffer.Ptr = F->Buffer.Location;
fseek(F->Handle, 0, SEEK_SET); fseek(F->Handle, 0, SEEK_SET);
fread(F->Buffer.Location, F->Buffer.Size, 1, F->Handle); fread(F->Buffer.Location, F->Buffer.Size, 1, F->Handle);
fclose(F->Handle); CloseFile(F, FALSE);
F->Handle = 0;
Result = RC_SUCCESS; Result = RC_SUCCESS;
} }
else else
{ {
perror(F->Path); Result = RC_ERROR_FILE_LOCKED;
}
} }
} }
return Result; return Result;
@ -1914,16 +1952,6 @@ ConfigErrorUnset(config_identifier_id FieldID)
"Unset %s\n", ConfigIdentifiers[FieldID].String); "Unset %s\n", ConfigIdentifiers[FieldID].String);
} }
void
ConfigFileIncludeError(string *Filename, uint64_t LineNumber, string Path)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, S_WARNING, ED_CONFIG);
fprintf(stderr,
"Included file could not be opened (%s): ", strerror(errno));
PrintStringC(CS_MAGENTA_BOLD, Path);
fprintf(stderr, "\n");
}
void void
ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize) ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize)
{ {
@ -1933,6 +1961,27 @@ ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id Fi
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
void
ConfigErrorLockedConfigLocation(string *Filename, uint64_t LineNumber, string *Received)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, Filename ? S_WARNING : S_ERROR, ED_CONFIG);
fprintf(stderr, "%s file %s%.*s%s is in use by another Cinera instance\n", Filename ? "Included" : "Config", ColourStrings[CS_MAGENTA_BOLD], (int)Received->Length, Received->Base, ColourStrings[CS_END]);
}
void
ConfigErrorUnopenableConfigLocation(string *Filename, uint64_t LineNumber, string *Received)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, Filename ? S_WARNING : S_ERROR, ED_CONFIG);
fprintf(stderr, "%s file %s%.*s%s could not be opened: %s\n", Filename ? "Included" : "Config", ColourStrings[CS_MAGENTA_BOLD], (int)Received->Length, Received->Base, ColourStrings[CS_END], strerror(errno));
}
void
ConfigErrorLockedDBLocation(string *Filename, uint64_t LineNumber, string *Received)
{
ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_CONFIG);
fprintf(stderr, "File at db_location %s%.*s%s is in use by another Cinera instance\n", ColourStrings[CS_MAGENTA_BOLD], (int)Received->Length, Received->Base, ColourStrings[CS_END]);
}
void void
ConfigErrorInt(string *Filename, uint64_t LineNumber, severity Severity, char *Message, uint64_t Number) ConfigErrorInt(string *Filename, uint64_t LineNumber, severity Severity, char *Message, uint64_t Number)
{ {
@ -2740,6 +2789,7 @@ typedef struct
{ {
file File; file File;
file_signposted Metadata; file_signposted Metadata;
bool Ready;
db_header5 Header; db_header5 Header;
db_block_projects5 ProjectsBlock; db_block_projects5 ProjectsBlock;
@ -3024,17 +3074,23 @@ ClearTemplateMetadata(template *Template)
} }
void void
FreeFile(file *F) FreeFileBufferAndPath(file *F)
{ {
FreeBuffer(&F->Buffer); FreeBuffer(&F->Buffer);
Free(F->Path); Free(F->Path);
F->Handle = 0;
} }
void void
FreeSignpostedFile(file_signposted *F) FreeFile(file *F, bool CloseLocking)
{ {
FreeFile(&F->File); CloseFile(F, CloseLocking);
FreeFileBufferAndPath(F);
}
void
FreeSignpostedFile(file_signposted *F, bool CloseLocking)
{
FreeFile(&F->File, CloseLocking);
file_signposts Zero = {}; file_signposts Zero = {};
F->Signposts = Zero; F->Signposts = Zero;
} }
@ -3042,7 +3098,7 @@ FreeSignpostedFile(file_signposted *F)
void void
FreeTemplate(template *Template) FreeTemplate(template *Template)
{ {
FreeFile(&Template->File); FreeFile(&Template->File, NA);
ClearTemplateMetadata(Template); ClearTemplateMetadata(Template);
} }
@ -3152,8 +3208,6 @@ typedef enum
WT_CONFIG, WT_CONFIG,
} watch_type; } watch_type;
#include "cinera_config.c"
typedef struct typedef struct
{ {
watch_type Type; watch_type Type;
@ -3161,6 +3215,7 @@ typedef struct
extension_id Extension; extension_id Extension;
project *Project; project *Project;
asset *Asset; asset *Asset;
FILE *Handle;
} watch_file; } watch_file;
typedef struct typedef struct
@ -3178,6 +3233,8 @@ typedef struct
uint32_t DefaultEventsMask; uint32_t DefaultEventsMask;
} watch_handles; } watch_handles;
#include "cinera_config.c"
#define AFD 1 #define AFD 1
#define AFE 0 #define AFE 0
@ -4281,17 +4338,17 @@ InitTemplate(template *Template, string Location, template_type Type)
Template->Metadata.Type = Type; Template->Metadata.Type = Type;
if(Type == TEMPLATE_GLOBAL_SEARCH) if(Type == TEMPLATE_GLOBAL_SEARCH)
{ {
Template->File = InitFile(&Config->GlobalTemplatesDir, &Config->GlobalSearchTemplatePath, EXT_NULL); Template->File = InitFile(&Config->GlobalTemplatesDir, &Config->GlobalSearchTemplatePath, EXT_NULL, FALSE);
} }
else else
{ {
if(Location.Base[0] == '/') if(Location.Base[0] == '/')
{ {
Template->File = InitFile(0, &Location, EXT_NULL); Template->File = InitFile(0, &Location, EXT_NULL, FALSE);
} }
else else
{ {
Template->File = InitFile(Type == TEMPLATE_BESPOKE ? &CurrentProject->HMMLDir : &CurrentProject->TemplatesDir, &Location, EXT_NULL); Template->File = InitFile(Type == TEMPLATE_BESPOKE ? &CurrentProject->HMMLDir : &CurrentProject->TemplatesDir, &Location, EXT_NULL, FALSE);
} }
} }
fprintf(stderr, "%sPacking%s template: %s\n", ColourStrings[CS_ONGOING], ColourStrings[CS_END], Template->File.Path); fprintf(stderr, "%sPacking%s template: %s\n", ColourStrings[CS_ONGOING], ColourStrings[CS_END], Template->File.Path);
@ -4516,7 +4573,7 @@ ConstructAssetPath(file *AssetFile, string Filename, asset_type Type)
void void
CycleFile(file *File) CycleFile(file *File)
{ {
fclose(File->Handle); CloseFile(File, FALSE);
// TODO(matt): Rather than freeing the buffer, why not just realloc it to fit the new file? Couldn't that work easily? // TODO(matt): Rather than freeing the buffer, why not just realloc it to fit the new file? Couldn't that work easily?
// The reason we Free / Reread is to save us having to shuffle the buffer contents around, basically // The reason we Free / Reread is to save us having to shuffle the buffer contents around, basically
// If we switch the whole database over to use linked lists - the database being the only file we actually cycle // If we switch the whole database over to use linked lists - the database being the only file we actually cycle
@ -4541,7 +4598,7 @@ CycleSignpostedFile(file_signposted *File)
// NOTE(matt) This file_signposted struct is totally hardcoded to the Metadata file, but may suffice for now // NOTE(matt) This file_signposted struct is totally hardcoded to the Metadata file, but may suffice for now
// //
// TODO(matt): This is probably insufficient. We likely need to offset the pointers any time we add stuff to the database // TODO(matt): This is probably insufficient. We likely need to offset the pointers any time we add stuff to the database
fclose(File->File.Handle); CloseFile(&File->File, FALSE);
// TODO(matt): Rather than freeing the buffer, why not just realloc it to fit the new file? Couldn't that work easily? // TODO(matt): Rather than freeing the buffer, why not just realloc it to fit the new file? Couldn't that work easily?
// The reason we Free / Reread is to save us having to shuffle the buffer contents around, basically // The reason we Free / Reread is to save us having to shuffle the buffer contents around, basically
// If we switch the whole database over to use linked lists - the database being the only file we actually cycle // If we switch the whole database over to use linked lists - the database being the only file we actually cycle
@ -4834,9 +4891,7 @@ SnipeChecksumAndCloseFile(file *HTMLFile, db_asset *Asset, int LandmarksInFile,
HTMLFile->Handle = fopen(HTMLFile->Path, "w"); HTMLFile->Handle = fopen(HTMLFile->Path, "w");
fwrite(HTMLFile->Buffer.Location, HTMLFile->Buffer.Size, 1, HTMLFile->Handle); fwrite(HTMLFile->Buffer.Location, HTMLFile->Buffer.Size, 1, HTMLFile->Handle);
fclose(HTMLFile->Handle); FreeFile(HTMLFile, NA);
HTMLFile->Handle = 0;
FreeFile(HTMLFile);
} }
// TODO(matt): Bounds-check the Metadata.File.Buffer // TODO(matt): Bounds-check the Metadata.File.Buffer
@ -5136,13 +5191,25 @@ AccumulateFileEditSize(file_signposted *File, int64_t Bytes)
File->Signposts.Edit.Size += Bytes; File->Signposts.Edit.Size += Bytes;
} }
FILE *
OpenFileForWriting(file *F)
{
if(F->Locking && F->Handle)
{
fclose(F->Handle);
}
F->Handle = fopen(F->Path, "w");
TryLock(F);
return F->Handle;
}
// TODO(matt): Consider enforcing an order of these blocks, basically putting the easy-to-skip ones first... // TODO(matt): Consider enforcing an order of these blocks, basically putting the easy-to-skip ones first...
void * void *
InitBlock(block_id ID) InitBlock(block_id ID)
{ {
db_header *Header = (db_header *)DB.Metadata.File.Buffer.Location; db_header *Header = (db_header *)DB.Metadata.File.Buffer.Location;
++Header->BlockCount; ++Header->BlockCount;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle); fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata); SetFileEditPosition(&DB.Metadata);
@ -5419,9 +5486,10 @@ IsSymlink(char *Filepath)
return Result; return Result;
} }
void watch_file *
PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extension, watch_type Type, project *Project, asset *Asset) PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extension, watch_type Type, project *Project, asset *Asset)
{ {
watch_file *Result = 0;
bool Required = TRUE; bool Required = TRUE;
for(int i = 0; i < Handle->Files.ItemCount; ++i) for(int i = 0; i < Handle->Files.ItemCount; ++i)
{ {
@ -5432,14 +5500,16 @@ PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extens
{ {
if(This->Extension == Extension) if(This->Extension == Extension)
{ {
Result = This;
Required = FALSE; Required = FALSE;
break; break;
} }
} }
else if(StringsMatch(This->Path, Filepath)) else if(StringsMatch(This->Path, Filepath))
{ {
Required = FALSE; Result = This;
EraseCurrentStringFromBook(&WatchHandles.Paths); EraseCurrentStringFromBook(&WatchHandles.Paths);
Required = FALSE;
break; break;
} }
} }
@ -5459,7 +5529,9 @@ PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extens
New->Type = Type; New->Type = Type;
New->Project = Project; New->Project = Project;
New->Asset = Asset; New->Asset = Asset;
Result = New;
} }
return Result;
} }
bool bool
@ -5535,9 +5607,10 @@ GetNearestExistingPath(string Path)
return Result; return Result;
} }
void watch_file *
PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset) PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset)
{ {
watch_file *Result = 0;
// NOTE(matt): This function influences RemoveAndFreeAllButFirstWatchFile(). If we change to write different strings into // NOTE(matt): This function influences RemoveAndFreeAllButFirstWatchFile(). If we change to write different strings into
// the W->Paths memory_book, we must reflect that change over to RemoveAndFreeAllButFirstWatchFile() // the W->Paths memory_book, we must reflect that change over to RemoveAndFreeAllButFirstWatchFile()
@ -5675,7 +5748,8 @@ PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *P
//PrintWatchHandles(); //PrintWatchHandles();
} }
PushWatchFileUniquely(Watch, Filename, Extension, Type, Project, Asset); Result = PushWatchFileUniquely(Watch, Filename, Extension, Type, Project, Asset);
return Result;
} }
bool bool
@ -5795,6 +5869,17 @@ UpdateNeighbourhoodPointers(neighbourhood *N, file_signposts *S)
N->Next = S->Next.Ptr; N->Next = S->Next.Ptr;
} }
void
WriteEntireDatabase(neighbourhood *N)
{
// NOTE(matt): This may suffice when the only changes are to existing fixed-size variables,
// e.g. ProjectsBlock->GlobalSearchDir
OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
}
int int
UpdateAssetInDB(asset *Asset) UpdateAssetInDB(asset *Asset)
{ {
@ -5854,7 +5939,7 @@ UpdateAssetInDB(asset *Asset)
FreeString(&Message); FreeString(&Message);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle); fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata); SetFileEditPosition(&DB.Metadata);
@ -5865,7 +5950,7 @@ UpdateAssetInDB(asset *Asset)
else else
{ {
// Append new asset, not bothering to insertion sort because there likely won't be many // Append new asset, not bothering to insertion sort because there likely won't be many
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
char *InsertionPoint = LocateEndOfAssetsBlock(AssetsBlock); char *InsertionPoint = LocateEndOfAssetsBlock(AssetsBlock);
AssetIndexInDB = AssetsBlock->Count; AssetIndexInDB = AssetsBlock->Count;
@ -5994,7 +6079,7 @@ UpdateAsset(asset *Asset, bool Defer)
{ {
AssetIndexInDB = UpdateAssetInDB(Asset); AssetIndexInDB = UpdateAssetInDB(Asset);
} }
FreeFile(&File); FreeFile(&File, NA);
return AssetIndexInDB; return AssetIndexInDB;
} }
@ -6068,7 +6153,7 @@ PlaceAsset(string Filename, asset_type Type, uint64_t Variants, bool Associated,
printf("%sNonexistent%s %s asset: %s\n", ColourStrings[CS_WARNING], ColourStrings[CS_END], AssetTypeNames[Type], File.Path); printf("%sNonexistent%s %s asset: %s\n", ColourStrings[CS_WARNING], ColourStrings[CS_END], AssetTypeNames[Type], File.Path);
} }
FreeFile(&File); FreeFile(&File, NA);
return This; return This;
} }
@ -7601,7 +7686,7 @@ GenerateTopicColours(neighbourhood *N, string Topic)
printf(" Freed Topics (%ld)\n", Topics.Buffer.Size); printf(" Freed Topics (%ld)\n", Topics.Buffer.Size);
#endif #endif
fclose(Topics.Handle); CloseFile(&Topics, NA);
asset *Asset = GetPlaceInBook(&Assets, ASSET_CSS_TOPICS); asset *Asset = GetPlaceInBook(&Assets, ASSET_CSS_TOPICS);
if(Asset->Known) if(Asset->Known)
@ -7623,7 +7708,7 @@ GenerateTopicColours(neighbourhood *N, string Topic)
} }
} }
} }
FreeFile(&Topics); FreeFile(&Topics, NA);
} }
return Result; return Result;
} }
@ -9115,7 +9200,7 @@ void
ExamineDB(void) ExamineDB(void)
{ {
DB.Metadata.File.Buffer.ID = BID_DATABASE; DB.Metadata.File.Buffer.ID = BID_DATABASE;
DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL); DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL, TRUE);
ReadFileIntoBuffer(&DB.Metadata.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail? ReadFileIntoBuffer(&DB.Metadata.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail?
if(DB.Metadata.File.Buffer.Location) if(DB.Metadata.File.Buffer.Location)
@ -9146,7 +9231,7 @@ ExamineDB(void)
{ {
fprintf(stderr, "Unable to open database file %s: %s\n", DB.Metadata.File.Path, strerror(errno)); fprintf(stderr, "Unable to open database file %s: %s\n", DB.Metadata.File.Path, strerror(errno));
} }
FreeFile(&DB.Metadata.File); FreeFile(&DB.Metadata.File, TRUE);
} }
#define HMMLCleanup() \ #define HMMLCleanup() \
@ -9323,7 +9408,7 @@ DeleteSearchPageFromFilesystem(string BaseDir, string SearchLocation, string Pro
remove(DB.File.Path); remove(DB.File.Path);
// TODO(matt): Consider the correctness of this. // TODO(matt): Consider the correctness of this.
FreeFile(&DB.File); FreeFile(&DB.File, NA);
char *SearchDirectory = ConstructDirectoryPath(&BaseDir, &SearchLocation, 0); char *SearchDirectory = ConstructDirectoryPath(&BaseDir, &SearchLocation, 0);
remove(SearchDirectory); remove(SearchDirectory);
@ -11866,7 +11951,7 @@ InitIndexFile(project *P)
DB.File.Handle = fopen(DB.File.Path, "w"); DB.File.Handle = fopen(DB.File.Path, "w");
fprintf(DB.File.Handle, "---\n"); fprintf(DB.File.Handle, "---\n");
fclose(DB.File.Handle); CloseFile(&DB.File, NA);
ReadFileIntoBuffer(&DB.File); ReadFileIntoBuffer(&DB.File);
} }
} }
@ -12243,10 +12328,7 @@ RenumberEntries(neighbourhood *N, db_header_project *P, int64_t IndexOfFirstEntr
Cursor += fwrite(Cursor, 1, DB.File.Buffer.Size - (Cursor - DB.File.Buffer.Location), DB.File.Handle); Cursor += fwrite(Cursor, 1, DB.File.Buffer.Size - (Cursor - DB.File.Buffer.Location), DB.File.Handle);
CycleFile(&DB.File); CycleFile(&DB.File);
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { return; } WriteEntireDatabase(N);
fwrite(DB.Metadata.File.Buffer.Location, 1, DB.Metadata.File.Buffer.Size, DB.Metadata.File.Handle);
CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
} }
db_entry * db_entry *
@ -12328,7 +12410,7 @@ InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTempl
fwrite(DB.File.Buffer.Location, EntryInsertionStart, 1, DB.File.Handle); fwrite(DB.File.Buffer.Location, EntryInsertionStart, 1, DB.File.Handle);
fwrite(CollationBuffers->SearchEntry.Location, N->This->Size, 1, DB.File.Handle); fwrite(CollationBuffers->SearchEntry.Location, N->This->Size, 1, DB.File.Handle);
fwrite(DB.File.Buffer.Location + EntryInsertionEnd, DB.File.Buffer.Size - EntryInsertionEnd, 1, DB.File.Handle); fwrite(DB.File.Buffer.Location + EntryInsertionEnd, DB.File.Buffer.Size - EntryInsertionEnd, 1, DB.File.Handle);
fclose(DB.File.Handle); CloseFile(&DB.File, NA);
if(N->This->Size == EntryInsertionEnd - EntryInsertionStart) if(N->This->Size == EntryInsertionEnd - EntryInsertionStart)
{ {
@ -12350,7 +12432,7 @@ InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTempl
Ptr += sizeof(*N->Project) + sizeof(db_entry) * N->ThisIndex; Ptr += sizeof(*N->Project) + sizeof(db_entry) * N->ThisIndex;
uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location; uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location;
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { return 0; } if(!(OpenFileForWriting(&DB.Metadata.File))) { return 0; }
fwrite(DB.Metadata.File.Buffer.Location, BytesIntoFile, 1, DB.Metadata.File.Handle); fwrite(DB.Metadata.File.Buffer.Location, BytesIntoFile, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata); SetFileEditPosition(&DB.Metadata);
@ -12401,7 +12483,7 @@ WritePastAssetsHeader(void)
DB.Metadata.File.Buffer.Ptr = DB.Metadata.Signposts.AssetsBlock.Ptr; DB.Metadata.File.Buffer.Ptr = DB.Metadata.Signposts.AssetsBlock.Ptr;
DB.Metadata.File.Buffer.Ptr += sizeof(db_block_assets); DB.Metadata.File.Buffer.Ptr += sizeof(db_block_assets);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Ptr - DB.Metadata.File.Buffer.Location, 1, DB.Metadata.File.Handle); fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Ptr - DB.Metadata.File.Buffer.Location, 1, DB.Metadata.File.Handle);
} }
@ -13197,7 +13279,7 @@ InsertNeighbourLink(db_header_project *P, db_entry *From, db_entry *To, enum8(li
if(To) { DeclaimBuffer(&ToPlayerURL); } if(To) { DeclaimBuffer(&ToPlayerURL); }
DeclaimBuffer(&Link); DeclaimBuffer(&Link);
fclose(HTML.Handle); CloseFile(&HTML, NA);
HTML.Handle = 0; HTML.Handle = 0;
} }
} }
@ -13205,7 +13287,7 @@ InsertNeighbourLink(db_header_project *P, db_entry *From, db_entry *To, enum8(li
{ {
Result = RC_ERROR_FILE; Result = RC_ERROR_FILE;
} }
FreeFile(&HTML); FreeFile(&HTML, NA);
MEM_TEST_END("InsertNeighbourLink()"); MEM_TEST_END("InsertNeighbourLink()");
return Result; return Result;
} }
@ -13236,14 +13318,14 @@ DeleteNeighbourLinks(neighbourhood *N)
ReadPlayerPageIntoBuffer(&HTML, Wrap0i(N->Project->BaseDir), Wrap0i(N->Project->PlayerLocation), Wrap0i(Entry->OutputLocation)); ReadPlayerPageIntoBuffer(&HTML, Wrap0i(N->Project->BaseDir), Wrap0i(N->Project->PlayerLocation), Wrap0i(Entry->OutputLocation));
if(HTML.Buffer.Location) if(HTML.Buffer.Location)
{ {
if(!(HTML.Handle = fopen(HTML.Path, "w"))) { FreeFile(&HTML); return RC_ERROR_FILE; }; if(!(HTML.Handle = fopen(HTML.Path, "w"))) { FreeFile(&HTML, NA); return RC_ERROR_FILE; };
fwrite(HTML.Buffer.Location, Entry->LinkOffsets.PrevStart, 1, HTML.Handle); fwrite(HTML.Buffer.Location, Entry->LinkOffsets.PrevStart, 1, HTML.Handle);
fwrite(HTML.Buffer.Location + Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd, Entry->LinkOffsets.NextStart, 1, HTML.Handle); fwrite(HTML.Buffer.Location + Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd, Entry->LinkOffsets.NextStart, 1, HTML.Handle);
fwrite(HTML.Buffer.Location + Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd + Entry->LinkOffsets.NextStart + Entry->LinkOffsets.NextEnd, fwrite(HTML.Buffer.Location + Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd + Entry->LinkOffsets.NextStart + Entry->LinkOffsets.NextEnd,
HTML.Buffer.Size - (Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd + Entry->LinkOffsets.NextStart + Entry->LinkOffsets.NextEnd), HTML.Buffer.Size - (Entry->LinkOffsets.PrevStart + Entry->LinkOffsets.PrevEnd + Entry->LinkOffsets.NextStart + Entry->LinkOffsets.NextEnd),
1, 1,
HTML.Handle); HTML.Handle);
fclose(HTML.Handle); CloseFile(&HTML, NA);
Entry->LinkOffsets.PrevEnd = 0; Entry->LinkOffsets.PrevEnd = 0;
Entry->LinkOffsets.NextEnd = 0; Entry->LinkOffsets.NextEnd = 0;
if(N->Prev && N->PrevIndex >= 0) if(N->Prev && N->PrevIndex >= 0)
@ -13262,7 +13344,7 @@ DeleteNeighbourLinks(neighbourhood *N)
} }
} }
FreeFile(&HTML); FreeFile(&HTML, NA);
return RC_SUCCESS; return RC_SUCCESS;
} }
@ -13331,7 +13413,7 @@ MarkNextAsFirst(neighbourhood *N)
N->Next->LinkOffsets.PrevEnd = Link.Ptr - Link.Location; N->Next->LinkOffsets.PrevEnd = Link.Ptr - Link.Location;
DeclaimBuffer(&Link); DeclaimBuffer(&Link);
fclose(HTML.Handle); CloseFile(&HTML, NA);
} }
else else
{ {
@ -13339,7 +13421,7 @@ MarkNextAsFirst(neighbourhood *N)
N->Next->LinkOffsets = Blank; N->Next->LinkOffsets = Blank;
} }
FreeFile(&HTML); FreeFile(&HTML, NA);
} }
void void
@ -13369,7 +13451,7 @@ MarkPrevAsFinal(neighbourhood *N)
N->Prev->LinkOffsets.NextEnd = Link.Ptr - Link.Location; N->Prev->LinkOffsets.NextEnd = Link.Ptr - Link.Location;
DeclaimBuffer(&Link); DeclaimBuffer(&Link);
fclose(HTML.Handle); CloseFile(&HTML, NA);
} }
else else
{ {
@ -13377,7 +13459,7 @@ MarkPrevAsFinal(neighbourhood *N)
N->Prev->LinkOffsets = Blank; N->Prev->LinkOffsets = Blank;
} }
FreeFile(&HTML); FreeFile(&HTML, NA);
} }
void void
@ -13534,7 +13616,7 @@ DeleteFromDB(neighbourhood *N, string BaseFilename, db_entry *Deceased)
} }
N->Project->EntryCount = NewEntryCount; N->Project->EntryCount = NewEntryCount;
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; } if(!(OpenFileForWriting(&DB.Metadata.File))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; }
char *Ptr = (char *)N->This; char *Ptr = (char *)N->This;
uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location; uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location;
@ -14560,7 +14642,7 @@ SetCurrentProject(project *P, neighbourhood *N)
{ {
if(CurrentProject != P) if(CurrentProject != P)
{ {
FreeFile(&DB.File); FreeFile(&DB.File, NA);
if(CurrentProject) if(CurrentProject)
{ {
@ -14608,7 +14690,6 @@ RecheckPrivacyRecursively(project *P, neighbourhood *N, buffers *CollationBuffer
DeleteStaleAssets(); DeleteStaleAssets();
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
} }
} }
for(int i = 0; i < P->Child.ItemCount; ++i) for(int i = 0; i < P->Child.ItemCount; ++i)
@ -14723,7 +14804,7 @@ UpgradeDB(int OriginalDBVersion)
ClearCopyStringNoFormat(DB.AssetsBlock.JSDir, sizeof(DB.AssetsBlock.JSDir), Config->JSDir); ClearCopyStringNoFormat(DB.AssetsBlock.JSDir, sizeof(DB.AssetsBlock.JSDir), Config->JSDir);
++DB.Header.BlockCount; ++DB.Header.BlockCount;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
fwrite(&DB.Header, sizeof(DB.Header), 1, DB.Metadata.File.Handle); fwrite(&DB.Header, sizeof(DB.Header), 1, DB.Metadata.File.Handle);
DB.Metadata.Signposts.ProjectsBlock.Byte = ftell(DB.Metadata.File.Handle); DB.Metadata.Signposts.ProjectsBlock.Byte = ftell(DB.Metadata.File.Handle);
@ -14829,7 +14910,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
Free(NewSearchDirectory); Free(NewSearchDirectory);
FreeFile(&DB.File); FreeFile(&DB.File, NA);
DB.File.Path = ConstructIndexFilePath(CurrentProject->BaseDir, CurrentProject->SearchLocation, CurrentProject->ID); DB.File.Path = ConstructIndexFilePath(CurrentProject->BaseDir, CurrentProject->SearchLocation, CurrentProject->ID);
ReadFileIntoBuffer(&DB.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail? ReadFileIntoBuffer(&DB.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail?
@ -14847,7 +14928,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
if(StringsDiffer(CurrentProject->BaseDir, Wrap0i(N->Project->BaseDir))) if(StringsDiffer(CurrentProject->BaseDir, Wrap0i(N->Project->BaseDir)))
{ {
ClearCopyStringNoFormat(N->Project->BaseDir, sizeof(N->Project->BaseDir), CurrentProject->BaseDir); ClearCopyStringNoFormat(N->Project->BaseDir, sizeof(N->Project->BaseDir), CurrentProject->BaseDir);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0); WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata); CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14857,7 +14938,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
if(StringsDiffer(CurrentProject->BaseURL, Wrap0i(N->Project->BaseURL))) if(StringsDiffer(CurrentProject->BaseURL, Wrap0i(N->Project->BaseURL)))
{ {
ClearCopyStringNoFormat(N->Project->BaseURL, sizeof(N->Project->BaseURL), CurrentProject->BaseURL); ClearCopyStringNoFormat(N->Project->BaseURL, sizeof(N->Project->BaseURL), CurrentProject->BaseURL);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0); WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata); CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14866,7 +14947,7 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified)
if(HasNewPlayerLocation || HasNewSearchLocation) if(HasNewPlayerLocation || HasNewSearchLocation)
{ {
if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; } if(!(OpenFileForWriting(&DB.Metadata.File))) { FreeBuffer(&DB.Metadata.File.Buffer); return RC_ERROR_FILE; }
ClearCopyStringNoFormat(N->Project->BaseDir, sizeof(N->Project->BaseDir), CurrentProject->BaseDir); ClearCopyStringNoFormat(N->Project->BaseDir, sizeof(N->Project->BaseDir), CurrentProject->BaseDir);
ClearCopyStringNoFormat(N->Project->BaseURL, sizeof(N->Project->BaseURL), CurrentProject->BaseURL); ClearCopyStringNoFormat(N->Project->BaseURL, sizeof(N->Project->BaseURL), CurrentProject->BaseURL);
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle); fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
@ -14942,7 +15023,7 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe
if(StringsDiffer(CurrentProject->Title, Wrap0i(N->Project->Title))) if(StringsDiffer(CurrentProject->Title, Wrap0i(N->Project->Title)))
{ {
ClearCopyStringNoFormat(N->Project->Title, sizeof(N->Project->Title), CurrentProject->Title); ClearCopyStringNoFormat(N->Project->Title, sizeof(N->Project->Title), CurrentProject->Title);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0); WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata); CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14952,7 +15033,7 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe
if(StringsDiffer(CurrentProject->Theme, Wrap0i(N->Project->Theme))) if(StringsDiffer(CurrentProject->Theme, Wrap0i(N->Project->Theme)))
{ {
ClearCopyStringNoFormat(N->Project->Theme, sizeof(N->Project->Theme), CurrentProject->Theme); ClearCopyStringNoFormat(N->Project->Theme, sizeof(N->Project->Theme), CurrentProject->Theme);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0); WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata); CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -14962,7 +15043,7 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe
if(StringsDiffer(CurrentProject->Numbering.Unit, Wrap0i(N->Project->Unit))) if(StringsDiffer(CurrentProject->Numbering.Unit, Wrap0i(N->Project->Unit)))
{ {
ClearCopyStringNoFormat(N->Project->Unit, sizeof(N->Project->Unit), CurrentProject->Numbering.Unit); ClearCopyStringNoFormat(N->Project->Unit, sizeof(N->Project->Unit), CurrentProject->Numbering.Unit);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToEnd(&DB.Metadata.File, 0); WriteFromByteToEnd(&DB.Metadata.File, 0);
CycleSignpostedFile(&DB.Metadata); CycleSignpostedFile(&DB.Metadata);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
@ -15082,16 +15163,6 @@ InitAccumulator(project_generations *G)
return Result; return Result;
} }
void
WriteEntireDatabase()
{
// NOTE(matt): This may suffice when the only changes are to existing fixed-size variables,
// e.g. ProjectsBlock->GlobalSearchDir
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
fwrite(DB.Metadata.File.Buffer.Location, DB.Metadata.File.Buffer.Size, 1, DB.Metadata.File.Handle);
fclose(DB.Metadata.File.Handle);
}
rc rc
InitDB(void) InitDB(void)
{ {
@ -15101,9 +15172,15 @@ InitDB(void)
// need a separate InitIndex() function that we can call when looping over the projects after ParseConfig() // need a separate InitIndex() function that we can call when looping over the projects after ParseConfig()
DB.Metadata.File.Buffer.ID = BID_DATABASE; DB.Metadata.File.Buffer.ID = BID_DATABASE;
DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL); DB.Metadata.File = InitFile(0, &Config->DatabaseLocation, EXT_NULL, TRUE);
ReadFileIntoBuffer(&DB.Metadata.File); // NOTE(matt): Could we actually catch errors (permissions?) here and bail? if(ReadFileIntoBuffer(&DB.Metadata.File) == RC_ERROR_FILE_LOCKED) // NOTE(matt): Could we actually catch errors (permissions?) here and bail?
{
ConfigErrorLockedDBLocation(0, 0, &Config->DatabaseLocation);
CloseFile(&DB.Metadata.File, TRUE);
Result = RC_ERROR_FILE_LOCKED;
}
else
{
if(DB.Metadata.File.Buffer.Location) if(DB.Metadata.File.Buffer.Location)
{ {
// TODO(matt): Handle this gracefully (it'll be an invalid file) // TODO(matt): Handle this gracefully (it'll be an invalid file)
@ -15169,12 +15246,12 @@ InitDB(void)
} }
} }
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); if(OpenFileForWriting(&DB.Metadata.File))
if(DB.Metadata.File.Handle)
{ {
fwrite(B->Location, B->Size, 1, DB.Metadata.File.Handle); fwrite(B->Location, B->Size, 1, DB.Metadata.File.Handle);
SetFileEditPosition(&DB.Metadata); SetFileEditPosition(&DB.Metadata);
CycleSignpostedFile(&DB.Metadata); CycleSignpostedFile(&DB.Metadata);
DB.Ready = TRUE;
} }
else else
{ {
@ -15233,9 +15310,7 @@ InitDB(void)
if(Result == RC_SUCCESS) if(Result == RC_SUCCESS)
{ {
if(OpenFileForWriting(&DB.Metadata.File))
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w");
if(DB.Metadata.File.Handle)
{ {
fwrite(&DB.Header, sizeof(DB.Header), 1, DB.Metadata.File.Handle); fwrite(&DB.Header, sizeof(DB.Header), 1, DB.Metadata.File.Handle);
@ -15245,10 +15320,11 @@ InitDB(void)
DB.Metadata.Signposts.AssetsBlock.Byte = ftell(DB.Metadata.File.Handle); DB.Metadata.Signposts.AssetsBlock.Byte = ftell(DB.Metadata.File.Handle);
fwrite(&DB.AssetsBlock, sizeof(DB.AssetsBlock), 1, DB.Metadata.File.Handle); fwrite(&DB.AssetsBlock, sizeof(DB.AssetsBlock), 1, DB.Metadata.File.Handle);
fclose(DB.Metadata.File.Handle); CloseFile(&DB.Metadata.File, FALSE);
ReadFileIntoBuffer(&DB.Metadata.File); ReadFileIntoBuffer(&DB.Metadata.File);
DB.Metadata.Signposts.ProjectsBlock.Ptr = DB.Metadata.File.Buffer.Location + DB.Metadata.Signposts.ProjectsBlock.Byte; DB.Metadata.Signposts.ProjectsBlock.Ptr = DB.Metadata.File.Buffer.Location + DB.Metadata.Signposts.ProjectsBlock.Byte;
DB.Metadata.Signposts.AssetsBlock.Ptr = DB.Metadata.File.Buffer.Location + DB.Metadata.Signposts.AssetsBlock.Byte; DB.Metadata.Signposts.AssetsBlock.Ptr = DB.Metadata.File.Buffer.Location + DB.Metadata.Signposts.AssetsBlock.Byte;
DB.Ready = TRUE;
} }
else else
{ {
@ -15262,11 +15338,12 @@ InitDB(void)
#if 0 #if 0
DB.File.Handle = fopen(DB.File.Path, "w"); DB.File.Handle = fopen(DB.File.Path, "w");
fprintf(DB.File.Handle, "---\n"); fprintf(DB.File.Handle, "---\n");
fclose(DB.File.Handle); CloseFile(&DB.File, NA);
ReadFileIntoBuffer(&DB.File); ReadFileIntoBuffer(&DB.File);
#endif #endif
} }
} }
}
return Result; return Result;
} }
@ -15407,7 +15484,7 @@ InsertProjectIntoDB_TopLevel(project_generations *G, db_block_projects **Block,
{ {
uint64_t Byte = 0; uint64_t Byte = 0;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Block); WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Block);
db_block_projects NewBlock = **Block; db_block_projects NewBlock = **Block;
@ -15431,7 +15508,7 @@ InsertProjectIntoDB(project_generations *G, db_header_project **Parent, db_heade
{ {
uint64_t Byte = 0; uint64_t Byte = 0;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent); WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent);
db_header_project NewParent = **Parent; db_header_project NewParent = **Parent;
@ -15633,7 +15710,7 @@ DeleteProject_TopLevel(db_block_projects **Parent, db_header_project **Child, pr
uint64_t PPos = (char *)*Parent - DB.Metadata.File.Buffer.Location; uint64_t PPos = (char *)*Parent - DB.Metadata.File.Buffer.Location;
uint64_t CPos = (char *)*Child - DB.Metadata.File.Buffer.Location; uint64_t CPos = (char *)*Child - DB.Metadata.File.Buffer.Location;
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
uint64_t Byte = 0; uint64_t Byte = 0;
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent); WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent);
@ -15663,7 +15740,7 @@ DeleteProject(db_header_project **Parent, db_header_project **Child, project_gen
uint64_t CPos = (char *)*Child - DB.Metadata.File.Buffer.Location; uint64_t CPos = (char *)*Child - DB.Metadata.File.Buffer.Location;
//db_project_index Index = GetCurrentProjectIndex(G); //db_project_index Index = GetCurrentProjectIndex(G);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
uint64_t Byte = 0; uint64_t Byte = 0;
WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent); WriteFromByteToPointer(&DB.Metadata.File, &Byte, *Parent);
@ -15723,7 +15800,7 @@ ReorganiseProjectsInterior(project_generations *G, project *CChild, uint64_t Chi
project_generations ThisAcc = InitAccumulator(G); project_generations ThisAcc = InitAccumulator(G);
AccumulateProjectIndices(&ThisAcc, This); AccumulateProjectIndices(&ThisAcc, This);
DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"); OpenFileForWriting(&DB.Metadata.File);
uint64_t Byte = 0; uint64_t Byte = 0;
WriteFromByteToPointer(&DB.Metadata.File, &Byte, SChild); WriteFromByteToPointer(&DB.Metadata.File, &Byte, SChild);
@ -16036,13 +16113,13 @@ SyncGlobalPagesWithInput(neighbourhood *N, buffers *CollationBuffers)
if(StringsDiffer(StoredGlobalSearchDir, Config->GlobalSearchDir)) if(StringsDiffer(StoredGlobalSearchDir, Config->GlobalSearchDir))
{ {
ClearCopyStringNoFormat(ProjectsBlock->GlobalSearchDir, sizeof(ProjectsBlock->GlobalSearchDir), Config->GlobalSearchDir); ClearCopyStringNoFormat(ProjectsBlock->GlobalSearchDir, sizeof(ProjectsBlock->GlobalSearchDir), Config->GlobalSearchDir);
WriteEntireDatabase(); WriteEntireDatabase(N);
} }
if(StringsDiffer(StoredGlobalSearchURL, Config->GlobalSearchURL)) if(StringsDiffer(StoredGlobalSearchURL, Config->GlobalSearchURL))
{ {
ClearCopyStringNoFormat(ProjectsBlock->GlobalSearchURL, sizeof(ProjectsBlock->GlobalSearchURL), Config->GlobalSearchURL); ClearCopyStringNoFormat(ProjectsBlock->GlobalSearchURL, sizeof(ProjectsBlock->GlobalSearchURL), Config->GlobalSearchURL);
WriteEntireDatabase(); WriteEntireDatabase(N);
} }
if(!ProjectsBlock->GlobalSearchDir[0]) if(!ProjectsBlock->GlobalSearchDir[0])
@ -16350,6 +16427,15 @@ RemoveAndFreeWatchHandles(watch_handles *W)
{ {
watch_handle *This = GetPlaceInBook(&W->Handles, i); watch_handle *This = GetPlaceInBook(&W->Handles, i);
inotify_rm_watch(inotifyInstance, This->Descriptor); inotify_rm_watch(inotifyInstance, This->Descriptor);
for(int j = 0; j < This->Files.ItemCount; ++j)
{
watch_file *File = GetPlaceInBook(&This->Files, j);
if(File->Handle)
{
fclose(File->Handle);
File->Handle = 0;
}
}
FreeBook(&This->Files); FreeBook(&This->Files);
} }
FreeAndReinitialiseBook(&W->Handles); FreeAndReinitialiseBook(&W->Handles);
@ -16359,8 +16445,9 @@ RemoveAndFreeWatchHandles(watch_handles *W)
void void
DiscardAllAndFreeConfig(void) DiscardAllAndFreeConfig(void)
{ {
FreeSignpostedFile(&DB.Metadata); // NOTE(matt): This seems fine FreeSignpostedFile(&DB.Metadata, TRUE); // NOTE(matt): This seems fine
FreeFile(&DB.File); // NOTE(matt): This seems fine FreeFile(&DB.File, NA); // NOTE(matt): This seems fine
DB.Ready = FALSE;
FreeAssets(&Assets); // NOTE(matt): This seems fine FreeAssets(&Assets); // NOTE(matt): This seems fine
RemoveAndFreeWatchHandles(&WatchHandles); RemoveAndFreeWatchHandles(&WatchHandles);
CurrentProject = 0; // NOTE(matt): This is fine CurrentProject = 0; // NOTE(matt): This is fine
@ -16572,6 +16659,8 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke
switch(WatchFile->Type) switch(WatchFile->Type)
{ {
case WT_HMML: case WT_HMML:
{
if(DB.Ready)
{ {
SetCurrentProject(WatchFile->Project, N); SetCurrentProject(WatchFile->Project, N);
string BaseFilename = GetBaseFilename(Wrap0(Event->name), WatchFile->Extension); string BaseFilename = GetBaseFilename(Wrap0(Event->name), WatchFile->Extension);
@ -16583,14 +16672,18 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke
{ {
Inserted |= (InsertEntry(N, CollationBuffers, BespokeTemplate, BaseFilename, 0) == RC_SUCCESS); Inserted |= (InsertEntry(N, CollationBuffers, BespokeTemplate, BaseFilename, 0) == RC_SUCCESS);
} }
}
} break; } break;
case WT_ASSET: case WT_ASSET:
{
if(DB.Ready)
{ {
UpdateAsset(WatchFile->Asset, FALSE); UpdateAsset(WatchFile->Asset, FALSE);
UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts);
#if DEBUG_LANDMARKS #if DEBUG_LANDMARKS
UpdatedAsset = TRUE; UpdatedAsset = TRUE;
#endif #endif
}
} break; } break;
case WT_CONFIG: case WT_CONFIG:
{ {
@ -16749,7 +16842,6 @@ main(int ArgC, char **Args)
PushWatchHandle(ConfigPathL, EXT_NULL, WT_CONFIG, 0, 0); PushWatchHandle(ConfigPathL, EXT_NULL, WT_CONFIG, 0, 0);
Config = ParseConfig(ConfigPathL, &TokensList); Config = ParseConfig(ConfigPathL, &TokensList);
rc Succeeding = RC_SUCCESS;
if(Config) if(Config)
{ {
if(Mode & MODE_EXAMINE) if(Mode & MODE_EXAMINE)
@ -16766,7 +16858,7 @@ main(int ArgC, char **Args)
else else
{ {
/* */ MEM_TEST_MID("main()"); /* */ MEM_TEST_MID("main()");
/* +MEM */ Succeeding = InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate); /* +MEM */ InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate);
/* */ MEM_TEST_MID("main()"); /* */ MEM_TEST_MID("main()");
} }
} }
@ -16782,8 +16874,6 @@ main(int ArgC, char **Args)
} }
} }
if(Succeeding == RC_SUCCESS)
{
if(inotifyInstance != -1) if(inotifyInstance != -1)
{ {
while(GlobalRunning && MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL) while(GlobalRunning && MonitorFilesystem(&Neighbourhood, &CollationBuffers, &BespokeTemplate, ConfigPathL, &TokensList) != RC_ARENA_FULL)
@ -16798,7 +16888,7 @@ main(int ArgC, char **Args)
// REST PUT request from insobot when a quote changes (unless we're supposed to poll insobot for them?), and rebuild // REST PUT request from insobot when a quote changes (unless we're supposed to poll insobot for them?), and rebuild
// the player page(s) accordingly. // the player page(s) accordingly.
// //
if(!(Mode & MODE_DRYRUN) && Config && Config->RespectingPrivacy && time(0) - LastPrivacyCheck > Config->PrivacyCheckInterval) if(!(Mode & MODE_DRYRUN) && DB.Ready && Config && Config->RespectingPrivacy && time(0) - LastPrivacyCheck > Config->PrivacyCheckInterval)
{ {
RecheckPrivacy(&Neighbourhood, &CollationBuffers, &BespokeTemplate); RecheckPrivacy(&Neighbourhood, &CollationBuffers, &BespokeTemplate);
} }
@ -16812,9 +16902,7 @@ main(int ArgC, char **Args)
} }
DiscardAllAndFreeConfig(); DiscardAllAndFreeConfig();
RemoveAndFreeWatchHandles(&WatchHandles);
MEM_TEST_END("main()"); MEM_TEST_END("main()");
} }
}
Exit(); Exit();
} }

View File

@ -8,12 +8,6 @@ exit
// config // config
// //
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#define ArgCountPointer(...) (sizeof((void *[]){__VA_ARGS__})/sizeof(void *)) #define ArgCountPointer(...) (sizeof((void *[]){__VA_ARGS__})/sizeof(void *))
#define DigitsInInt(I) DigitsInInt_(I, sizeof(*I)) #define DigitsInInt(I) DigitsInInt_(I, sizeof(*I))
@ -348,6 +342,15 @@ PushTokens(memory_book *TokensList)
return This; return This;
} }
void
PopTokens(memory_book *TokensList)
{
--TokensList->ItemCount;
tokens *This = GetPlaceInBook(TokensList, TokensList->ItemCount);
FreeBook(&This->Token);
This->CurrentLine = 0;
}
void void
FreeTokensList(memory_book *TokensList) FreeTokensList(memory_book *TokensList)
{ {
@ -357,7 +360,8 @@ FreeTokensList(memory_book *TokensList)
This->CurrentIndex = 0; This->CurrentIndex = 0;
This->CurrentLine = 0; This->CurrentLine = 0;
FreeBook(&This->Token); FreeBook(&This->Token);
FreeFile(&This->File); FreeFileBufferAndPath(&This->File); // NOTE(matt): Not closing file because the next time we touch this handle is in
// RemoveAndFreeWatchHandles() where we will close it
} }
TokensList->ItemCount = 0; TokensList->ItemCount = 0;
} }
@ -588,22 +592,36 @@ typedef struct
} config; } config;
char *ExpandPath(string Path, string *RelativeToFile); // NOTE(matt): Forward declared. Consider reorganising the code? char *ExpandPath(string Path, string *RelativeToFile); // NOTE(matt): Forward declared. Consider reorganising the code?
void PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset); // NOTE(matt): Forward declared. Consider reorganising the code? watch_file *PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *Project, asset *Asset); // NOTE(matt): Forward declared. Consider reorganising the code?
tokens * tokens *
Tokenise(memory_book *TokensList, string Path) Tokenise(memory_book *TokensList, string Path, string *Filename, uint64_t LineNumber)
{ {
tokens *Result = 0; tokens *Result = 0;
char *Path0 = MakeString0("l", &Path); watch_file *WatchFile = PushWatchHandle(Path, EXT_NULL, WT_CONFIG, 0, 0);
FILE *Handle = 0;
PushWatchHandle(Path, EXT_NULL, WT_CONFIG, 0, 0);
if((Handle = fopen(Path0, "r")))
{
fclose(Handle);
Result = PushTokens(TokensList); Result = PushTokens(TokensList);
Result->File = InitFile(0, &Path, EXT_NULL); Result->File = InitFile(0, &Path, EXT_NULL, TRUE);
ReadFileIntoBuffer(&Result->File); switch(ReadFileIntoBuffer(&Result->File))
{
case RC_ERROR_FILE_LOCKED:
{
ConfigErrorLockedConfigLocation(Filename, LineNumber, &Path);
FreeFile(&Result->File, TRUE);
PopTokens(TokensList);
Result = 0;
} break;
case RC_ERROR_FILE:
{
ConfigErrorUnopenableConfigLocation(Filename, LineNumber, &Path);
FreeFile(&Result->File, TRUE);
PopTokens(TokensList);
Result = 0;
} break;
default:
{
WatchFile->Handle = Result->File.Handle;
buffer *B = &Result->File.Buffer; buffer *B = &Result->File.Buffer;
SkipWhitespace(Result, B); SkipWhitespace(Result, B);
while(B->Ptr - B->Location < B->Size) while(B->Ptr - B->Location < B->Size)
@ -770,12 +788,8 @@ Tokenise(memory_book *TokensList, string Path)
Advance(B, Advancement); Advance(B, Advancement);
SkipWhitespace(Result, B); SkipWhitespace(Result, B);
} }
} break;
} }
else
{
ConfigError(0, 0, S_WARNING, "Unable to open config file: ", &Path);
}
Free(Path0);
return Result; return Result;
} }
@ -2341,7 +2355,8 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T
} }
if(!I) if(!I)
{ {
I = Tokenise(TokensList, IncludePathL); token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex);
I = Tokenise(TokensList, IncludePathL, &Filepath, This->LineNumber);
} }
if(I) if(I)
{ {
@ -2352,11 +2367,6 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T
return 0; return 0;
} }
} }
else
{
token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex);
ConfigFileIncludeError(&Filepath, This->LineNumber, Wrap0(IncludePath));
}
Free(IncludePath); Free(IncludePath);
} }
else else
@ -4977,7 +4987,7 @@ ParseConfig(string Path, memory_book *TokensList)
MEM_LOOP_POST("InitTypeSpecs") MEM_LOOP_POST("InitTypeSpecs")
#endif #endif
config *Result = 0; config *Result = 0;
tokens *T = Tokenise(TokensList, Path); tokens *T = Tokenise(TokensList, Path, NA, NA);
#if 0 #if 0
MEM_LOOP_PRE_FREE("Tokenise") MEM_LOOP_PRE_FREE("Tokenise")
@ -4993,7 +5003,7 @@ ParseConfig(string Path, memory_book *TokensList)
scope_tree *ScopeTree = InitRootScopeTree(); scope_tree *ScopeTree = InitRootScopeTree();
SetTypeSpec(ScopeTree, &TypeSpecs); SetTypeSpec(ScopeTree, &TypeSpecs);
SetDefaults(ScopeTree, &TypeSpecs); SetDefaults(ScopeTree, &TypeSpecs);
ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, 0); ScopeTree = ScopeTokens(ScopeTree, TokensList, T, &TypeSpecs, NA);
// TODO(matt): Mem testing // TODO(matt): Mem testing
// //