diff --git a/cinera/cinera.c b/cinera/cinera.c index b300ca1..da5974b 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -1,7 +1,7 @@ #if 0 ctime -begin ${0%.*}.ctm -gcc -g -fsanitize=address -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl -#gcc -O2 -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl +#gcc -g -fsanitize=address -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl +gcc -O2 -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl ctime -end ${0%.*}.ctm exit #endif @@ -14,11 +14,11 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 5, - .Patch = 33 + .Patch = 34 }; // TODO(matt): Copy in the DB 3 stuff from cinera_working.c -#define CINERA_DB_VERSION 2 +#define CINERA_DB_VERSION 3 #define DEBUG 0 #define DEBUG_MEM 0 @@ -47,6 +47,15 @@ typedef unsigned int bool; #define Kilobytes(Bytes) Bytes << 10 #define Megabytes(Bytes) Bytes << 20 +#define MAX_PROJECT_ID_LENGTH 31 +#define MAX_PROJECT_NAME_LENGTH 63 +#define MAX_BASE_URL_LENGTH 127 +#define MAX_RELATIVE_PAGE_LOCATION_LENGTH 31 +#define MAX_PLAYER_URL_PREFIX_LENGTH 15 + +#define MAX_BASE_FILENAME_LENGTH 31 +#define MAX_TITLE_LENGTH 128 - (MAX_BASE_FILENAME_LENGTH + 1) - (int)sizeof(link_insertion_offsets) - (int)sizeof(unsigned short int) - 1 // NOTE(matt): We size this such that index_metadata is 128 bytes total + enum { EDITION_SINGLE, @@ -70,6 +79,7 @@ enum enum { MODE_ONESHOT = 1 << 0, + MODE_EXAMINE = 1 << 1, } modes; enum @@ -113,10 +123,54 @@ typedef struct { buffer Buffer; FILE *Handle; - char Path[256]; + char Path[256]; // NOTE(matt): Could this just be a char *? int FileSize; } file_buffer; +// TODO(matt): Increment CINERA_DB_VERSION! +typedef struct +{ + // NOTE(matt): Consider augmenting this to contain such stuff as: "hex signature" + unsigned int CurrentDBVersion; // NOTE(matt): Put this first to aid reliability + version CurrentAppVersion; + version CurrentHMMLVersion; + + unsigned int InitialDBVersion; + version InitialAppVersion; + version InitialHMMLVersion; + + unsigned short int EntryCount; + char ProjectID[MAX_PROJECT_ID_LENGTH + 1]; + char ProjectName[MAX_PROJECT_NAME_LENGTH + 1]; + char BaseURL[MAX_BASE_URL_LENGTH + 1]; + char IndexLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1]; + char PlayerLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1]; + char PlayerURLPrefix[MAX_PLAYER_URL_PREFIX_LENGTH + 1]; +} index_header; + +typedef struct +{ + unsigned int PrevStart, NextStart; + unsigned short int PrevEnd, NextEnd; +} link_insertion_offsets; // NOTE(matt): Relative + +typedef struct +{ + link_insertion_offsets LinkOffsets; + unsigned short int Size; + char BaseFilename[MAX_BASE_FILENAME_LENGTH + 1]; + char Title[MAX_TITLE_LENGTH + 1]; +} index_metadata; + +typedef struct +{ + file_buffer File; + file_buffer Metadata; + index_header Header; + index_metadata Entry; +} index; +// TODO(matt): Increment CINERA_DB_VERSION! + typedef struct { buffer IncludesIndex; @@ -126,10 +180,10 @@ typedef struct buffer Menus; buffer Player; buffer ScriptPlayer; - char ProjectName[32]; - char Title[256]; - char URLIndex[2048]; - char URLPlayer[2048]; + char ProjectName[MAX_PROJECT_NAME_LENGTH + 1]; + char Title[MAX_TITLE_LENGTH + 1]; + char URLIndex[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1]; + char URLPlayer[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1]; char VideoID[16]; } buffers; @@ -435,6 +489,15 @@ CopyBuffer(buffer *Dest, buffer *Src) *Dest->Ptr = '\0'; } +void +Clear(char *String, int Size) +{ + for(int i = 0; i < Size; ++i) + { + String[i] = 0; + } +} + int CopyStringNoFormat(char *Dest, char *String) { @@ -448,6 +511,13 @@ CopyStringNoFormat(char *Dest, char *String) return Length; } +int +ClearCopyStringNoFormat(char *Dest, int DestSize, char *String) +{ + Clear(Dest, DestSize); + return(CopyStringNoFormat(Dest, String)); +} + // TODO(matt): Maybe do a version of this that takes a string as a Terminator int CopyStringNoFormatT(char *Dest, char *String, char Terminator) @@ -825,15 +895,6 @@ ConstructTemplatePath(template *Template, int TemplateType) } } -void -Clear(char *String, int Size) -{ - for(int i = 0; i < Size; ++i) - { - String[i] = 0; - } -} - int InitTemplate(template **Template) { @@ -1960,18 +2021,20 @@ PrintUsage(char *BinaryLocation, config *DefaultConfig) " -o \n" " Override default output player location (\"%s\")\n" "\n" + " -e\n" + " Display (examine) index file and exit\n" " -f\n" " Force integration with an incomplete template\n" "\n" " -l \n" " Override default log level (%d), where n is from 0 (terse) to 7 (verbose)\n" - " -U \n" + " -u \n" " Override default update interval (%d)\n" //" -c config location\n" " -v\n" - " display version and exit\n" + " Display version and exit\n" " -h\n" - " display this help\n" + " Display this help\n" "\n" "Template:\n" " A complete index template shall contain exactly one each of the following tags:\n" @@ -1987,13 +2050,14 @@ PrintUsage(char *BinaryLocation, config *DefaultConfig) " \n" " must come after and \n" "\n" + " Optional tags available for use in your player template:\n" + " \n" + " \n" + "\n" " Other available tags:\n" " \n" - " \n" " \n" " Only really usable if BaseURL is set \e[1;30m(-B)\e[0m\n" - " \n" - " Intended only for your player template\n" "\n" "HMML Specification:\n" " https://git.handmade.network/Annotation-Pushers/Annotation-System/wikis/hmmlspec\n", @@ -2281,6 +2345,138 @@ MediumExists(char *Medium) return FALSE; } +int +ReadFileIntoBuffer(file_buffer *File, int BufferPadding) +{ + if(!(File->Handle = fopen(File->Path, "r"))) + { + return RC_ERROR_FILE; + } + + fseek(File->Handle, 0, SEEK_END); + File->FileSize = ftell(File->Handle); + File->Buffer.Size = File->FileSize + 1 + BufferPadding; // NOTE(matt): +1 to accommodate a NULL terminator + fseek(File->Handle, 0, SEEK_SET); + + // TODO(matt): Consider using the MemoryArena? Maybe have separate ReadFileIntoMemory() and ReadFileIntoArena() + if(!(File->Buffer.Location = malloc(File->Buffer.Size))) + { + fclose(File->Handle); + return RC_ERROR_MEMORY; + } + File->Buffer.Ptr = File->Buffer.Location; + + fread(File->Buffer.Location, File->FileSize, 1, File->Handle); + File->Buffer.Location[File->FileSize] = '\0'; + fclose(File->Handle); + return RC_SUCCESS; +} + +// NOTE(matt): Currently unused +int +WriteBufferToFile(file_buffer *File, buffer *Buffer, int BytesToWrite, bool KeepFileHandleOpen) +{ + if(!(File->Handle = fopen(File->Path, "w"))) + { + return RC_ERROR_FILE; + } + + fwrite(Buffer->Location, BytesToWrite, 1, File->Handle); + if(!KeepFileHandleOpen) + { + fclose(File->Handle); + } + return RC_SUCCESS; +} + +typedef struct +{ + char *BaseFilename; + char *Title; +} neighbour; + +typedef struct +{ + neighbour Prev; + neighbour Next; +} neighbours; + +int +ExamineIndex(index *Index) +{ + int IndexMetadataFileReadCode = ReadFileIntoBuffer(&Index->Metadata, 0); + switch(IndexMetadataFileReadCode) + { + case RC_ERROR_MEMORY: + return RC_ERROR_MEMORY; + case RC_ERROR_FILE: + fprintf(stderr, "Unable to open index file %s: %s\n", Index->Metadata.Path, strerror(errno)); + return RC_ERROR_FILE; + case RC_SUCCESS: + break; + } + + Index->Header = *(index_header *)Index->Metadata.Buffer.Ptr; + // TODO(matt): Check that we're the current version, and maybe print out stuff accordingly? Or do we just straight up + // enforce that examining an index requires upgrading to the current version? + printf("Current:\n" + "\tDBVersion: %d\n" + "\tAppVersion: %d.%d.%d\n" + "\tHMMLVersion: %d.%d.%d\n" + "\n" + "Initial:\n" + "\tDBVersion: %d\n" + "\tAppVersion: %d.%d.%d\n" + "\tHMMLVersion: %d.%d.%d\n" + "\n" + "Project ID: %s\n" + "Project Full Name: %s\n" + "\n" + "Base URL: %s\n" + "Relative Index Page Location: %s\n" + "Relative Player Page Location: %s\n" + "Player Page URL Prefix: %s\n" + "\n" + "Entries: %d\n", + + Index->Header.CurrentDBVersion, + Index->Header.CurrentAppVersion.Major, Index->Header.CurrentAppVersion.Minor, Index->Header.CurrentAppVersion.Patch, + Index->Header.CurrentHMMLVersion.Major, Index->Header.CurrentHMMLVersion.Minor, Index->Header.CurrentHMMLVersion.Patch, + + Index->Header.InitialDBVersion, + Index->Header.InitialAppVersion.Major, Index->Header.InitialAppVersion.Minor, Index->Header.InitialAppVersion.Patch, + Index->Header.InitialHMMLVersion.Major, Index->Header.InitialHMMLVersion.Minor, Index->Header.InitialHMMLVersion.Patch, + + Index->Header.ProjectID, + Index->Header.ProjectName, + + Index->Header.BaseURL, + StringsDiffer(Index->Header.IndexLocation, "") ? Index->Header.IndexLocation : "(same as Base URL)", + StringsDiffer(Index->Header.PlayerLocation, "") ? Index->Header.PlayerLocation : "(directly descended from Base URL)", + StringsDiffer(Index->Header.PlayerURLPrefix, "") ? Index->Header.PlayerURLPrefix : "(no special prefix, the player page URLs equal their entry's base filename)", + + Index->Header.EntryCount); + + Index->Metadata.Buffer.Ptr += sizeof(index_header); + for(int EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex) + { + Index->Entry = *(index_metadata *)Index->Metadata.Buffer.Ptr; + printf(" %3d\t%s%sSize: %4d\t%d\t%d\t%d\t%d\n" + "\t %s\n", + EntryIndex + 1, Index->Entry.BaseFilename, + StringLength(Index->Entry.BaseFilename) > 8 ? "\t" : "\t\t", // NOTE(matt): Janktasm + Index->Entry.Size, + Index->Entry.LinkOffsets.PrevStart, + Index->Entry.LinkOffsets.PrevEnd, + Index->Entry.LinkOffsets.NextStart, + Index->Entry.LinkOffsets.NextEnd, + Index->Entry.Title); + Index->Metadata.Buffer.Ptr += sizeof(index_metadata); + } + FreeBuffer(&Index->Metadata.Buffer); + return RC_SUCCESS; +} + #define HMMLCleanup() \ DeclaimBuffer(&FilterState); \ DeclaimBuffer(&CreditsMenu); \ @@ -2292,7 +2488,7 @@ MediumExists(char *Medium) hmml_free(&HMML); int -HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filename) +HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filename, link_insertion_offsets *LinkOffsets, neighbours *Neighbours) { RewindBuffer(&CollationBuffers->IncludesPlayer); RewindBuffer(&CollationBuffers->Menus); @@ -2325,22 +2521,34 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen if(HMML.well_formed) { + bool HaveErrors = FALSE; + char *BaseFilename = GetBaseFilename(Filename, ".hmml"); + if(StringLength(BaseFilename) > MAX_BASE_FILENAME_LENGTH) + { + fprintf(stderr, "\e[1;31mBase filename \"%s\" is too long (%d/%d characters)\e[0m\n", BaseFilename, StringLength(BaseFilename), MAX_BASE_FILENAME_LENGTH); + HaveErrors = TRUE; + } + if(!HMML.metadata.title) { fprintf(stderr, "Please set the title attribute in the [video] node of your .hmml file\n"); - fprintf(stderr, "\e[1;31mSkipping\e[0m %s\n", BaseFilename); - hmml_free(&HMML); - return RC_ERROR_HMML; + HaveErrors = TRUE; + } + else if(StringLength(HMML.metadata.title) > MAX_TITLE_LENGTH) + { + fprintf(stderr, "\e[1;31mVideo title \"%s\" is too long (%d/%d characters)\e[0m\n", HMML.metadata.title, StringLength(HMML.metadata.title), MAX_TITLE_LENGTH); + HaveErrors = TRUE; + } + else + { + CopyString(CollationBuffers->Title, HMML.metadata.title); } - CopyString(CollationBuffers->Title, HMML.metadata.title); if(!HMML.metadata.member) { fprintf(stderr, "Please set the member attribute in the [video] node of your .hmml file\n"); - fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); - hmml_free(&HMML); - return RC_ERROR_HMML; + HaveErrors = TRUE; } if(!HMML.metadata.project && !StringsDiffer(Config.Theme, "")) @@ -2351,22 +2559,18 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen "\t2. ProjectID on the command line with -p\n" "\t3. Style on the command line with -s\n" ); - fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); - hmml_free(&HMML); - return RC_ERROR_HMML; + HaveErrors = TRUE; } if(!HMML.metadata.id) { fprintf(stderr, "Please set the id attribute in the [video] node of your .hmml file\n"); - fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); - hmml_free(&HMML); - return RC_ERROR_HMML; + HaveErrors = TRUE; } CopyString(CollationBuffers->VideoID, HMML.metadata.id); buffer URLPlayer; - ClaimBuffer(&URLPlayer, "URLPlayer", 2048); + ClaimBuffer(&URLPlayer, "URLPlayer", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH); ConstructPlayerURL(&URLPlayer, BaseFilename); CopyString(CollationBuffers->URLPlayer, URLPlayer.Location); DeclaimBuffer(&URLPlayer); @@ -2390,28 +2594,41 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen { DefaultMedium = HMML.metadata.medium; } - else + else { HaveErrors = TRUE; } + } + + if(!HaveErrors) + { + if(HMML.metadata.template) { - fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); - hmml_free(&HMML); - return RC_ERROR_HMML; + switch(ValidateTemplate(BespokeTemplate, HMML.metadata.template, TEMPLATE_BESPOKE)) + { + case RC_ARENA_FULL: + case RC_INVALID_TEMPLATE: // Invalid template + case RC_ERROR_FILE: // Could not load template + case RC_ERROR_MEMORY: // Could not allocate memory for template + HaveErrors = TRUE; + case RC_SUCCESS: + break; + } } } - if(HMML.metadata.template) + if(HaveErrors) { - switch(ValidateTemplate(BespokeTemplate, HMML.metadata.template, TEMPLATE_BESPOKE)) - { - case RC_ARENA_FULL: - case RC_INVALID_TEMPLATE: // Invalid template - case RC_ERROR_FILE: // Could not load template - case RC_ERROR_MEMORY: // Could not allocate memory for template - fprintf(stderr, "\e[1;31mSkipping\e[0m %s - %s\n", BaseFilename, HMML.metadata.title); - hmml_free(&HMML); - return RC_ERROR_HMML; - case RC_SUCCESS: - break; - } + fprintf(stderr, "\e[1;31mSkipping\e[0m %s", BaseFilename); + if(HMML.metadata.title) { fprintf(stderr, " - %s", HMML.metadata.title); } + fprintf(stderr, "\n"); + hmml_free(&HMML); + return RC_ERROR_HMML; + } + + if(LinkOffsets) + { + LinkOffsets->PrevStart = 0; + LinkOffsets->NextStart = 0; + LinkOffsets->PrevEnd = 0; + LinkOffsets->NextEnd = 0; } #if DEBUG @@ -2484,6 +2701,36 @@ HMMLToBuffers(buffers *CollationBuffers, template **BespokeTemplate, char *Filen "
\n" "
\n", HMML.metadata.id, StringsDiffer(Config.Theme, "") ? Config.Theme : HMML.metadata.project); + if(LinkOffsets) + { + LinkOffsets->PrevStart = (CollationBuffers->Player.Ptr - CollationBuffers->Player.Location); + if(Neighbours->Prev.BaseFilename || Neighbours->Next.BaseFilename) + { + if(Neighbours->Prev.BaseFilename) + { + // TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here + buffer PreviousPlayerURL; + ClaimBuffer(&PreviousPlayerURL, "PreviousPlayerURL", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH); + ConstructPlayerURL(&PreviousPlayerURL, Neighbours->Prev.BaseFilename); + CopyStringToBuffer(&CollationBuffers->Player, + "
Previous: '%s'
\n", + PreviousPlayerURL.Location, + Neighbours->Prev.Title); + + DeclaimBuffer(&PreviousPlayerURL); + } + else + { + CopyStringToBuffer(&CollationBuffers->Player, + "
Welcome to %s
\n", CollationBuffers->ProjectName); + } + } + LinkOffsets->PrevEnd = (CollationBuffers->Player.Ptr - CollationBuffers->Player.Location - LinkOffsets->PrevStart); + } + + CopyStringToBuffer(&CollationBuffers->Player, + "
\n"); + switch(BuildCredits(&CreditsMenu, &HasCreditsMenu, &HMML.metadata)) { case CreditsError_NoHost: @@ -3548,6 +3795,36 @@ AppendedIdentifier: "
"); + CopyStringToBuffer(&CollationBuffers->Player, + "
\n"); + + if(LinkOffsets) + { + LinkOffsets->NextStart = (CollationBuffers->Player.Ptr - CollationBuffers->Player.Location - (LinkOffsets->PrevStart + LinkOffsets->PrevEnd)); + if(Neighbours->Prev.BaseFilename || Neighbours->Next.BaseFilename) + { + if(Neighbours->Next.BaseFilename) + { + // TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here + buffer NextPlayerURL; + ClaimBuffer(&NextPlayerURL, "NextPlayerURL", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH); + ConstructPlayerURL(&NextPlayerURL, Neighbours->Next.BaseFilename); + CopyStringToBuffer(&CollationBuffers->Player, + "
Next: '%s'
\n", + NextPlayerURL.Location, + Neighbours->Next.Title); + + DeclaimBuffer(&NextPlayerURL); + } + else + { + CopyStringToBuffer(&CollationBuffers->Player, + "
You have arrived at the (current) end of %s
\n", CollationBuffers->ProjectName); + } + } + LinkOffsets->NextEnd = (CollationBuffers->Player.Ptr - CollationBuffers->Player.Location - (LinkOffsets->PrevStart + LinkOffsets->PrevEnd + LinkOffsets->NextStart)); + } + CopyStringToBuffer(&CollationBuffers->Player, " \n" " "); @@ -3555,7 +3832,7 @@ AppendedIdentifier: // TODO(matt): Maybe do something about indentation levels buffer URLIndex; - ClaimBuffer(&URLIndex, "URLIndex", 2048); + ClaimBuffer(&URLIndex, "URLIndex", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1); ConstructIndexURL(&URLIndex); CopyString(CollationBuffers->URLIndex, URLIndex.Location); DeclaimBuffer(&URLIndex); @@ -3668,7 +3945,7 @@ AppendedIdentifier: } int -BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, int PageType) +BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, int PageType, int *PlayerOffset) { #if DEBUG printf("\n\n --- Buffer Collation ---\n" @@ -3705,6 +3982,7 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i Output.Ptr = Output.Location; + bool NeedPlayerOffset = PlayerOffset ? TRUE : FALSE; Template->Buffer.Ptr = Template->Buffer.Location; for(int i = 0; i < Template->Metadata.TagCount; ++i) { @@ -3732,7 +4010,14 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i CopyStringToBuffer(&Output, CollationBuffers->Title); break; case TAG_URL: - CopyStringToBuffer(&Output, PageType == PAGE_PLAYER ? CollationBuffers->URLPlayer : CollationBuffers->URLIndex); + if(PageType == PAGE_PLAYER) + { + CopyStringToBuffer(&Output, CollationBuffers->URLPlayer); + } + else + { + CopyStringToBuffer(&Output, CollationBuffers->URLIndex); + } break; case TAG_VIDEO_ID: CopyStringToBuffer(&Output, CollationBuffers->VideoID); @@ -3750,18 +4035,27 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i } break; case TAG_INCLUDES: - CopyBuffer(&Output, PageType == PAGE_PLAYER ? &CollationBuffers->IncludesPlayer : &CollationBuffers->IncludesIndex); + if(PageType == PAGE_PLAYER) + { + CopyBuffer(&Output, &CollationBuffers->IncludesPlayer); + } + else + { + CopyBuffer(&Output, &CollationBuffers->IncludesIndex); + } break; case TAG_MENUS: CopyBuffer(&Output, &CollationBuffers->Menus); break; case TAG_PLAYER: + if(NeedPlayerOffset) { *PlayerOffset = (Output.Ptr - Output.Location); NeedPlayerOffset = !NeedPlayerOffset; } CopyBuffer(&Output, &CollationBuffers->Player); break; case TAG_SCRIPT: CopyBuffer(&Output, &CollationBuffers->ScriptPlayer); break; } + DepartComment(&Template->Buffer); } while(Template->Buffer.Ptr - Template->Buffer.Location < Template->Buffer.Size) @@ -3827,6 +4121,9 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i CopyBuffer(&Master, &CollationBuffers->Menus); CopyStringToBuffer(&Master, "\n" " "); + + if(PlayerOffset) { *PlayerOffset = Master.Ptr - Master.Location; } + CopyBuffer(&Master, &CollationBuffers->Player); CopyStringToBuffer(&Master, "\n" " "); @@ -3858,50 +4155,6 @@ BuffersToHTML(buffers *CollationBuffers, template *Template, char *OutputPath, i } } -int -ReadFileIntoBuffer(file_buffer *File, int BufferPadding) -{ - if(!(File->Handle = fopen(File->Path, "r"))) - { - return RC_ERROR_FILE; - } - - fseek(File->Handle, 0, SEEK_END); - File->FileSize = ftell(File->Handle); - File->Buffer.Size = File->FileSize + 1 + BufferPadding; // NOTE(matt): +1 to accommodate a NULL terminator - fseek(File->Handle, 0, SEEK_SET); - - // TODO(matt): Consider using the MemoryArena? Maybe have separate ReadFileIntoMemory() and ReadFileIntoArena() - if(!(File->Buffer.Location = malloc(File->Buffer.Size))) - { - fclose(File->Handle); - return RC_ERROR_MEMORY; - } - File->Buffer.Ptr = File->Buffer.Location; - - fread(File->Buffer.Location, File->FileSize, 1, File->Handle); - File->Buffer.Location[File->FileSize] = '\0'; - fclose(File->Handle); - return RC_SUCCESS; -} - -// NOTE(matt): Currently unused -int -WriteBufferToFile(file_buffer *File, buffer *Buffer, int BytesToWrite, bool KeepFileHandleOpen) -{ - if(!(File->Handle = fopen(File->Path, "w"))) - { - return RC_ERROR_FILE; - } - - fwrite(Buffer->Location, BytesToWrite, 1, File->Handle); - if(!KeepFileHandleOpen) - { - fclose(File->Handle); - } - return RC_SUCCESS; -} - enum { C_SEEK_FORWARDS, @@ -3983,62 +4236,10 @@ SeekBufferForString(buffer *Buffer, char *String, return RC_SUCCESS; } -// TODO(matt): Increment CINERA_DB_VERSION! -typedef struct -{ - // NOTE(matt): Consider augmenting this to contain such stuff as: "hex signature" - // InitialDBVersion - // CurrentDBVersion - // InitialAppVersion - // CurrentAppVersion - // InitialHMMLVersion - // CurrentHMMLVersion - - unsigned int DBVersion; // NOTE(matt): Put this first to aid reliability - version AppVersion; - version HMMLVersion; - unsigned int EntryCount; - char IndexLocation[32]; - char PlayerLocation[32]; -} index_header; - -typedef struct -{ - int Size; - char BaseFilename[32]; -} index_metadata; - -typedef struct -{ - file_buffer File; - file_buffer Metadata; - index_header Header; - index_metadata Entry; -} index; -// TODO(matt): Increment CINERA_DB_VERSION! - int -InsertIntoIndex(buffers *CollationBuffers, template **BespokeTemplate, char *BaseFilename) +InsertIntoIndex(index *Index, buffers *CollationBuffers, template **BespokeTemplate, char *BaseFilename) { - // NOTE(matt): The index will be stored in two files: - // 1) ProjectID.index - // 2) ProjectID.metadata - // - // .index is all of the stuff needed for the search - // .metadata is: int DBVersion - // version AppVersion - // version HMMLVersion - // int EntryCount - // for each Entry - // int Size from start of "name:" to end of "---\n") - // char BaseFilename[32] (0-padded) - // - - - index Index = { 0 }; - Index.Metadata.Buffer.ID = "IndexMetadata"; - CopyString(Index.Metadata.Path, "%s/%s.metadata", Config.BaseDir, Config.ProjectID); - int IndexMetadataFileReadCode = ReadFileIntoBuffer(&Index.Metadata, 0); + int IndexMetadataFileReadCode = ReadFileIntoBuffer(&Index->Metadata, 0); switch(IndexMetadataFileReadCode) { case RC_ERROR_MEMORY: @@ -4048,10 +4249,7 @@ InsertIntoIndex(buffers *CollationBuffers, template **BespokeTemplate, char *Bas break; } - Index.File.Buffer.ID = "IndexFile"; - CopyString(Index.File.Path, "%s/%s.index", Config.BaseDir, Config.ProjectID); - - int IndexFileReadCode = ReadFileIntoBuffer(&Index.File, 0); + int IndexFileReadCode = ReadFileIntoBuffer(&Index->File, 0); switch(IndexFileReadCode) { case RC_ERROR_MEMORY: @@ -4064,85 +4262,102 @@ InsertIntoIndex(buffers *CollationBuffers, template **BespokeTemplate, char *Bas int MetadataInsertionOffset = -1; int IndexEntryInsertionStart = -1; int IndexEntryInsertionEnd = -1; - Index.Header.EntryCount = 0; + Index->Header.EntryCount = 0; char *IndexEntryStart; bool Found = FALSE; - char InputFile[StringLength(BaseFilename) + StringLength(".hmml")]; - CopyString(InputFile, "%s.hmml", BaseFilename); - switch(HMMLToBuffers(CollationBuffers, BespokeTemplate, InputFile)) - { - // TODO(matt): Actually sort out the fatality of these cases, once we are always-on - case RC_ERROR_FILE: - case RC_ERROR_FATAL: - return RC_ERROR_FATAL; - case RC_ERROR_HMML: - case RC_ERROR_MAX_REFS: - case RC_ERROR_QUOTE: - case RC_INVALID_REFERENCE: - return RC_ERROR_HMML; - case RC_SUCCESS: - break; - }; - - Index.Entry.Size = CollationBuffers->Search.Ptr - CollationBuffers->Search.Location; - for(int i = 0; i < ArrayCount(Index.Entry.BaseFilename); ++i) - { - Index.Entry.BaseFilename[i] = '\0'; - } - CopyString(Index.Entry.BaseFilename, BaseFilename); - int EntryIndex; + neighbours Neighbours = { 0 }; + index_metadata *This, *Next; if(IndexMetadataFileReadCode == RC_SUCCESS && IndexFileReadCode == RC_SUCCESS) { // TODO(matt): Index validation? // Maybe at least if(!StringsDiffer(..., "name: \""); // and check that we won't crash through the end of the file when skipping to the next entry - Index.Header = *(index_header *)Index.Metadata.Buffer.Ptr; - Index.Metadata.Buffer.Ptr += sizeof(Index.Header); - Index.File.Buffer.Ptr += StringLength("---\n"); - IndexEntryStart = Index.File.Buffer.Ptr; + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location; + Index->Header = *(index_header *)Index->Metadata.Buffer.Ptr; + Index->Header.CurrentDBVersion = CINERA_DB_VERSION; + Index->Header.CurrentAppVersion = CINERA_APP_VERSION; + Index->Header.CurrentHMMLVersion.Major = hmml_version.Major; + Index->Header.CurrentHMMLVersion.Minor = hmml_version.Minor; + Index->Header.CurrentHMMLVersion.Patch = hmml_version.Patch; - for(EntryIndex = 0; EntryIndex < Index.Header.EntryCount; ++EntryIndex) + Index->Metadata.Buffer.Ptr += sizeof(Index->Header); + Index->File.Buffer.Ptr += StringLength("---\n"); + IndexEntryStart = Index->File.Buffer.Ptr; + + for(EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex) { - index_metadata This = *(index_metadata *)Index.Metadata.Buffer.Ptr; - if(!StringsDiffer(This.BaseFilename, BaseFilename)) + This = (index_metadata *)Index->Metadata.Buffer.Ptr; + if(!StringsDiffer(This->BaseFilename, BaseFilename)) { // Reinsert - MetadataInsertionOffset = Index.Metadata.Buffer.Ptr - Index.Metadata.Buffer.Location; - IndexEntryInsertionStart = IndexEntryStart - Index.File.Buffer.Location; - IndexEntryInsertionEnd = IndexEntryInsertionStart + This.Size; + if(EntryIndex < (Index->Header.EntryCount - 1)) + { + Next = (index_metadata *)(Index->Metadata.Buffer.Ptr + sizeof(Index->Entry)); + Neighbours.Next.BaseFilename = Next->BaseFilename; + Neighbours.Next.Title = Next->Title; + } + + MetadataInsertionOffset = Index->Metadata.Buffer.Ptr - Index->Metadata.Buffer.Location; + IndexEntryInsertionStart = IndexEntryStart - Index->File.Buffer.Location; + IndexEntryInsertionEnd = IndexEntryInsertionStart + This->Size; Found = TRUE; break; - } - else if(StringsDiffer(This.BaseFilename, BaseFilename) > 0) + else if(StringsDiffer(This->BaseFilename, BaseFilename) > 0) { // Insert - MetadataInsertionOffset = Index.Metadata.Buffer.Ptr - Index.Metadata.Buffer.Location; - IndexEntryInsertionStart = IndexEntryStart - Index.File.Buffer.Location; + Neighbours.Next.BaseFilename = This->BaseFilename; + Neighbours.Next.Title = This->Title; + + MetadataInsertionOffset = Index->Metadata.Buffer.Ptr - Index->Metadata.Buffer.Location; + IndexEntryInsertionStart = IndexEntryStart - Index->File.Buffer.Location; break; } else { - Index.Metadata.Buffer.Ptr += sizeof(Index.Entry); + Index->Metadata.Buffer.Ptr += sizeof(Index->Entry); - IndexEntryStart += This.Size; - Index.File.Buffer.Ptr = IndexEntryStart; + IndexEntryStart += This->Size; + Index->File.Buffer.Ptr = IndexEntryStart; + + Neighbours.Prev.BaseFilename = This->BaseFilename; + Neighbours.Prev.Title = This->Title; } } } else { // NOTE(matt): Initialising new index_header - Index.Header.DBVersion = CINERA_DB_VERSION; - Index.Header.AppVersion = CINERA_APP_VERSION; - Index.Header.HMMLVersion.Major = hmml_version.Major; - Index.Header.HMMLVersion.Minor = hmml_version.Minor; - Index.Header.HMMLVersion.Patch = hmml_version.Patch; - CopyString(Index.Header.IndexLocation, Config.IndexLocation); - CopyString(Index.Header.PlayerLocation, Config.PlayerLocation); + Index->Header.CurrentDBVersion = CINERA_DB_VERSION; + Index->Header.CurrentAppVersion = CINERA_APP_VERSION; + Index->Header.CurrentHMMLVersion.Major = hmml_version.Major; + Index->Header.CurrentHMMLVersion.Minor = hmml_version.Minor; + Index->Header.CurrentHMMLVersion.Patch = hmml_version.Patch; + + Index->Header.InitialDBVersion = CINERA_DB_VERSION; + Index->Header.InitialAppVersion = CINERA_APP_VERSION; + Index->Header.InitialHMMLVersion.Major = hmml_version.Major; + Index->Header.InitialHMMLVersion.Minor = hmml_version.Minor; + Index->Header.InitialHMMLVersion.Patch = hmml_version.Patch; + + CopyStringNoFormat(Index->Header.ProjectID, Config.ProjectID); + + for(int ProjectIndex = 0; ProjectIndex < ArrayCount(ProjectInfo); ++ProjectIndex) + { + if(!StringsDiffer(ProjectInfo[ProjectIndex].ProjectID, Config.ProjectID)) + { + CopyStringNoFormat(Index->Header.ProjectName, ProjectInfo[ProjectIndex].FullName); + break; + } + } + + CopyStringNoFormat(Index->Header.BaseURL, Config.BaseURL); + CopyStringNoFormat(Index->Header.IndexLocation, Config.IndexLocation); + CopyStringNoFormat(Index->Header.PlayerLocation, Config.PlayerLocation); + CopyStringNoFormat(Index->Header.PlayerURLPrefix, Config.PlayerURLPrefix); DIR *OutputDirectoryHandle; if(!(OutputDirectoryHandle = opendir(Config.BaseDir))) @@ -4157,16 +4372,37 @@ InsertIntoIndex(buffers *CollationBuffers, template **BespokeTemplate, char *Bas closedir(OutputDirectoryHandle); } - if(!(Index.Metadata.Handle = fopen(Index.Metadata.Path, "w"))) { FreeBuffer(&Index.Metadata.Buffer); return RC_ERROR_FILE; } - if(!(Index.File.Handle = fopen(Index.File.Path, "w"))) { FreeBuffer(&Index.File.Buffer); return RC_ERROR_FILE; } + char InputFile[StringLength(BaseFilename) + StringLength(".hmml")]; + CopyString(InputFile, "%s.hmml", BaseFilename); + switch(HMMLToBuffers(CollationBuffers, BespokeTemplate, InputFile, &Index->Entry.LinkOffsets, &Neighbours)) + { + // TODO(matt): Actually sort out the fatality of these cases, once we are always-on + case RC_ERROR_FILE: + case RC_ERROR_FATAL: + return RC_ERROR_FATAL; + case RC_ERROR_HMML: + case RC_ERROR_MAX_REFS: + case RC_ERROR_QUOTE: + case RC_INVALID_REFERENCE: + return RC_ERROR_HMML; + case RC_SUCCESS: + break; + }; - if(!Found) { ++Index.Header.EntryCount; } + Index->Entry.Size = CollationBuffers->Search.Ptr - CollationBuffers->Search.Location; + ClearCopyStringNoFormat(Index->Entry.BaseFilename, sizeof(Index->Entry.BaseFilename), BaseFilename); + ClearCopyStringNoFormat(Index->Entry.Title, sizeof(Index->Entry.Title), CollationBuffers->Title); - fwrite(&Index.Header, sizeof(Index.Header), 1, Index.Metadata.Handle); + if(!(Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"))) { FreeBuffer(&Index->Metadata.Buffer); return RC_ERROR_FILE; } + if(!(Index->File.Handle = fopen(Index->File.Path, "w"))) { FreeBuffer(&Index->File.Buffer); return RC_ERROR_FILE; } + + if(!Found) { ++Index->Header.EntryCount; } + + fwrite(&Index->Header, sizeof(Index->Header), 1, Index->Metadata.Handle); if(IndexMetadataFileReadCode == RC_SUCCESS) { - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location + sizeof(Index.Header); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(Index->Header); } if(Found) @@ -4176,13 +4412,13 @@ InsertIntoIndex(buffers *CollationBuffers, template **BespokeTemplate, char *Bas // a file triggers an IN_DELETE followed by an IN_CLOSE_WRITE event // Reinsert - fwrite(Index.Metadata.Buffer.Ptr, MetadataInsertionOffset - sizeof(Index.Header), 1, Index.Metadata.Handle); - fwrite(&Index.Entry, sizeof(Index.Entry), 1, Index.Metadata.Handle); - fwrite(Index.Metadata.Buffer.Ptr - sizeof(Index.Header) + MetadataInsertionOffset + sizeof(Index.Entry), Index.Metadata.FileSize - MetadataInsertionOffset - sizeof(Index.Entry), 1, Index.Metadata.Handle); + fwrite(Index->Metadata.Buffer.Ptr, MetadataInsertionOffset - sizeof(Index->Header), 1, Index->Metadata.Handle); + fwrite(&Index->Entry, sizeof(Index->Entry), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Ptr - sizeof(Index->Header) + MetadataInsertionOffset + sizeof(Index->Entry), Index->Metadata.FileSize - MetadataInsertionOffset - sizeof(Index->Entry), 1, Index->Metadata.Handle); - fwrite(Index.File.Buffer.Location, IndexEntryInsertionStart, 1, Index.File.Handle); - fwrite(CollationBuffers->Search.Location, Index.Entry.Size, 1, Index.File.Handle); - fwrite(Index.File.Buffer.Location + IndexEntryInsertionEnd, Index.File.FileSize - IndexEntryInsertionEnd, 1, Index.File.Handle); + fwrite(Index->File.Buffer.Location, IndexEntryInsertionStart, 1, Index->File.Handle); + fwrite(CollationBuffers->Search.Location, Index->Entry.Size, 1, Index->File.Handle); + fwrite(Index->File.Buffer.Location + IndexEntryInsertionEnd, Index->File.FileSize - IndexEntryInsertionEnd, 1, Index->File.Handle); LogError(LOG_NOTICE, "Reinserted %s - %s", BaseFilename, CollationBuffers->Title); fprintf(stderr, "\e[1;33mReinserted\e[0m %s - %s\n", BaseFilename, CollationBuffers->Title); @@ -4190,13 +4426,13 @@ InsertIntoIndex(buffers *CollationBuffers, template **BespokeTemplate, char *Bas else if(MetadataInsertionOffset >= 0 && IndexEntryInsertionStart >= 0) { // Insert new - fwrite(Index.Metadata.Buffer.Ptr, MetadataInsertionOffset - sizeof(Index.Header), 1, Index.Metadata.Handle); - fwrite(&Index.Entry, sizeof(Index.Entry), 1, Index.Metadata.Handle); - fwrite(Index.Metadata.Buffer.Ptr - sizeof(Index.Header) + MetadataInsertionOffset, Index.Metadata.FileSize - MetadataInsertionOffset, 1, Index.Metadata.Handle); + fwrite(Index->Metadata.Buffer.Ptr, MetadataInsertionOffset - sizeof(Index->Header), 1, Index->Metadata.Handle); + fwrite(&Index->Entry, sizeof(Index->Entry), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Ptr - sizeof(Index->Header) + MetadataInsertionOffset, Index->Metadata.FileSize - MetadataInsertionOffset, 1, Index->Metadata.Handle); - fwrite(Index.File.Buffer.Location, IndexEntryInsertionStart, 1, Index.File.Handle); - fwrite(CollationBuffers->Search.Location, Index.Entry.Size, 1, Index.File.Handle); - fwrite(Index.File.Buffer.Location + IndexEntryInsertionStart, Index.File.FileSize - IndexEntryInsertionStart, 1, Index.File.Handle); + fwrite(Index->File.Buffer.Location, IndexEntryInsertionStart, 1, Index->File.Handle); + fwrite(CollationBuffers->Search.Location, Index->Entry.Size, 1, Index->File.Handle); + fwrite(Index->File.Buffer.Location + IndexEntryInsertionStart, Index->File.FileSize - IndexEntryInsertionStart, 1, Index->File.Handle); LogError(LOG_NOTICE, "Inserted %s - %s", BaseFilename, CollationBuffers->Title); fprintf(stderr, "\e[1;32mInserted\e[0m %s - %s\n", BaseFilename, CollationBuffers->Title); @@ -4206,24 +4442,24 @@ InsertIntoIndex(buffers *CollationBuffers, template **BespokeTemplate, char *Bas // Append new if(IndexMetadataFileReadCode == RC_SUCCESS) { - fwrite(Index.Metadata.Buffer.Ptr, Index.Metadata.FileSize - sizeof(Index.Header), 1, Index.Metadata.Handle); - fwrite(Index.File.Buffer.Location, Index.File.FileSize, 1, Index.File.Handle); + fwrite(Index->Metadata.Buffer.Ptr, Index->Metadata.FileSize - sizeof(Index->Header), 1, Index->Metadata.Handle); + fwrite(Index->File.Buffer.Location, Index->File.FileSize, 1, Index->File.Handle); } else { - fprintf(Index.File.Handle, "---\n"); + fprintf(Index->File.Handle, "---\n"); } - fwrite(&Index.Entry, sizeof(Index.Entry), 1, Index.Metadata.Handle); - fwrite(CollationBuffers->Search.Location, Index.Entry.Size, 1, Index.File.Handle); + fwrite(&Index->Entry, sizeof(Index->Entry), 1, Index->Metadata.Handle); + fwrite(CollationBuffers->Search.Location, Index->Entry.Size, 1, Index->File.Handle); LogError(LOG_NOTICE, "Appended %s - %s", BaseFilename, CollationBuffers->Title); fprintf(stderr, "\e[1;32mAppended\e[0m %s - %s\n", BaseFilename, CollationBuffers->Title); } - fclose(Index.Metadata.Handle); - fclose(Index.File.Handle); - FreeBuffer(&Index.Metadata.Buffer); - FreeBuffer(&Index.File.Buffer); - return RC_SUCCESS; + fclose(Index->Metadata.Handle); + fclose(Index->File.Handle); + FreeBuffer(&Index->Metadata.Buffer); + FreeBuffer(&Index->File.Buffer); + return Found ? RC_SUCCESS : RC_UNFOUND; } void @@ -4257,15 +4493,338 @@ ConstructDirectoryPath(buffer *DirectoryPath, int PageType, char *PageLocation, } } +enum +{ + LINK_INCLUDE, + LINK_EXCLUDE +} link_types; + +enum +{ + LINK_PREV, + LINK_NEXT +} link_directions; + +int InsertNeighbourLink(file_buffer *FromFile, index_metadata *From, index_metadata *To, int LinkDirection, char *ProjectName, bool FromHasOneNeighbour) +{ + if(ReadFileIntoBuffer(FromFile, 0) == RC_SUCCESS) + { + if(!(FromFile->Handle = fopen(FromFile->Path, "w"))) { FreeBuffer(&FromFile->Buffer); return RC_ERROR_FILE; }; + + buffer Link; + ClaimBuffer(&Link, "Link", 4096); + + buffer ToPlayerURL; + if(To) + { + ClaimBuffer(&ToPlayerURL, "ToPlayerURL", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH); + ConstructPlayerURL(&ToPlayerURL, To->BaseFilename); + } + + switch(LinkDirection) + { + case LINK_PREV: + { + int NewPrevEnd = 0; + int NewNextEnd = 0; + fwrite(FromFile->Buffer.Location, From->LinkOffsets.PrevStart, 1, FromFile->Handle); + if(To) + { + CopyStringToBuffer(&Link, + "
Previous: '%s'
\n", + ToPlayerURL.Location, + To->Title); + } + else + { + CopyStringToBuffer(&Link, + "
Welcome to %s
\n", ProjectName); + } + NewPrevEnd = Link.Ptr - Link.Location; + fwrite(Link.Location, (Link.Ptr - Link.Location), 1, FromFile->Handle); + if(FromHasOneNeighbour) + { + fwrite(FromFile->Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, From->LinkOffsets.NextStart, 1, FromFile->Handle); + RewindBuffer(&Link); + CopyStringToBuffer(&Link, + "
You have arrived at the (current) end of %s
\n", ProjectName); + NewNextEnd = Link.Ptr - Link.Location; + fwrite(Link.Location, NewNextEnd, 1, FromFile->Handle); + fwrite(FromFile->Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd, + FromFile->FileSize - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd), + 1, + FromFile->Handle); + } + else + { + fwrite(FromFile->Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, + FromFile->FileSize - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd), + 1, + FromFile->Handle); + } + + From->LinkOffsets.PrevEnd = NewPrevEnd; + if(FromHasOneNeighbour) { From->LinkOffsets.NextEnd = NewNextEnd; } + } break; + case LINK_NEXT: + { + int NewPrevEnd = 0; + int NewNextEnd = 0; + if(FromHasOneNeighbour) + { + fwrite(FromFile->Buffer.Location, From->LinkOffsets.PrevStart, 1, FromFile->Handle); + CopyStringToBuffer(&Link, + "
Welcome to %s
\n", ProjectName); + NewPrevEnd = Link.Ptr - Link.Location; + fwrite(Link.Location, NewPrevEnd, 1, FromFile->Handle); + RewindBuffer(&Link); + fwrite(FromFile->Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, + From->LinkOffsets.NextStart, 1, FromFile->Handle); + } + else + { + fwrite(FromFile->Buffer.Location, From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart, 1, FromFile->Handle); + } + + if(To) + { + CopyStringToBuffer(&Link, + "
Next: '%s'
\n", + ToPlayerURL.Location, + To->Title); + } + else + { + CopyStringToBuffer(&Link, + "
You have arrived at the (current) end of %s
\n", ProjectName); + } + NewNextEnd = Link.Ptr - Link.Location; + fwrite(Link.Location, (Link.Ptr - Link.Location), 1, FromFile->Handle); + + fwrite(FromFile->Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd, + FromFile->FileSize - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd), + 1, + FromFile->Handle); + + if(FromHasOneNeighbour) { From->LinkOffsets.PrevEnd = NewPrevEnd; } + From->LinkOffsets.NextEnd = NewNextEnd; + } break; + } + + if(To) { DeclaimBuffer(&ToPlayerURL); } + DeclaimBuffer(&Link); + fclose(FromFile->Handle); + FreeBuffer(&FromFile->Buffer); + return RC_SUCCESS; + } + else + { + return RC_ERROR_FILE; + } +} + +int DeleteNeighbourLinks(file_buffer *File, index_metadata *Metadata) +{ + if(ReadFileIntoBuffer(File, 0) == RC_SUCCESS) + { + if(!(File->Handle = fopen(File->Path, "w"))) { FreeBuffer(&File->Buffer); return RC_ERROR_FILE; }; + + fwrite(File->Buffer.Location, Metadata->LinkOffsets.PrevStart, 1, File->Handle); + fwrite(File->Buffer.Location + Metadata->LinkOffsets.PrevStart + Metadata->LinkOffsets.PrevEnd, Metadata->LinkOffsets.NextStart, 1, File->Handle); + fwrite(File->Buffer.Location + Metadata->LinkOffsets.PrevStart + Metadata->LinkOffsets.PrevEnd + Metadata->LinkOffsets.NextStart + Metadata->LinkOffsets.NextEnd, + File->FileSize - (Metadata->LinkOffsets.PrevStart + Metadata->LinkOffsets.PrevEnd + Metadata->LinkOffsets.NextStart + Metadata->LinkOffsets.NextEnd), + 1, + File->Handle); + fclose(File->Handle); + Metadata->LinkOffsets.PrevEnd = 0; + Metadata->LinkOffsets.NextEnd = 0; + FreeBuffer(&File->Buffer); + return RC_SUCCESS; + } + else + { + return RC_ERROR_FILE; + } +} + +int LinkNeighbours(index *Index, char *BaseFilename, int LinkType) +{ + switch(ReadFileIntoBuffer(&Index->Metadata, 0)) + { + case RC_ERROR_FILE: + return RC_ERROR_FILE; + case RC_ERROR_MEMORY: + LogError(LOG_ERROR, "DeleteFromIndex(): %s", strerror(errno)); + return RC_ERROR_MEMORY; + case RC_SUCCESS: + break; + } + + index_header Header = *(index_header *)Index->Metadata.Buffer.Ptr; + Index->Metadata.Buffer.Ptr += sizeof(Header); + index_metadata *Prev = { 0 }; + index_metadata *This = { 0 }; + index_metadata *Next = { 0 }; + int EntryIndex = 0; + + switch(LinkType) + { + case LINK_INCLUDE: + { + for(EntryIndex = 0; EntryIndex < Header.EntryCount; ++EntryIndex) + { + This = (index_metadata *)Index->Metadata.Buffer.Ptr; + if(!StringsDiffer(This->BaseFilename, BaseFilename)) + { + if(EntryIndex < (Header.EntryCount - 1)) + { + Next = (index_metadata *)(Index->Metadata.Buffer.Ptr + sizeof(index_metadata)); + } + break; + } + Prev = This; + Index->Metadata.Buffer.Ptr += sizeof(index_metadata); + } + } break; + case LINK_EXCLUDE: + { + if(Index->Header.EntryCount == 1) + { + This = (index_metadata *)Index->Metadata.Buffer.Ptr; + } + else + { + This = (index_metadata *)Index->Metadata.Buffer.Ptr; + Index->Metadata.Buffer.Ptr += sizeof(index_metadata); + Next = (index_metadata *)Index->Metadata.Buffer.Ptr; + + for(EntryIndex = 1; EntryIndex < Header.EntryCount; ++EntryIndex) + { + Prev = This; + This = Next; + if(EntryIndex < (Index->Header.EntryCount - 1)) + { + Index->Metadata.Buffer.Ptr += sizeof(index_metadata); + Next = (index_metadata *)Index->Metadata.Buffer.Ptr; + } + else { Next = 0; } + if(StringsDiffer(BaseFilename, This->BaseFilename) < 0) + { + break; + } + } + } + } break; + } + + if(!Prev && !Next) + { + buffer ThisPlayerPagePath; + ClaimBuffer(&ThisPlayerPagePath, "ThisPlayerPagePath", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10); + ConstructDirectoryPath(&ThisPlayerPagePath, PAGE_PLAYER, Config.PlayerLocation, This->BaseFilename); + CopyStringToBuffer(&ThisPlayerPagePath, "/index.html"); + + file_buffer ThisPlayerPage; + CopyStringNoFormat(ThisPlayerPage.Path, ThisPlayerPagePath.Location); + + DeleteNeighbourLinks(&ThisPlayerPage, This); + + DeclaimBuffer(&ThisPlayerPagePath); + + Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"); + fwrite(Index->Metadata.Buffer.Location, ((char *)This - Index->Metadata.Buffer.Location), 1, Index->Metadata.Handle); + fwrite(This, sizeof(index_metadata), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Location + ((char *)This - Index->Metadata.Buffer.Location) + sizeof(index_metadata), + Index->Metadata.FileSize - (((char *)This - Index->Metadata.Buffer.Location) + sizeof(index_metadata)), + 1, Index->Metadata.Handle); + fclose(Index->Metadata.Handle); + } + + if(Prev) + { + buffer PreviousPlayerPagePath; + ClaimBuffer(&PreviousPlayerPagePath, "PreviousPlayerPagePath", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10); + ConstructDirectoryPath(&PreviousPlayerPagePath, PAGE_PLAYER, Config.PlayerLocation, Prev->BaseFilename); + CopyStringToBuffer(&PreviousPlayerPagePath, "/index.html"); + + file_buffer PreviousPlayerPage; + CopyStringNoFormat(PreviousPlayerPage.Path, PreviousPlayerPagePath.Location); + + switch(LinkType) + { + case LINK_EXCLUDE: + { + buffer ThisPlayerPagePath; + ClaimBuffer(&ThisPlayerPagePath, "ThisPlayerPagePath", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10); + ConstructDirectoryPath(&ThisPlayerPagePath, PAGE_PLAYER, Config.PlayerLocation, This->BaseFilename); + CopyStringToBuffer(&ThisPlayerPagePath, "/index.html"); + + file_buffer ThisPlayerPage; + CopyStringNoFormat(ThisPlayerPage.Path, ThisPlayerPagePath.Location); + + InsertNeighbourLink(&ThisPlayerPage, This, Prev, LINK_PREV, Index->Header.ProjectName, Next ? FALSE : TRUE); + + DeclaimBuffer(&ThisPlayerPagePath); + + Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"); + fwrite(Index->Metadata.Buffer.Location, ((char* )This - Index->Metadata.Buffer.Location), 1, Index->Metadata.Handle); + fwrite(This, sizeof(index_metadata), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Location + ((char* )This - Index->Metadata.Buffer.Location) + sizeof(index_metadata), + Index->Metadata.FileSize - (((char* )This - Index->Metadata.Buffer.Location) + sizeof(index_metadata)), + 1, Index->Metadata.Handle); + fclose(Index->Metadata.Handle); + + if(EntryIndex == Index->Header.EntryCount) { break; } + } + case LINK_INCLUDE: + { + InsertNeighbourLink(&PreviousPlayerPage, Prev, This, LINK_NEXT, Index->Header.ProjectName, EntryIndex == 1 ? TRUE : FALSE); + } break; + } + + DeclaimBuffer(&PreviousPlayerPagePath); + + Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"); + fwrite(Index->Metadata.Buffer.Location, ((char *)Prev - Index->Metadata.Buffer.Location), 1, Index->Metadata.Handle); + fwrite(Prev, sizeof(index_metadata), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Location + ((char *)Prev - Index->Metadata.Buffer.Location) + sizeof(index_metadata), + Index->Metadata.FileSize - (((char *)Prev - Index->Metadata.Buffer.Location) + sizeof(index_metadata)), + 1, Index->Metadata.Handle); + fclose(Index->Metadata.Handle); + } + + if(Next && LinkType == LINK_INCLUDE) + { + buffer NextPlayerPagePath; + ClaimBuffer(&NextPlayerPagePath, "NextPlayerPagePath", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10); + ConstructDirectoryPath(&NextPlayerPagePath, PAGE_PLAYER, Config.PlayerLocation, Next->BaseFilename); + CopyStringToBuffer(&NextPlayerPagePath, "/index.html"); + + file_buffer NextPlayerPage; + CopyStringNoFormat(NextPlayerPage.Path, NextPlayerPagePath.Location); + + InsertNeighbourLink(&NextPlayerPage, Next, This, LINK_PREV, Index->Header.ProjectName, EntryIndex == (Index->Header.EntryCount - 2) ? TRUE : FALSE); + + DeclaimBuffer(&NextPlayerPagePath); + + Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"); + fwrite(Index->Metadata.Buffer.Location, ((char* )Next - Index->Metadata.Buffer.Location), 1, Index->Metadata.Handle); + fwrite(Next, sizeof(index_metadata), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Location + ((char* )Next - Index->Metadata.Buffer.Location) + sizeof(index_metadata), + Index->Metadata.FileSize - (((char* )Next - Index->Metadata.Buffer.Location) + sizeof(index_metadata)), + 1, Index->Metadata.Handle); + fclose(Index->Metadata.Handle); + } + + FreeBuffer(&Index->Metadata.Buffer); + return RC_SUCCESS; +} + int -DeleteFromIndex(char *BaseFilename) +DeleteFromIndex(index *Index, char *BaseFilename) { // TODO(matt): LogError() - index Index; - - Index.Metadata.Buffer.ID = "IndexMetadata"; - CopyString(Index.Metadata.Path, "%s/%s.metadata", Config.BaseDir, Config.ProjectID); - switch(ReadFileIntoBuffer(&Index.Metadata, 0)) + switch(ReadFileIntoBuffer(&Index->Metadata, 0)) { case RC_ERROR_FILE: return RC_ERROR_FILE; @@ -4276,9 +4835,7 @@ DeleteFromIndex(char *BaseFilename) break; } - Index.File.Buffer.ID = "Index"; - CopyString(Index.File.Path, "%s/%s.index", Config.BaseDir, Config.ProjectID); - switch(ReadFileIntoBuffer(&Index.File, 0)) + switch(ReadFileIntoBuffer(&Index->File, 0)) { case RC_ERROR_FILE: return RC_ERROR_FILE; @@ -4289,8 +4846,8 @@ DeleteFromIndex(char *BaseFilename) break; } - Index.Header = *(index_header *)Index.Metadata.Buffer.Ptr; - Index.Metadata.Buffer.Ptr += sizeof(Index.Header); + Index->Header = *(index_header *)Index->Metadata.Buffer.Ptr; + Index->Metadata.Buffer.Ptr += sizeof(Index->Header); bool Found = FALSE; int DeleteMetadataFrom = -1; @@ -4298,14 +4855,14 @@ DeleteFromIndex(char *BaseFilename) int DeleteFileTo = -1; int SizeAcc = 0; - for(int EntryIndex = 0; EntryIndex < Index.Header.EntryCount; ++EntryIndex, Index.Metadata.Buffer.Ptr += sizeof(index_metadata)) + for(int EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex, Index->Metadata.Buffer.Ptr += sizeof(index_metadata)) { - index_metadata This = *(index_metadata *)Index.Metadata.Buffer.Ptr; + index_metadata This = *(index_metadata *)Index->Metadata.Buffer.Ptr; if(!StringsDiffer(This.BaseFilename, BaseFilename)) { Found = TRUE; - --Index.Header.EntryCount; - DeleteMetadataFrom = Index.Metadata.Buffer.Ptr - Index.Metadata.Buffer.Location; + --Index->Header.EntryCount; + DeleteMetadataFrom = Index->Metadata.Buffer.Ptr - Index->Metadata.Buffer.Location; DeleteFileFrom = StringLength("---\n") + SizeAcc; DeleteFileTo = DeleteFileFrom + This.Size; break; @@ -4315,7 +4872,7 @@ DeleteFromIndex(char *BaseFilename) if(Found) { - if(Index.Header.EntryCount == 0) + if(Index->Header.EntryCount == 0) { buffer IndexDirectory; ClaimBuffer(&IndexDirectory, "IndexDirectory", 1024); @@ -4325,53 +4882,47 @@ DeleteFromIndex(char *BaseFilename) remove(IndexPagePath); remove(IndexDirectory.Location); DeclaimBuffer(&IndexDirectory); - remove(Index.Metadata.Path); - remove(Index.File.Path); + remove(Index->Metadata.Path); + remove(Index->File.Path); } else { - if(!(Index.Metadata.Handle = fopen(Index.Metadata.Path, "w"))) { FreeBuffer(&Index.Metadata.Buffer); return RC_ERROR_FILE; } - if(!(Index.File.Handle = fopen(Index.File.Path, "w"))) { FreeBuffer(&Index.File.Buffer); return RC_ERROR_FILE; } + if(!(Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"))) { FreeBuffer(&Index->Metadata.Buffer); return RC_ERROR_FILE; } + if(!(Index->File.Handle = fopen(Index->File.Path, "w"))) { FreeBuffer(&Index->File.Buffer); return RC_ERROR_FILE; } - fwrite(&Index.Header, sizeof(Index.Header), 1, Index.Metadata.Handle); - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location + sizeof(Index.Header); + fwrite(&Index->Header, sizeof(Index->Header), 1, Index->Metadata.Handle); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(Index->Header); - fwrite(Index.Metadata.Buffer.Ptr, DeleteMetadataFrom - sizeof(Index.Header), 1, Index.Metadata.Handle); - fwrite(Index.Metadata.Buffer.Ptr + DeleteMetadataFrom - sizeof(Index.Header) + sizeof(Index.Entry), Index.Metadata.FileSize - DeleteMetadataFrom - sizeof(Index.Entry), 1, Index.Metadata.Handle); - fclose(Index.Metadata.Handle); + fwrite(Index->Metadata.Buffer.Ptr, DeleteMetadataFrom - sizeof(Index->Header), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Ptr + DeleteMetadataFrom - sizeof(Index->Header) + sizeof(Index->Entry), Index->Metadata.FileSize - DeleteMetadataFrom - sizeof(Index->Entry), 1, Index->Metadata.Handle); + fclose(Index->Metadata.Handle); - fwrite(Index.File.Buffer.Location, DeleteFileFrom, 1, Index.File.Handle); - fwrite(Index.File.Buffer.Location + DeleteFileTo, Index.File.FileSize - DeleteFileTo, 1, Index.File.Handle); - fclose(Index.File.Handle); + fwrite(Index->File.Buffer.Location, DeleteFileFrom, 1, Index->File.Handle); + fwrite(Index->File.Buffer.Location + DeleteFileTo, Index->File.FileSize - DeleteFileTo, 1, Index->File.Handle); + fclose(Index->File.Handle); } } - FreeBuffer(&Index.Metadata.Buffer); - FreeBuffer(&Index.File.Buffer); + FreeBuffer(&Index->Metadata.Buffer); + FreeBuffer(&Index->File.Buffer); return Found ? RC_SUCCESS : RC_NOOP; } int -IndexToBuffer(buffers *CollationBuffers) // NOTE(matt): This guy malloc's CollationBuffers->Index +IndexToBuffer(index *Index, buffers *CollationBuffers) // NOTE(matt): This guy malloc's CollationBuffers->Index { // TODO(matt): Consider parsing the index into a linked / skip list, or do something to save us having to iterate through // the index file multiple times - index Index; - Index.Metadata.Buffer.ID = "IndexMetadata"; - CopyString(Index.Metadata.Path, "%s/%s.metadata", Config.BaseDir, Config.ProjectID); - int IndexMetadataFileReadCode = ReadFileIntoBuffer(&Index.Metadata, 0); - - Index.File.Buffer.ID = "IndexFile"; - CopyString(Index.File.Path, "%s/%s.index", Config.BaseDir, Config.ProjectID); - int IndexFileReadCode = ReadFileIntoBuffer(&Index.File, 0); + int IndexMetadataFileReadCode = ReadFileIntoBuffer(&Index->Metadata, 0); + int IndexFileReadCode = ReadFileIntoBuffer(&Index->File, 0); if(IndexMetadataFileReadCode == RC_SUCCESS && IndexFileReadCode == RC_SUCCESS) { - Index.Header = *(index_header*)Index.Metadata.Buffer.Ptr; - Index.Metadata.Buffer.Ptr += sizeof(Index.Header); - Index.File.Buffer.Ptr += StringLength("---\n"); - char *IndexEntryStart = Index.File.Buffer.Ptr; + Index->Header = *(index_header*)Index->Metadata.Buffer.Ptr; + Index->Metadata.Buffer.Ptr += sizeof(Index->Header); + Index->File.Buffer.Ptr += StringLength("---\n"); + char *IndexEntryStart = Index->File.Buffer.Ptr; bool ProjectFound = FALSE; int ProjectIndex; @@ -4387,8 +4938,8 @@ IndexToBuffer(buffers *CollationBuffers) // NOTE(matt): This guy malloc's Collat if(!ProjectFound) { fprintf(stderr, "Missing Project Info for %s\n", Config.ProjectID); - FreeBuffer(&Index.Metadata.Buffer); - FreeBuffer(&Index.File.Buffer); + FreeBuffer(&Index->Metadata.Buffer); + FreeBuffer(&Index->File.Buffer); return RC_ERROR_PROJECT; } @@ -4418,7 +4969,7 @@ IndexToBuffer(buffers *CollationBuffers) // NOTE(matt): This guy malloc's Collat ConstructURLPrefix(&URLPrefix, INCLUDE_JS, PAGE_INDEX); buffer PlayerURL; - ClaimBuffer(&PlayerURL, "PlayerURL", 4096); + ClaimBuffer(&PlayerURL, "PlayerURL", MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH); ConstructPlayerURL(&PlayerURL, ""); char Script[532 + StringLength(URLPrefix.Location) + (StringLength(Config.ProjectID) * 2)]; @@ -4444,21 +4995,21 @@ IndexToBuffer(buffers *CollationBuffers) // NOTE(matt): This guy malloc's Collat int EntryLength = 32 + StringLength(ProjectInfo[ProjectIndex].Unit) + 16 + 256; - CollationBuffers->Index.Size = StringLength(queryContainer) + (Index.Header.EntryCount * EntryLength) + StringLength(Script); + CollationBuffers->Index.Size = StringLength(queryContainer) + (Index->Header.EntryCount * EntryLength) + StringLength(Script); if(!(CollationBuffers->Index.Location = malloc(CollationBuffers->Index.Size))) { - FreeBuffer(&Index.Metadata.Buffer); - FreeBuffer(&Index.File.Buffer); + FreeBuffer(&Index->Metadata.Buffer); + FreeBuffer(&Index->File.Buffer); return(RC_ERROR_MEMORY); } CollationBuffers->Index.Ptr = CollationBuffers->Index.Location; CopyStringToBuffer(&CollationBuffers->Index, queryContainer); - for(int EntryIndex = 0; EntryIndex < Index.Header.EntryCount; ++EntryIndex) + for(int EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex) { - index_metadata This = *(index_metadata *)Index.Metadata.Buffer.Ptr; + index_metadata This = *(index_metadata *)Index->Metadata.Buffer.Ptr; char Number[16]; CopyString(Number, This.BaseFilename + StringLength(Config.ProjectID)); if(ProjectInfo[ProjectIndex].NumberingScheme == NS_LINEAR) @@ -4472,9 +5023,9 @@ IndexToBuffer(buffers *CollationBuffers) // NOTE(matt): This guy malloc's Collat } } - SeekBufferForString(&Index.File.Buffer, "title: \"", C_SEEK_FORWARDS, C_SEEK_AFTER); + SeekBufferForString(&Index->File.Buffer, "title: \"", C_SEEK_FORWARDS, C_SEEK_AFTER); char Title[256]; - CopyStringNoFormatT(Title, Index.File.Buffer.Ptr, '\n'); + CopyStringNoFormatT(Title, Index->File.Buffer.Ptr, '\n'); Title[StringLength(Title) - 1] = '\0'; ConstructPlayerURL(&PlayerURL, This.BaseFilename); @@ -4507,17 +5058,17 @@ IndexToBuffer(buffers *CollationBuffers) // NOTE(matt): This guy malloc's Collat Title); } - Index.Metadata.Buffer.Ptr += sizeof(Index.Entry); + Index->Metadata.Buffer.Ptr += sizeof(Index->Entry); IndexEntryStart += This.Size; - Index.File.Buffer.Ptr = IndexEntryStart; + Index->File.Buffer.Ptr = IndexEntryStart; } DeclaimBuffer(&PlayerURL); CopyStringToBuffer(&CollationBuffers->Index, Script); - FreeBuffer(&Index.Metadata.Buffer); - FreeBuffer(&Index.File.Buffer); + FreeBuffer(&Index->Metadata.Buffer); + FreeBuffer(&Index->File.Buffer); return RC_SUCCESS; } else @@ -4559,7 +5110,7 @@ StripSurroundingSlashes(char *String) // NOTE(matt): For relative paths } int -GeneratePlayerPage(buffers *CollationBuffers, template *PlayerTemplate, char *BaseFilename) +GeneratePlayerPage(index *Index, buffers *CollationBuffers, template *PlayerTemplate, char *BaseFilename) { buffer OutputDirectoryPath; ClaimBuffer(&OutputDirectoryPath, "OutputDirectoryPath", 1024); @@ -4587,11 +5138,35 @@ GeneratePlayerPage(buffers *CollationBuffers, template *PlayerTemplate, char *Ba if(PlayerTemplate->Metadata.Tag[TagIndex].TagCode == TAG_INDEX) { IndexInTemplate = TRUE; - IndexToBuffer(CollationBuffers); + IndexToBuffer(Index, CollationBuffers); break; } } - BuffersToHTML(CollationBuffers, PlayerTemplate, PlayerPagePath, PAGE_PLAYER); + int PlayerOffset = 0; // NOTE(matt): Could just straight up pass the LinkOffsets.PrevStart directly... + BuffersToHTML(CollationBuffers, PlayerTemplate, PlayerPagePath, PAGE_PLAYER, &PlayerOffset); + Index->Entry.LinkOffsets.PrevStart += PlayerOffset; + + ReadFileIntoBuffer(&Index->Metadata, 0); + Index->Metadata.Buffer.Ptr += sizeof(index_header); + int MetadataInsertionOffset = 0; + for(int EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex) + { + index_metadata This = *(index_metadata *)Index->Metadata.Buffer.Ptr; + if(!StringsDiffer(This.BaseFilename, Index->Entry.BaseFilename)) + { + MetadataInsertionOffset = (Index->Metadata.Buffer.Ptr - Index->Metadata.Buffer.Location); + break; + } + Index->Metadata.Buffer.Ptr += sizeof(index_metadata); + } + + if(!(Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"))) { FreeBuffer(&Index->Metadata.Buffer); return RC_ERROR_FILE; } + fwrite(Index->Metadata.Buffer.Location, MetadataInsertionOffset, 1, Index->Metadata.Handle); + fwrite(&Index->Entry, sizeof(Index->Entry), 1, Index->Metadata.Handle); + fwrite(Index->Metadata.Buffer.Ptr + sizeof(Index->Entry), Index->Metadata.FileSize - MetadataInsertionOffset - sizeof(Index->Entry), 1, Index->Metadata.Handle); + fclose(Index->Metadata.Handle); + FreeBuffer(&Index->Metadata.Buffer); + if(IndexInTemplate) { FreeBuffer(&CollationBuffers->Index); @@ -4600,7 +5175,7 @@ GeneratePlayerPage(buffers *CollationBuffers, template *PlayerTemplate, char *Ba } int -GenerateIndexPage(buffers *CollationBuffers, template *IndexTemplate) +GenerateIndexPage(index *Index, buffers *CollationBuffers, template *IndexTemplate) { buffer OutputDirectoryPath; ClaimBuffer(&OutputDirectoryPath, "OutputDirectoryPath", 1024); @@ -4621,8 +5196,8 @@ GenerateIndexPage(buffers *CollationBuffers, template *IndexTemplate) char IndexPagePath[1024]; CopyString(IndexPagePath, "%s/index.html", OutputDirectoryPath.Location); DeclaimBuffer(&OutputDirectoryPath); - IndexToBuffer(CollationBuffers); - BuffersToHTML(CollationBuffers, IndexTemplate, IndexPagePath, PAGE_INDEX); + IndexToBuffer(Index, CollationBuffers); + BuffersToHTML(CollationBuffers, IndexTemplate, IndexPagePath, PAGE_INDEX, 0); FreeBuffer(&CollationBuffers->Index); return RC_SUCCESS; } @@ -4668,16 +5243,17 @@ DeletePlayerPageFromFilesystem(char *BaseFilename, char *PlayerLocation, bool Re } void -DeleteEntry(char *BaseFilename) +DeleteEntry(index *Index, char *BaseFilename) { - if(DeleteFromIndex(BaseFilename) == RC_SUCCESS) + if(DeleteFromIndex(Index, BaseFilename) == RC_SUCCESS) { + LinkNeighbours(Index, BaseFilename, LINK_EXCLUDE); DeletePlayerPageFromFilesystem(BaseFilename, Config.PlayerLocation, FALSE); } } int -MonitorDirectory(buffers *CollationBuffers, template *IndexTemplate, template *PlayerTemplate, template *BespokeTemplate, int inotifyInstance, int WatchDescriptor) +MonitorDirectory(index *Index, buffers *CollationBuffers, template *IndexTemplate, template *PlayerTemplate, template *BespokeTemplate, int inotifyInstance, int WatchDescriptor) { #if DEBUG_MEM @@ -4687,63 +5263,51 @@ MonitorDirectory(buffers *CollationBuffers, template *IndexTemplate, template *P #endif buffer Events; - // TODO(matt): Figure out the max size necessary for the Events buffer - if(ClaimBuffer(&Events, "inotify Events", Kilobytes(1024)) == RC_ARENA_FULL) { return RC_ARENA_FULL; }; + if(ClaimBuffer(&Events, "inotify Events", Kilobytes(1)) == RC_ARENA_FULL) { return RC_ARENA_FULL; }; struct inotify_event *Event; - int BytesRead; + int BytesRead = read(inotifyInstance, Events.Location, Events.Size); + if(inotifyInstance < 0) { perror("MonitorDirectory()"); } - while((BytesRead = read(inotifyInstance, Events.Location, Events.Size)) != -1 && errno == EAGAIN && BytesRead > 0) + for(Events.Ptr = Events.Location; + Events.Ptr < Events.Location + BytesRead && Events.Ptr - Events.Location < Events.Size; + Events.Ptr += sizeof(struct inotify_event) + Event->len) { - for(Events.Ptr = Events.Location; - Events.Ptr < Events.Location + BytesRead && Events.Ptr - Events.Location < Events.Size; - Events.Ptr += sizeof(struct inotify_event) + Event->len) + Event = (struct inotify_event *)Events.Ptr; + char *Ptr; + Ptr = Event->name; + Ptr += (StringLength(Event->name) - StringLength(".hmml")); + if(!(StringsDiffer(Ptr, ".hmml"))) { -NextEvent: - Event = (struct inotify_event *)Events.Ptr; - char *Ptr; - Ptr = Event->name; - Ptr += (StringLength(Event->name) - StringLength(".hmml")); - if(!(StringsDiffer(Ptr, ".hmml"))) + *Ptr = '\0'; + char BaseFilename[256]; + CopyString(BaseFilename, Event->name); + *Ptr = '.'; + + // TODO(matt): Maybe handle IN_ALL_EVENTS + if(Event->mask & IN_DELETE || Event->mask & IN_MOVED_FROM) { - *Ptr = '\0'; - char BaseFilename[256]; - CopyString(BaseFilename, Event->name); - *Ptr = '.'; - - // TODO(matt): Maybe handle IN_ALL_EVENTS - - if(Event->mask & IN_DELETE || Event->mask & IN_MOVED_FROM) + DeleteEntry(Index, BaseFilename); + } + else + { + switch(InsertIntoIndex(Index, CollationBuffers, &BespokeTemplate, BaseFilename)) { - DeleteEntry(BaseFilename); - } - else - { - if(InsertIntoIndex(CollationBuffers, &BespokeTemplate, BaseFilename) == RC_SUCCESS) - { - if(BespokeTemplate->Metadata.Filename && StringsDiffer(BespokeTemplate->Metadata.Filename, "")) + case RC_SUCCESS: + case RC_UNFOUND: + LinkNeighbours(Index, BaseFilename, LINK_INCLUDE); { - GeneratePlayerPage(CollationBuffers, BespokeTemplate, BaseFilename); - DeclaimTemplate(BespokeTemplate); - } - else - { - GeneratePlayerPage(CollationBuffers, PlayerTemplate, BaseFilename); - } - GenerateIndexPage(CollationBuffers, IndexTemplate); - } - else - { - if(Events.Ptr < Events.Location + BytesRead && Events.Ptr - Events.Location < Events.Size) - { - Events.Ptr += sizeof(struct inotify_event) + Event->len; - goto NextEvent; - } - else - { - break; - } - } + if(BespokeTemplate->Metadata.Filename && StringsDiffer(BespokeTemplate->Metadata.Filename, "")) + { + GeneratePlayerPage(Index, CollationBuffers, BespokeTemplate, BaseFilename); + DeclaimTemplate(BespokeTemplate); + } + else + { + GeneratePlayerPage(Index, CollationBuffers, PlayerTemplate, BaseFilename); + } + GenerateIndexPage(Index, CollationBuffers, IndexTemplate); + } break; } } } @@ -4790,52 +5354,131 @@ RemoveDirectoryRecursively(char *Path) int UpgradeDB(index *Index) { - int DBVersion = Index->Header.DBVersion; - switch(DBVersion) + // DBVersion 1 + typedef struct { unsigned int DBVersion; version AppVersion; version HMMLVersion; unsigned int EntryCount; } index_header1; + typedef struct { int Size; char BaseFilename[32]; } index_metadata1; + typedef struct { file_buffer File; file_buffer Metadata; index_header1 Header; index_metadata1 Entry; } index1; + // + + // DBVersion 2 + typedef struct { unsigned int DBVersion; version AppVersion; version HMMLVersion; unsigned int EntryCount; char IndexLocation[32]; char PlayerLocation[32]; } index_header2; + typedef struct { int Size; char BaseFilename[32]; } index_metadata2; + typedef struct { file_buffer File; file_buffer Metadata; index_header2 Header; index_metadata2 Entry; } index2; + // + + // NOTE(matt): For each new DB version, we must declare and initialise one instance of each preceding version, only cast the + // incoming Index to the type of the OriginalDBVersion, and move into the final case all operations on that incoming Index + + bool OnlyHeaderChanged = TRUE; + int OriginalHeaderSize = 0; + index1 Index1 = { 0 }; + index2 Index2 = { 0 }; + + int OriginalDBVersion = Index->Header.CurrentDBVersion; + switch(OriginalDBVersion) { case 1: { - typedef struct + OriginalHeaderSize = sizeof(index_header1); + Index1.Header = *(index_header1 *)Index->Metadata.Buffer.Ptr; + + Index2.Header.DBVersion = CINERA_DB_VERSION; + Index2.Header.AppVersion = CINERA_APP_VERSION; + Index2.Header.HMMLVersion.Major = hmml_version.Major; + Index2.Header.HMMLVersion.Minor = hmml_version.Minor; + Index2.Header.HMMLVersion.Patch = hmml_version.Patch; + Index2.Header.EntryCount = Index1.Header.EntryCount; + + Clear(Index2.Header.IndexLocation, sizeof(Index2.Header.IndexLocation)); + Clear(Index2.Header.PlayerLocation, sizeof(Index2.Header.PlayerLocation)); + } + case 2: + { + if(OriginalDBVersion == 2) { - unsigned int DBVersion; - version AppVersion; - version HMMLVersion; - unsigned int EntryCount; - } index_header1; + OriginalHeaderSize = sizeof(index_header2); + Index2.Header = *(index_header2 *)Index->Metadata.Buffer.Ptr; + } - typedef struct + Index->Header.InitialDBVersion = Index2.Header.DBVersion; + Index->Header.InitialAppVersion = Index2.Header.AppVersion; + Index->Header.InitialHMMLVersion.Major = Index2.Header.HMMLVersion.Major; + Index->Header.InitialHMMLVersion.Minor = Index2.Header.HMMLVersion.Minor; + Index->Header.InitialHMMLVersion.Patch = Index2.Header.HMMLVersion.Patch; + + Index->Header.CurrentDBVersion = CINERA_DB_VERSION; + Index->Header.CurrentAppVersion = CINERA_APP_VERSION; + Index->Header.CurrentHMMLVersion.Major = hmml_version.Major; + Index->Header.CurrentHMMLVersion.Minor = hmml_version.Minor; + Index->Header.CurrentHMMLVersion.Patch = hmml_version.Patch; + + Index->Header.EntryCount = Index2.Header.EntryCount; + + ClearCopyStringNoFormat(Index->Header.ProjectID, sizeof(Index->Header.ProjectID), Config.ProjectID); + + Clear(Index->Header.ProjectName, sizeof(Index->Header.ProjectName)); + for(int ProjectIndex = 0; ProjectIndex < ArrayCount(ProjectInfo); ++ProjectIndex) { - int Size; - char BaseFilename[32]; - } index_metadata1; + if(!StringsDiffer(ProjectInfo[ProjectIndex].ProjectID, Config.ProjectID)) + { + CopyString(Index->Header.ProjectName, ProjectInfo[ProjectIndex].FullName); + break; + } + } - typedef struct - { - file_buffer File; - file_buffer Metadata; - index_header1 Header; - index_metadata1 Entry; - } index1; + ClearCopyStringNoFormat(Index->Header.BaseURL, sizeof(Index->Header.BaseURL), Config.BaseURL); + ClearCopyStringNoFormat(Index->Header.IndexLocation, sizeof(Index->Header.IndexLocation), Index2.Header.IndexLocation); + ClearCopyStringNoFormat(Index->Header.PlayerLocation, sizeof(Index->Header.PlayerLocation), Index2.Header.PlayerLocation); + ClearCopyStringNoFormat(Index->Header.PlayerURLPrefix, sizeof(Index->Header.PlayerURLPrefix), Config.PlayerURLPrefix); - index1 OldIndex = { 0 }; - OldIndex.Header = *(index_header1 *)Index->Metadata.Buffer.Ptr; - Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(OldIndex.Header); - - Index->Header.DBVersion = CINERA_DB_VERSION; - Index->Header.AppVersion = CINERA_APP_VERSION; - Index->Header.HMMLVersion.Major = hmml_version.Major; - Index->Header.HMMLVersion.Minor = hmml_version.Minor; - Index->Header.HMMLVersion.Patch = hmml_version.Patch; - Index->Header.EntryCount = OldIndex.Header.EntryCount; - - Clear(Index->Header.IndexLocation, sizeof(Index->Header.IndexLocation)); - Clear(Index->Header.PlayerLocation, sizeof(Index->Header.PlayerLocation)); + OnlyHeaderChanged = FALSE; if(!(Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"))) { FreeBuffer(&Index->Metadata.Buffer); return RC_ERROR_FILE; } fwrite(&Index->Header, sizeof(Index->Header), 1, Index->Metadata.Handle); - Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(OldIndex.Header); - fwrite(Index->Metadata.Buffer.Ptr, Index->Metadata.FileSize - sizeof(OldIndex.Header), 1, Index->Metadata.Handle); - Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location; + + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + OriginalHeaderSize; + + if(ReadFileIntoBuffer(&Index->File, 0) == RC_ERROR_FILE) + { + fprintf(stderr, "\e[1;31mUnable to open index file\e[0m %s: %s\n" + "Removing %s and starting afresh\n", Index->File.Path, strerror(errno), + Index->Metadata.Path); + fclose(Index->Metadata.Handle); + FreeBuffer(&Index->Metadata.Buffer); + remove(Index->Metadata.Path); + return RC_ERROR_FILE; + } + Index->File.Buffer.Ptr += StringLength("---\n"); + + for(int EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex) + { + // NOTE(matt): We can use either index_metadata1 or 2 here because they are the same + index_metadata2 This = *(index_metadata2 *)Index->Metadata.Buffer.Ptr; + + Index->Entry.LinkOffsets.PrevStart = 0; + Index->Entry.LinkOffsets.NextStart = 0; + Index->Entry.LinkOffsets.PrevEnd = 0; + Index->Entry.LinkOffsets.NextEnd = 0; + + Index->Entry.Size = This.Size; + + ClearCopyStringNoFormat(Index->Entry.BaseFilename, sizeof(Index->Entry.BaseFilename), This.BaseFilename); + + char *IndexEntryStart = Index->File.Buffer.Ptr; + SeekBufferForString(&Index->File.Buffer, "title: \"", C_SEEK_FORWARDS, C_SEEK_AFTER); + Clear(Index->Entry.Title, sizeof(Index->Entry.Title)); + CopyStringNoFormatT(Index->Entry.Title, Index->File.Buffer.Ptr, '\n'); + Index->Entry.Title[StringLength(Index->Entry.Title) - 1] = '\0'; + + fwrite(&Index->Entry, sizeof(Index->Entry), 1, Index->Metadata.Handle); + + Index->Metadata.Buffer.Ptr += sizeof(This); + IndexEntryStart += This.Size; + Index->File.Buffer.Ptr = IndexEntryStart; + } + + FreeBuffer(&Index->File.Buffer); + fclose(Index->Metadata.Handle); FreeBuffer(&Index->Metadata.Buffer); if(ReadFileIntoBuffer(&Index->Metadata, 0) == RC_ERROR_FILE) @@ -4843,9 +5486,24 @@ UpgradeDB(index *Index) return RC_ERROR_FILE; } } - break; } - fprintf(stderr, "\n\e[1;32mUpgraded Cinera DB from %d to %d!\e[0m\n\n", DBVersion, Index->Header.DBVersion); + + if(OnlyHeaderChanged) + { + if(!(Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"))) { FreeBuffer(&Index->Metadata.Buffer); return RC_ERROR_FILE; } + fwrite(&Index->Header, sizeof(Index->Header), 1, Index->Metadata.Handle); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + OriginalHeaderSize; + fwrite(Index->Metadata.Buffer.Ptr, Index->Metadata.FileSize - OriginalHeaderSize, 1, Index->Metadata.Handle); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location; + fclose(Index->Metadata.Handle); + FreeBuffer(&Index->Metadata.Buffer); + if(ReadFileIntoBuffer(&Index->Metadata, 0) == RC_ERROR_FILE) + { + return RC_ERROR_FILE; + } + } + + fprintf(stderr, "\n\e[1;32mUpgraded Cinera DB from %d to %d!\e[0m\n\n", OriginalDBVersion, Index->Header.CurrentDBVersion); return RC_SUCCESS; } @@ -4856,90 +5514,91 @@ typedef struct } index_entry; // Metadata, unless we actually want to bolster this? int -DeleteDeadIndexEntries() +DeleteDeadIndexEntries(index *Index) { // TODO(matt): More rigorously figure out who we should delete // Maybe compare the output directory and the input HMML names - index Index = { 0 }; - Index.Metadata.Buffer.ID = "IndexMetadata"; - CopyString(Index.Metadata.Path, "%s/%s.metadata", Config.BaseDir, Config.ProjectID); - if(ReadFileIntoBuffer(&Index.Metadata, 0) == RC_ERROR_FILE) + if(ReadFileIntoBuffer(&Index->Metadata, 0) == RC_ERROR_FILE) { return RC_ERROR_FILE; } - Index.Header = *(index_header *)Index.Metadata.Buffer.Ptr; - if(Index.Header.DBVersion != CINERA_DB_VERSION) + Index->Header = *(index_header *)Index->Metadata.Buffer.Ptr; + if(Index->Header.CurrentDBVersion < CINERA_DB_VERSION) { - if(CINERA_DB_VERSION == 3) + if(CINERA_DB_VERSION == 4) { - fprintf(stderr, "\n\e[1;31mHandle conversion from CINERA_DB_VERSION %d to %d!\e[0m\n\n", Index.Header.DBVersion, CINERA_DB_VERSION); + fprintf(stderr, "\n\e[1;31mHandle conversion from CINERA_DB_VERSION %d to %d!\e[0m\n\n", Index->Header.CurrentDBVersion, CINERA_DB_VERSION); exit(RC_ERROR_FATAL); } - UpgradeDB(&Index); + if(UpgradeDB(Index) == RC_ERROR_FILE) { return RC_NOOP; + } + } + else if(Index->Header.CurrentDBVersion > CINERA_DB_VERSION) + { + fprintf(stderr, "\e[1;31mUnsupported DB Version (%d). Please upgrade Cinera\e[0m\n", Index->Header.CurrentDBVersion); + exit(RC_ERROR_FATAL); } - if(StringsDiffer(Index.Header.PlayerLocation, Config.PlayerLocation)) + if(StringsDiffer(Index->Header.PlayerLocation, Config.PlayerLocation)) { buffer PlayerDirectory; ClaimBuffer(&PlayerDirectory, "PlayerDirectory", 1024); printf("\e[1;33mRelocating Player Page%s from %s to %s\e[0m\n", - Index.Header.EntryCount > 1 ? "s" : "", - (StringsDiffer(Index.Header.PlayerLocation, "") ? Index.Header.PlayerLocation : (StringsDiffer(Config.BaseDir, ".") ? Config.BaseDir : "\"Base Directory\"")), + Index->Header.EntryCount > 1 ? "s" : "", + (StringsDiffer(Index->Header.PlayerLocation, "") ? Index->Header.PlayerLocation : (StringsDiffer(Config.BaseDir, ".") ? Config.BaseDir : "\"Base Directory\"")), (StringsDiffer(Config.PlayerLocation, "") ? Config.PlayerLocation : (StringsDiffer(Config.BaseDir, ".") ? Config.BaseDir : "\"Base Directory\""))); - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location + sizeof(Index.Header); - for(int EntryIndex = 0; EntryIndex < Index.Header.EntryCount; ++EntryIndex) + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(Index->Header); + for(int EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex) { - index_metadata This = *(index_metadata *)Index.Metadata.Buffer.Ptr; - ConstructDirectoryPath(&PlayerDirectory, PAGE_PLAYER, Index.Header.PlayerLocation, This.BaseFilename); - DeletePlayerPageFromFilesystem(This.BaseFilename, Index.Header.PlayerLocation, TRUE); - Index.Metadata.Buffer.Ptr += sizeof(This); + index_metadata This = *(index_metadata *)Index->Metadata.Buffer.Ptr; + ConstructDirectoryPath(&PlayerDirectory, PAGE_PLAYER, Index->Header.PlayerLocation, This.BaseFilename); + DeletePlayerPageFromFilesystem(This.BaseFilename, Index->Header.PlayerLocation, TRUE); + Index->Metadata.Buffer.Ptr += sizeof(This); } DeclaimBuffer(&PlayerDirectory); - RemoveDirectoryRecursively(Index.Header.PlayerLocation); - Clear(Index.Header.PlayerLocation, sizeof(Index.Header.PlayerLocation)); - CopyString(Index.Header.PlayerLocation, Config.PlayerLocation); - if(!(Index.Metadata.Handle = fopen(Index.Metadata.Path, "w"))) { FreeBuffer(&Index.Metadata.Buffer); return RC_ERROR_FILE; } - fwrite(&Index.Header, sizeof(Index.Header), 1, Index.Metadata.Handle); - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location + sizeof(Index.Header); - fwrite(Index.Metadata.Buffer.Ptr, Index.Metadata.FileSize - sizeof(Index.Header), 1, Index.Metadata.Handle); - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location; - fclose(Index.Metadata.Handle); + RemoveDirectoryRecursively(Index->Header.PlayerLocation); + ClearCopyStringNoFormat(Index->Header.PlayerLocation, sizeof(Index->Header.PlayerLocation), Config.PlayerLocation); + if(!(Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"))) { FreeBuffer(&Index->Metadata.Buffer); return RC_ERROR_FILE; } + fwrite(&Index->Header, sizeof(Index->Header), 1, Index->Metadata.Handle); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(Index->Header); + fwrite(Index->Metadata.Buffer.Ptr, Index->Metadata.FileSize - sizeof(Index->Header), 1, Index->Metadata.Handle); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location; + fclose(Index->Metadata.Handle); } - if(StringsDiffer(Index.Header.IndexLocation, Config.IndexLocation)) + if(StringsDiffer(Index->Header.IndexLocation, Config.IndexLocation)) { printf("\e[1;33mRelocating Index Page from %s to %s\e[0m\n", - (StringsDiffer(Index.Header.IndexLocation, "") ? Index.Header.IndexLocation : (StringsDiffer(Config.BaseDir, ".") ? Config.BaseDir : "\"Base Directory\"")), + (StringsDiffer(Index->Header.IndexLocation, "") ? Index->Header.IndexLocation : (StringsDiffer(Config.BaseDir, ".") ? Config.BaseDir : "\"Base Directory\"")), (StringsDiffer(Config.IndexLocation, "") ? Config.IndexLocation : (StringsDiffer(Config.BaseDir, ".") ? Config.BaseDir : "\"Base Directory\""))); buffer IndexDirectory; ClaimBuffer(&IndexDirectory, "IndexDirectory", 1024); - ConstructDirectoryPath(&IndexDirectory, PAGE_INDEX, Index.Header.IndexLocation, ""); + ConstructDirectoryPath(&IndexDirectory, PAGE_INDEX, Index->Header.IndexLocation, ""); char IndexPagePath[2048] = { 0 }; CopyString(IndexPagePath, "%s/index.html", IndexDirectory.Location); remove(IndexPagePath); RemoveDirectoryRecursively(IndexDirectory.Location); DeclaimBuffer(&IndexDirectory); - Clear(Index.Header.IndexLocation, sizeof(Index.Header.IndexLocation)); - CopyString(Index.Header.IndexLocation, Config.IndexLocation); - if(!(Index.Metadata.Handle = fopen(Index.Metadata.Path, "w"))) { FreeBuffer(&Index.Metadata.Buffer); return RC_ERROR_FILE; } - fwrite(&Index.Header, sizeof(Index.Header), 1, Index.Metadata.Handle); - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location + sizeof(Index.Header); - fwrite(Index.Metadata.Buffer.Ptr, Index.Metadata.FileSize - sizeof(Index.Header), 1, Index.Metadata.Handle); - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location; - fclose(Index.Metadata.Handle); + ClearCopyStringNoFormat(Index->Header.IndexLocation, sizeof(Index->Header.IndexLocation), Config.IndexLocation); + if(!(Index->Metadata.Handle = fopen(Index->Metadata.Path, "w"))) { FreeBuffer(&Index->Metadata.Buffer); return RC_ERROR_FILE; } + fwrite(&Index->Header, sizeof(Index->Header), 1, Index->Metadata.Handle); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(Index->Header); + fwrite(Index->Metadata.Buffer.Ptr, Index->Metadata.FileSize - sizeof(Index->Header), 1, Index->Metadata.Handle); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location; + fclose(Index->Metadata.Handle); } - Index.Metadata.Buffer.Ptr = Index.Metadata.Buffer.Location + sizeof(Index.Header); + Index->Metadata.Buffer.Ptr = Index->Metadata.Buffer.Location + sizeof(Index->Header); - index_entry Entries[Index.Header.EntryCount]; + index_entry Entries[Index->Header.EntryCount]; - for(int EntryIndex = 0; EntryIndex < Index.Header.EntryCount; ++EntryIndex) + for(int EntryIndex = 0; EntryIndex < Index->Header.EntryCount; ++EntryIndex) { - index_metadata This = *(index_metadata *)Index.Metadata.Buffer.Ptr; + index_metadata This = *(index_metadata *)Index->Metadata.Buffer.Ptr; CopyStringNoFormat(Entries[EntryIndex].ID, This.BaseFilename); Entries[EntryIndex].Present = FALSE; - Index.Metadata.Buffer.Ptr += sizeof(This); + Index->Metadata.Buffer.Ptr += sizeof(This); } DIR *ProjectDirHandle; @@ -4960,7 +5619,7 @@ DeleteDeadIndexEntries() if(!(StringsDiffer(Ptr, ".hmml"))) { *Ptr = '\0'; - for(int i = 0; i < Index.Header.EntryCount; ++i) + for(int i = 0; i < Index->Header.EntryCount; ++i) { if(!StringsDiffer(Entries[i].ID, ProjectFiles->d_name)) { @@ -4973,24 +5632,24 @@ DeleteDeadIndexEntries() closedir(ProjectDirHandle); bool Deleted = FALSE; - for(int i = 0; i < Index.Header.EntryCount; ++i) + for(int i = 0; i < Index->Header.EntryCount; ++i) { if(Entries[i].Present == FALSE) { Deleted = TRUE; - DeleteEntry(Entries[i].ID); + DeleteEntry(Index, Entries[i].ID); } } - FreeBuffer(&Index.Metadata.Buffer); + FreeBuffer(&Index->Metadata.Buffer); return Deleted ? RC_SUCCESS : RC_NOOP; } int -SyncIndexWithInput(buffers *CollationBuffers, template *IndexTemplate, template *PlayerTemplate, template *BespokeTemplate) +SyncIndexWithInput(index *Index, buffers *CollationBuffers, template *IndexTemplate, template *PlayerTemplate, template *BespokeTemplate) { bool Deleted = FALSE; - if(DeleteDeadIndexEntries() == RC_SUCCESS) + if(DeleteDeadIndexEntries(Index) == RC_SUCCESS) { Deleted = TRUE; } @@ -5013,18 +5672,23 @@ SyncIndexWithInput(buffers *CollationBuffers, template *IndexTemplate, template if(!(StringsDiffer(Ptr, ".hmml"))) { *Ptr = '\0'; - if(InsertIntoIndex(CollationBuffers, &BespokeTemplate, ProjectFiles->d_name) == RC_SUCCESS) + switch(InsertIntoIndex(Index, CollationBuffers, &BespokeTemplate, ProjectFiles->d_name)) { - if(BespokeTemplate->Metadata.Filename && StringsDiffer(BespokeTemplate->Metadata.Filename, "")) - { - GeneratePlayerPage(CollationBuffers, BespokeTemplate, ProjectFiles->d_name); - DeclaimTemplate(BespokeTemplate); - } - else - { - GeneratePlayerPage(CollationBuffers, PlayerTemplate, ProjectFiles->d_name); - } - Inserted = TRUE; + case RC_UNFOUND: + LinkNeighbours(Index, ProjectFiles->d_name, LINK_INCLUDE); + case RC_SUCCESS: + { + if(BespokeTemplate->Metadata.Filename && StringsDiffer(BespokeTemplate->Metadata.Filename, "")) + { + GeneratePlayerPage(Index, CollationBuffers, BespokeTemplate, ProjectFiles->d_name); + DeclaimTemplate(BespokeTemplate); + } + else + { + GeneratePlayerPage(Index, CollationBuffers, PlayerTemplate, ProjectFiles->d_name); + } + Inserted = TRUE; + } } } } @@ -5032,7 +5696,7 @@ SyncIndexWithInput(buffers *CollationBuffers, template *IndexTemplate, template if(Deleted || Inserted) { - GenerateIndexPage(CollationBuffers, IndexTemplate); + GenerateIndexPage(Index, CollationBuffers, IndexTemplate); } return RC_SUCCESS; } @@ -5054,6 +5718,10 @@ PrintVersions() int main(int ArgC, char **Args) { +#if 0 + printf("%lu\n", sizeof(index_metadata)); + exit(1); +#endif // TODO(matt): Read all defaults from the config config DefaultConfig = { .RootDir = ".", @@ -5100,7 +5768,7 @@ main(int ArgC, char **Args) } char CommandLineArg; - while((CommandLineArg = getopt(ArgC, Args, "a:b:B:c:d:fhi:j:l:m:n:o:p:qr:R:s:t:U:vx:y:")) != -1) + while((CommandLineArg = getopt(ArgC, Args, "a:b:B:c:d:efhi:j:l:m:n:o:p:qr:R:s:t:u:vx:y:")) != -1) { switch(CommandLineArg) { @@ -5119,6 +5787,9 @@ main(int ArgC, char **Args) case 'd': Config.ProjectDir = StripTrailingSlash(optarg); break; + case 'e': + Config.Mode |= MODE_EXAMINE; + break; case 'f': Config.ForceIntegration = TRUE; break; @@ -5160,7 +5831,7 @@ main(int ArgC, char **Args) case 't': Config.TemplatesDir = StripTrailingSlash(optarg); break; - case 'U': + case 'u': Config.UpdateInterval = StringToInt(optarg); break; case 'v': @@ -5179,32 +5850,86 @@ main(int ArgC, char **Args) } } + if(Config.Mode & MODE_EXAMINE) + { + index Index = { 0 }; + Index.Metadata.Buffer.ID = "IndexMetadata"; + // TODO(matt): Allow optionally passing a .metadata file as an argument? + CopyString(Index.Metadata.Path, "%s/%s.metadata", Config.BaseDir, Config.ProjectID); + ExamineIndex(&Index); + exit(RC_SUCCESS); + } + + bool HaveConfigErrors = FALSE; if(StringsDiffer(Config.ProjectID, "")) { + if(StringLength(Config.ProjectID) > MAX_PROJECT_ID_LENGTH) + { + fprintf(stderr, "\e[1;31mProjectID \"%s\" is too long (%d/%d characters)\e[0m\n", Config.ProjectID, StringLength(Config.ProjectID), MAX_PROJECT_ID_LENGTH); + HaveConfigErrors = TRUE; + } + Config.Edition = EDITION_PROJECT; for(int ProjectInfoIndex = 0; ProjectInfoIndex < ArrayCount(ProjectInfo); ++ProjectInfoIndex) { if(!StringsDiffer(Config.ProjectID, ProjectInfo[ProjectInfoIndex].ProjectID)) { + if(StringsDiffer(ProjectInfo[ProjectInfoIndex].Medium, "")) { Config.DefaultMedium = ProjectInfo[ProjectInfoIndex].Medium; } + if(StringsDiffer(ProjectInfo[ProjectInfoIndex].AltURLPrefix, "")) { - Config.PlayerURLPrefix = ProjectInfo[ProjectInfoIndex].AltURLPrefix; + if(StringLength(ProjectInfo[ProjectInfoIndex].AltURLPrefix) > MAX_PLAYER_URL_PREFIX_LENGTH) + { + fprintf(stderr, "\e[1;31mPlayer URL Prefix \"%s\" is too long (%d/%d characters)\e[0m\n", ProjectInfo[ProjectInfoIndex].AltURLPrefix, StringLength(ProjectInfo[ProjectInfoIndex].AltURLPrefix), MAX_PLAYER_URL_PREFIX_LENGTH); + HaveConfigErrors = TRUE; + } + else + { + Config.PlayerURLPrefix = ProjectInfo[ProjectInfoIndex].AltURLPrefix; + } } + + if(StringLength(ProjectInfo[ProjectInfoIndex].FullName) > MAX_PROJECT_NAME_LENGTH) + { + fprintf(stderr, "\e[1;31mProject Name \"%s\" is too long (%d/%d characters)\e[0m\n", ProjectInfo[ProjectInfoIndex].FullName, StringLength(ProjectInfo[ProjectInfoIndex].FullName), MAX_PROJECT_NAME_LENGTH); + HaveConfigErrors = TRUE; + } + break; } } } + if(StringsDiffer(Config.BaseURL, "") && StringLength(Config.BaseURL) > MAX_BASE_URL_LENGTH) + { + fprintf(stderr, "\e[1;31mBase URL \"%s\" is too long (%d/%d characters)\e[0m\n", Config.BaseURL, StringLength(Config.BaseURL), MAX_BASE_URL_LENGTH); + HaveConfigErrors = TRUE; + } + + if(StringsDiffer(Config.IndexLocation, "") && StringLength(Config.IndexLocation) > MAX_RELATIVE_PAGE_LOCATION_LENGTH) + { + fprintf(stderr, "\e[1;31mRelative Index Page Location \"%s\" is too long (%d/%d characters)\e[0m\n", Config.IndexLocation, StringLength(Config.IndexLocation), MAX_RELATIVE_PAGE_LOCATION_LENGTH); + HaveConfigErrors = TRUE; + } + + if(StringsDiffer(Config.PlayerLocation, "") && StringLength(Config.PlayerLocation) > MAX_RELATIVE_PAGE_LOCATION_LENGTH) + { + fprintf(stderr, "\e[1;31mRelative Player Page Location \"%s\" is too long (%d/%d characters)\e[0m\n", Config.PlayerLocation, StringLength(Config.PlayerLocation), MAX_RELATIVE_PAGE_LOCATION_LENGTH); + HaveConfigErrors = TRUE; + } + if(!MediumExists(Config.DefaultMedium)) { // TODO(matt): We'll want to stick around when we have multiple projects configured - exit(RC_RIP); + HaveConfigErrors = TRUE; } + if(HaveConfigErrors) { exit(RC_RIP); } + // NOTE(matt): Init MemoryArena (it is global) MemoryArena.Size = Megabytes(4); if(!(MemoryArena.Location = calloc(MemoryArena.Size, 1))) @@ -5281,13 +6006,6 @@ main(int ArgC, char **Args) } } - // NOTE(matt) - // - // Single Edition == Loop over Args[FileIndex] - // Project Edition == Loop over Config.ProjectDir - // - // Integrating or not - if(Config.Edition == EDITION_PROJECT) { @@ -5365,8 +6083,14 @@ main(int ArgC, char **Args) return(RC_SUCCESS); } + index Index = { 0 }; + Index.Metadata.Buffer.ID = "IndexMetadata"; + CopyString(Index.Metadata.Path, "%s/%s.metadata", Config.BaseDir, Config.ProjectID); + Index.File.Buffer.ID = "IndexFile"; + CopyString(Index.File.Path, "%s/%s.index", Config.BaseDir, Config.ProjectID); + printf("┌╼ Synchronising with annotation files in Project Input Directory ╾┐\n"); - SyncIndexWithInput(&CollationBuffers, IndexTemplate, PlayerTemplate, BespokeTemplate); + SyncIndexWithInput(&Index, &CollationBuffers, IndexTemplate, PlayerTemplate, BespokeTemplate); if(Config.Mode & MODE_ONESHOT) { goto RIP; @@ -5377,7 +6101,7 @@ main(int ArgC, char **Args) // NOTE(matt): Do we want to also watch IN_DELETE_SELF events? int WatchDescriptor = inotify_add_watch(inotifyInstance, Config.ProjectDir, IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO); - while(MonitorDirectory(&CollationBuffers, IndexTemplate, PlayerTemplate, BespokeTemplate, inotifyInstance, WatchDescriptor) != RC_ERROR_FATAL) + while(MonitorDirectory(&Index, &CollationBuffers, IndexTemplate, PlayerTemplate, BespokeTemplate, inotifyInstance, WatchDescriptor) != RC_ERROR_FATAL) { sleep(Config.UpdateInterval); } @@ -5401,7 +6125,7 @@ NextFile: if(!(StringsDiffer(Ptr, ".hmml"))) { CopyString(Config.SingleHMMLFilePath, Args[FileIndex]); - switch(HMMLToBuffers(&CollationBuffers, &BespokeTemplate, Args[FileIndex])) + switch(HMMLToBuffers(&CollationBuffers, &BespokeTemplate, Args[FileIndex], 0, 0)) { // TODO(matt): Actually sort out the fatality of these cases, once we are always-on case RC_ERROR_FILE: @@ -5422,7 +6146,7 @@ NextFile: switch(BuffersToHTML(&CollationBuffers, HasBespokeTemplate ? BespokeTemplate : PlayerTemplate, 0, - PAGE_PLAYER)) + PAGE_PLAYER, 0)) { // TODO(matt): Actually sort out the fatality of these cases, once we are always-on case RC_INVALID_TEMPLATE: diff --git a/cinera/cinera.css b/cinera/cinera.css index a6980eb..bfeccd3 100644 --- a/cinera/cinera.css +++ b/cinera/cinera.css @@ -415,19 +415,19 @@ } .cineraMenus > .menu > .filter_container .filter_content, -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories { cursor: pointer; display: flex; align-items: center; } .cineraMenus > .menu > .filter_container .filter_content .icon, -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories .categoryMedium { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .categoryMedium { font-style: normal; margin-left: 4px; } -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories .categoryMedium + .categoryMedium { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .categoryMedium + .categoryMedium { margin-left: 2px; } @@ -436,18 +436,18 @@ } .cineraMenus > .menu > .filter_container .filter_content.rant .icon, -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories .categoryMedium.rant { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .categoryMedium.rant { color: #BA0001; } .cineraMenus > .menu > .filter_container .filter_media .filter_content.off .icon, -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories .categoryMedium.off { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .categoryMedium.off { opacity: 0.32; } .cineraMenus > .menu > .filter_container .filter_content.rant .cineraText, -.cineraPlayerContainer .markers_container > .marker.rant .cineraContent, -.cineraPlayerContainer .markers_container > .marker.off_rant .cineraContent { +.cineraPlayerContainer .markers_container > .markers .marker.rant .cineraContent, +.cineraPlayerContainer .markers_container > .markers .marker.off_rant .cineraContent { font-variant: small-caps; } @@ -467,7 +467,42 @@ position: relative; } -.cineraPlayerContainer .markers_container > .marker { +.cineraPlayerContainer .markers_container > .episodeMarker { + text-decoration: none; + display: flex; + font-size: 11px; + font-weight: bold; +} + +.cineraPlayerContainer .markers_container > a.episodeMarker { + cursor: pointer; +} + +.cineraPlayerContainer .markers_container > div.episodeMarker { + cursor: default; +} + +.cineraPlayerContainer .markers_container > .episodeMarker div { + margin: auto; + text-align: center; +} + +.cineraPlayerContainer .markers_container > .episodeMarker div:not(:nth-of-type(2)) { + font-size: 15px; + font-weight: normal; +} + +.cineraPlayerContainer .markers_container > .episodeMarker div:nth-of-type(2) { + padding: 0 5px; + flex-grow: 1; +} + +.cineraPlayerContainer .markers_container > .episodeMarker.first, +.cineraPlayerContainer .markers_container > .episodeMarker.prev { + border-bottom: 4px double; +} + +.cineraPlayerContainer .markers_container > .markers .marker { border-bottom: 1px solid; position: relative; cursor: pointer; @@ -475,23 +510,36 @@ transition: max-height .32s; } -.cineraPlayerContainer .markers_container > .marker.skip { - max-height: 0; - transition: max-height .32s; - overflow: hidden; +.cineraPlayerContainer .markers_container > .markers .marker:last-of-type { + border-bottom: none; } -.cineraPlayerContainer .markers_container > .marker .cineraContent { +.cineraPlayerContainer .markers_container > .episodeMarker.next, +.cineraPlayerContainer .markers_container > .episodeMarker.last { + border-top: 4px double; +} + +.cineraPlayerContainer .markers_container > .episodeMarker, +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent { box-sizing: border-box; - display: block; - font-size: 14px; padding: 5px; width: 320px; word-wrap: break-word; } -.cineraPlayerContainer .markers_container > .marker.authored .cineraContent .author, -.cineraPlayerContainer .markers_container > .marker.off_authored .cineraContent .author { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent { + display: block; + font-size: 14px; +} + +.cineraPlayerContainer .markers_container > .markers .marker.skip { + max-height: 0; + transition: max-height .32s; + overflow: hidden; +} + +.cineraPlayerContainer .markers_container > .markers .marker.authored .cineraContent .author, +.cineraPlayerContainer .markers_container > .markers .marker.off_authored .cineraContent .author { font-style: normal; font-variant: normal; font-weight: bold; @@ -499,17 +547,17 @@ .cineraMenus > .menu > .refs .ref .ref_title, .cineraMenus .filter_content.authored .cineraText, -.cineraPlayerContainer .markers_container > .marker.authored, -.cineraPlayerContainer .markers_container > .marker.off_authored { +.cineraPlayerContainer .markers_container > .markers .marker.authored, +.cineraPlayerContainer .markers_container > .markers .marker.off_authored { font-style: oblique !important; } -.cineraPlayerContainer .markers_container > .marker .cineraContent sup { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent sup { font-style: normal; font-variant: normal; } -.cineraPlayerContainer .markers_container > .marker .progress { +.cineraPlayerContainer .markers_container > .markers .marker .progress { position: absolute; top: 0; left: 0; @@ -518,7 +566,7 @@ overflow: hidden; } -.cineraPlayerContainer .markers_container > .marker .timecode { +.cineraPlayerContainer .markers_container > .markers .marker .timecode { font-size: 9px; font-style: normal; padding-right: 8px; @@ -526,7 +574,7 @@ top: -2px; } -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories { display: inline-flex; margin: 4px; } @@ -536,18 +584,18 @@ } .cineraMenus > .menu > .filter_container .filter_content .category, -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories .category { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .category { border-radius: 50%; height: 5px; width: 5px; } .cineraMenus > .menu > .filter_container .filter_content .category.off, -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories .category.off { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .category.off { background: transparent; } -.cineraPlayerContainer .markers_container > .marker .cineraContent .cineraCategories .category { +.cineraPlayerContainer .markers_container > .markers .marker .cineraContent .cineraCategories .category { margin-left: 2px; }