From 274868783969c014e92512139482a3329e72bddb Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Wed, 24 Jun 2020 13:29:18 +0100 Subject: [PATCH] cinera.c: Sanitise Memory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes / Improvements: • Switch all growable arrays to use memory_book, rather than realloc() • Lift a bunch of hardcoded string lengths and item counts • malloc() the MemoryArena rather than calloc(), thus saving 3 MiB • Reorganise HMMLToBuffers() to return from one place, the end • Print indexing errors in the same style as config errors / warnings Diagnoses: • Identified sources of "non-freed" memory usage and marked them +MEM. This may aid future work on further reducing memory / cycle usage. New config settings: • suppress_prompts boolean --- cinera/cinera.c | 5077 +++++++++++++++++++++------------------- cinera/cinera_config.c | 162 +- 2 files changed, 2819 insertions(+), 2420 deletions(-) diff --git a/cinera/cinera.c b/cinera/cinera.c index 3684d9a..008d1d3 100644 --- a/cinera/cinera.c +++ b/cinera/cinera.c @@ -23,7 +23,7 @@ typedef struct version CINERA_APP_VERSION = { .Major = 0, .Minor = 7, - .Patch = 15 + .Patch = 16 }; #include // NOTE(matt): varargs @@ -116,7 +116,6 @@ clock_t TIMING_START; #define Free(M) free(M); M = 0; #define FreeAndResetCount(M, Count) { Free(M); Count = 0; } -#define FreeAndResetCountAndCapacity(M, Count, Capacity) { FreeAndResetCount(M, Count); Capacity = 0; } #define MIN(A, B) A < B ? A : B @@ -130,37 +129,6 @@ Clear(void *V, uint64_t Size) } } -void * -Fit(void *Base, uint16_t WidthInBytes, uint64_t ItemCount, uint32_t ItemsPerBlock, bool ZeroInitialise) -{ - if(ItemCount % ItemsPerBlock == 0) - { - Base = realloc(Base, (ItemCount + ItemsPerBlock) * WidthInBytes); - if(ZeroInitialise) - { - Clear(Base + WidthInBytes * ItemCount, WidthInBytes * ItemsPerBlock); - } - } - return Base; -} - - -void * -FitShrinkable(void *Base, uint16_t WidthInBytes, uint64_t ItemCount, uint64_t *Capacity, uint32_t ItemsPerBlock, bool ZeroInitialise) -{ - if(ItemCount == *Capacity) - { - Base = realloc(Base, (ItemCount + ItemsPerBlock) * WidthInBytes); - if(ZeroInitialise) - { - Clear(Base + WidthInBytes * ItemCount, WidthInBytes * ItemsPerBlock); - } - *Capacity += ItemsPerBlock; - } - return Base; -} - - // NOTE(matt): Memory testing #include int @@ -213,16 +181,48 @@ GetUsage(void) // NOTE(matt): One-time memory testing // #if DEBUG_MEMORY_LEAKAGE -#define MEM_TEST_INITIAL() int Initial = GetUsage() -#define MEM_TEST_MID(String) if(GetUsage() > Initial) fprintf(stderr, "%s: %i kb\n", String, GetUsage() - Initial) -#define MEM_TEST_AFTER(String) int Current = GetUsage();\ - Colourise(Current > Initial ? CS_RED : CS_BLUE);\ - fprintf(stderr, "%s leaks %i kb\n", String, Current - Initial);\ - Colourise(CS_END); +int DebugMemoryIndentLevel = 0; +#define MEM_TEST_TOP(String) int MemTestInitialValue = GetUsage(); int MemTestRunningValue = MemTestInitialValue;\ + /*\ + Colourise(CS_BLACK_BOLD);\ + fprintf(stderr, "[%5i]", __LINE__);\ + Colourise(CS_GREEN);\ + fprintf(stderr, " ");\ + Indent(DebugMemoryIndentLevel);\ + fprintf(stderr, "%s starts at %i kb\n", String, MemTestInitialValue);\ + Colourise(CS_END);\ + */\ + ++DebugMemoryIndentLevel; + +#define MEM_TEST_MID(String) if(GetUsage() > MemTestRunningValue)\ +{\ + Colourise(CS_BLACK_BOLD);\ + fprintf(stderr, "[%5i]", __LINE__);\ + Colourise(CS_YELLOW);\ + fprintf(stderr, " ");\ + Indent(DebugMemoryIndentLevel - 1);\ + fprintf(stderr, "%s is now at %i kb (+%i)\n", String, GetUsage(), GetUsage() - MemTestInitialValue);\ + Colourise(CS_END);\ + MemTestRunningValue = GetUsage();\ + WaitForInput();\ +} +#define MEM_TEST_END(String) MemTestRunningValue = GetUsage();\ + --DebugMemoryIndentLevel;\ + if(MemTestRunningValue > MemTestInitialValue)\ + {\ + Colourise(CS_BLACK_BOLD);\ + fprintf(stderr, "[%5i]", __LINE__);\ + Colourise(CS_RED);\ + fprintf(stderr, " ");\ + Indent(DebugMemoryIndentLevel);\ + fprintf(stderr, "%s ends at %i kb (+%i)\n", String, MemTestRunningValue, MemTestRunningValue - MemTestInitialValue);\ + Colourise(CS_END);\ + WaitForInput();\ + } #else -#define MEM_TEST_INITIAL() +#define MEM_TEST_TOP(String) #define MEM_TEST_MID(String) -#define MEM_TEST_AFTER(String) +#define MEM_TEST_END(String) #endif // //// @@ -261,6 +261,7 @@ typedef enum RC_SCHEME_MIXTURE, RC_SYNTAX_ERROR, RC_UNFOUND, + RC_UNHANDLED_REF_COMBO, RC_FAILURE, RC_SUCCESS } rc; @@ -462,6 +463,7 @@ typedef enum MBT_NONE, MBT_ASSET, + MBT_CATEGORY_INFO, MBT_CONFIG_IDENTIFIER_ID, MBT_CONFIG_BOOL_PAIR, MBT_CONFIG_INT_PAIR, @@ -469,23 +471,37 @@ typedef enum MBT_CONFIG_STRING_ASSOCIATION, MBT_CONFIG_TYPE_FIELD, MBT_CONFIG_TYPE_SPEC, + MBT_IDENTIFIER, + MBT_LANDMARK, MBT_MEDIUM, MBT_NAVIGATION_BUFFER, MBT_PERSON, MBT_PERSON_PTR, MBT_PROJECT, MBT_PROJECT_PTR, + MBT_REF_INFO, MBT_RESOLUTION_ERROR, + MBT_SCOPE_TREE, + MBT_SPEAKER, MBT_STRING, MBT_STRING_PTR, MBT_SUPPORT, MBT_TAG_OFFSET, MBT_TOKEN, MBT_TOKENS, + MBT_UINT32, MBT_VARIANT, MBT_VARIANT_STRING, + MBT_WATCH_FILE, + MBT_WATCH_HANDLE, + MBT_COUNT, } memory_book_type; +uint64_t MemoryBookTypeWidths[MBT_COUNT]; + +// TODO(matt): We probably want our memory_book to be able to support strings that span multiple pages +// SearchQuotes() especially could benefit from this + typedef struct { int64_t PageCount; @@ -498,18 +514,36 @@ typedef struct } memory_book; #define _memory_book(type) memory_book -void -InitBook(memory_book *M, uint64_t DataWidthInBytes, uint64_t ItemsPerPage, memory_book_type Type) +memory_book +InitBook(memory_book_type Type, uint64_t ItemsPerPage) { - M->PageSize = ItemsPerPage * DataWidthInBytes; - M->Type = Type; - M->DataWidthInBytes = DataWidthInBytes; + Assert(MemoryBookTypeWidths[Type]); + + memory_book Result = {}; + Result.PageSize = ItemsPerPage * MemoryBookTypeWidths[Type]; + Result.Type = Type; + Result.DataWidthInBytes = MemoryBookTypeWidths[Type]; + return Result; } -void -InitBookOfPointers(memory_book *M, uint64_t ItemsPerPage, memory_book_type Type) +memory_book +InitBookOfPointers(memory_book_type Type, uint64_t ItemsPerPage) { - InitBook(M, sizeof(uintptr_t), ItemsPerPage, Type); + memory_book Result = {}; + Result.PageSize = ItemsPerPage * sizeof(uintptr_t); + Result.Type = Type; + Result.DataWidthInBytes = sizeof(uintptr_t); + return Result; +} + +memory_book +InitBookOfStrings(uint64_t PageSizeInBytes) +{ + memory_book Result = {}; + Result.PageSize = PageSizeInBytes; + Result.Type = MBT_STRING; + Result.DataWidthInBytes = 1; + return Result; } memory_page * @@ -752,6 +786,7 @@ PrintBook(memory_book *M) } break; case MBT_NONE: Assert(0); break; case MBT_ASSET: Assert(0); break; + case MBT_CATEGORY_INFO: Assert(0); break; case MBT_CONFIG_IDENTIFIER_ID: Assert(0); break; case MBT_CONFIG_BOOL_PAIR: Assert(0); break; case MBT_CONFIG_INT_PAIR: Assert(0); break; @@ -759,20 +794,29 @@ PrintBook(memory_book *M) case MBT_CONFIG_STRING_ASSOCIATION: Assert(0); break; case MBT_CONFIG_TYPE_FIELD: Assert(0); break; case MBT_CONFIG_TYPE_SPEC: Assert(0); break; + case MBT_IDENTIFIER: Assert(0); break; + case MBT_LANDMARK: Assert(0); break; case MBT_MEDIUM: Assert(0); break; case MBT_NAVIGATION_BUFFER: Assert(0); break; case MBT_PERSON: Assert(0); break; case MBT_PERSON_PTR: Assert(0); break; case MBT_PROJECT: Assert(0); break; case MBT_PROJECT_PTR: Assert(0); break; + case MBT_REF_INFO: Assert(0); break; case MBT_RESOLUTION_ERROR: Assert(0); break; + case MBT_SCOPE_TREE: Assert(0); break; + case MBT_SPEAKER: Assert(0); break; case MBT_STRING_PTR: Assert(0); break; case MBT_SUPPORT: Assert(0); break; case MBT_TAG_OFFSET: Assert(0); break; case MBT_TOKEN: Assert(0); break; case MBT_TOKENS: Assert(0); break; + case MBT_UINT32: Assert(0); break; case MBT_VARIANT: Assert(0); break; case MBT_VARIANT_STRING: Assert(0); break; + case MBT_WATCH_FILE: Assert(0); break; + case MBT_WATCH_HANDLE: Assert(0); break; + case MBT_COUNT: break; } } @@ -865,11 +909,8 @@ StringsDifferS(char *NullTerminated, buffer *NotNullTerminated) int64_t StringsDiffer0(char *A, char *B) { - //fprintf(stderr, "%c vs %c\n", *A, *B); while(*A && *B && *A == *B) { - //fprintf(stderr, "%c vs %c\n", *A, *B); - //fprintf(stderr, "[%d] %c\n", Location, *A); ++A, ++B; } return *A - *B; @@ -1134,14 +1175,12 @@ Colourise(colour_code C) void PrintString(string S) { - //fprintf(stderr, "PrintString()\n"); fprintf(stderr, "%.*s", (int)S.Length, S.Base); } void PrintStringN(string S) { - //fprintf(stderr, "PrintString()\n"); fprintf(stderr, "\n%.*s", (int)S.Length, S.Base); } @@ -1163,6 +1202,16 @@ PrintStringC(colour_code Colour, string String) Colourise(CS_END); } +void +PrintStringCN(colour_code Colour, string String, bool PrependNewline, bool AppendNewline) +{ + if(PrependNewline) { fprintf(stderr, "\n"); } + Colourise(Colour); + PrintString(String); + Colourise(CS_END); + if(AppendNewline) { fprintf(stderr, "\n"); } +} + void PrintC(colour_code Colour, char *String) { @@ -1398,8 +1447,8 @@ file credits this person for the entire project. There is no way to \"uncredit\" video node of the entries for which they should be credited." }, { "js_path", "Path relative to assets_root_dir and assets_root_url where JavaScript files are located." }, - { "lineage", 0, 0, "A slash-separated string of all project IDs from the top of the family tree to the present project" }, - { "lineage_without_origin", 0, 0, "Same as the $lineage, without the first component (the $origin)" }, + { "lineage", 0, 0, "A slash-separated string of all project IDs from the top of the family tree to the present project." }, + { "lineage_without_origin", 0, 0, "Same as the $lineage, without the first component (the $origin)." }, { "log_level", "Possible log levels, from least to most verbose: \"emergency\", \"alert\", \"critical\", \"error\", \"warning\", \"notice\", \"informational\", \"debug\""}, { "medium", "In HMML an indexer may prefix a word with a colon, to mark it as a category. This category will then appear in the filter menu, for viewers to toggle on / off. A category may be either a topic (by \ @@ -1414,29 +1463,29 @@ default categories are assumed to be topics) or a medium, and both use the same \"$project$episode_number.hmml\". Under the \"linear\" scheme, Cinera tries to derive each entry's number in its project by skipping past the project ID, then replacing all underscores with full \ stops. This derived number is then used in the search results to denote the entry." }, - { "origin", 0, 0, "The ID of the project in our branch of the family tree which has no parent" }, + { "origin", 0, 0, "The ID of the project in our branch of the family tree which has no parent." }, { "owner", "The ID of the person (see also person) who owns the project. There may only be one owner, and they will appear in the credits menu as the host of each entry.", 0, -"The ID of the project's owner" +"The ID of the project's owner." }, { "person", "This is someone who played a role in the projects, for which they deserve credit (see also: owner, cohost, guest, indexer).", 0, -"The ID of the person within whose scope the variable occurs" +"The ID of the person within whose scope the variable occurs." }, - { "player_location", "The location of the project's player pages relative to base_dir and base_url" }, + { "player_location", "The location of the project's player pages relative to base_dir and base_url." }, { "player_template", "Path of a HTML template file relative to the templates_dir from which the project's player pages will be generated." }, { "privacy_check_interval", "In minutes, this sets how often to check if the privacy status of private entries has changed to \"public\"." }, { "project", "The work horse of the whole configuration. A config file lacking project scopes will produce no output. Notably project scopes may themselves contain project scopes.", 0, -"The ID of the project within which scope the variable occurs" +"The ID of the project within which scope the variable occurs." }, { "query_string", "This string (default \"r\") enables web browsers to cache asset files. We hash those files to produce a number, which we then write to HTML files in hexadecimal format, e.g. \ ?r=a59bb130. Hashing may be disabled by setting query_string = \"\";" }, - { "search_location", "The location of the project's search page relative to base_dir and base_url" }, + { "search_location", "The location of the project's search page relative to base_dir and base_url." }, { "search_template", "Path of a HTML template file relative to the templates_dir from which the project's search page will be generated." }, { "single_browser_tab", "Setting this to \"true\" (default \"false\") makes the search page open player pages in its own tab." }, { "stream_platform", "This is a setting for the future. We currently only support \"twitch\" but not in any meaningful way." }, @@ -1447,11 +1496,12 @@ perhaps to automatically construct paths, while recognising that same person whe { "support", "Information detailing where a person may be supported, to be cited in the credits menu.", 0, -"The ID of the support platform within which scope the variable occurs" +"The ID of the support platform within which scope the variable occurs." }, + { "suppress_prompts", "When issuing an error, Cinera prompts the user, giving them time to read the error. Setting this to \"true\" prevents this behaviour." }, { "templates_dir", "Absolute directory path, from where the player_template and search_template path may be derived." }, { "theme", "The theme used to style all the project's pages. As with all themes, its name forms the file path of a CSS file containing the style as follows: cinera__${theme}.css" }, - { "title", "The full name of the project" }, + { "title", "The full name of the project." }, { "title_list_delimiter", "Currently not implemented, probably to be removed." }, { "title_list_end", "Currently not implemented, probably to be removed." }, { "title_list_prefix", "Currently not implemented, probably to be removed." }, @@ -1523,6 +1573,7 @@ typedef enum IDENT_STREAM_PLATFORM, IDENT_STREAM_USERNAME, IDENT_SUPPORT, + IDENT_SUPPRESS_PROMPTS, IDENT_TEMPLATES_DIR, IDENT_THEME, IDENT_TITLE, @@ -1758,30 +1809,60 @@ typedef enum CAV_COUNT, } config_art_variant_id; +char *SeverityStrings[] = +{ + "error", + "warning", +}; + typedef enum { S_ERROR, S_WARNING, } severity; +char *ErrorDomainStrings[] = +{ + "Config", + "Indexing", +}; + +typedef enum +{ + ED_CONFIG, + ED_INDEXING, +} error_domain; + void -ConfigErrorFilenameAndLineNumber(char *Filename, uint64_t LineNumber, severity Severity) +ErrorFilenameAndLineNumber(string *Filename, uint64_t LineNumber, severity Severity, error_domain Domain) { fprintf(stderr, "\n" "┌─ "); + switch(Severity) { - case S_ERROR: PrintC(CS_ERROR, "Config error"); break; - case S_WARNING: PrintC(CS_WARNING, "Config warning"); break; + case S_ERROR: Colourise(CS_ERROR); break; + case S_WARNING: Colourise(CS_WARNING); break; } + + fprintf(stderr, "%s %s", ErrorDomainStrings[Domain], SeverityStrings[Severity]); + Colourise(CS_END); + if(Filename) { - fprintf(stderr, " on line "); - Colourise(CS_BLUE_BOLD); - fprintf(stderr, "%lu", LineNumber); - Colourise(CS_END); - fprintf(stderr, " of "); - PrintC(CS_CYAN, Filename); + if(LineNumber > 0) + { + fprintf(stderr, " on line "); + Colourise(CS_BLUE_BOLD); + fprintf(stderr, "%lu", LineNumber); + Colourise(CS_END); + fprintf(stderr, " of "); + } + else + { + fprintf(stderr, " with "); + } + PrintStringC(CS_CYAN, *Filename); } fprintf(stderr, "\n" "└─╼ "); @@ -1790,9 +1871,9 @@ ConfigErrorFilenameAndLineNumber(char *Filename, uint64_t LineNumber, severity S // TODO(matt): Get more errors going through Error() void -ConfigError(char *Filename, uint64_t LineNumber, severity Severity, char *Message, string *Received) +ConfigError(string *Filename, uint64_t LineNumber, severity Severity, char *Message, string *Received) { - ConfigErrorFilenameAndLineNumber(Filename, LineNumber, Severity); + ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_CONFIG); fprintf(stderr, "%s", Message); if(Received) @@ -1805,15 +1886,15 @@ ConfigError(char *Filename, uint64_t LineNumber, severity Severity, char *Messag void ConfigErrorUnset(config_identifier_id FieldID) { - ConfigErrorFilenameAndLineNumber(0, 0, S_ERROR); + ErrorFilenameAndLineNumber(0, 0, S_ERROR, ED_CONFIG); fprintf(stderr, "Unset %s\n", ConfigIdentifiers[FieldID].String); } void -ConfigFileIncludeError(char *Filename, uint64_t LineNumber, string Path) +ConfigFileIncludeError(string *Filename, uint64_t LineNumber, string Path) { - ConfigErrorFilenameAndLineNumber(Filename, LineNumber, S_WARNING); + ErrorFilenameAndLineNumber(Filename, LineNumber, S_WARNING, ED_CONFIG); fprintf(stderr, "Included file could not be opened (%s): ", strerror(errno)); PrintStringC(CS_MAGENTA_BOLD, Path); @@ -1821,18 +1902,18 @@ ConfigFileIncludeError(char *Filename, uint64_t LineNumber, string Path) } void -ConfigErrorSizing(char *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize) +ConfigErrorSizing(string *Filename, uint64_t LineNumber, config_identifier_id FieldID, string *Received, uint64_t MaxSize) { - ConfigErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR); + ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_CONFIG); fprintf(stderr, "%s value is too long (%lu/%lu characters): ", ConfigIdentifiers[FieldID].String, Received->Length, MaxSize); PrintStringC(CS_MAGENTA_BOLD, *Received); fprintf(stderr, "\n"); } void -ConfigErrorInt(char *Filename, uint64_t LineNumber, severity Severity, char *Message, uint64_t Number) +ConfigErrorInt(string *Filename, uint64_t LineNumber, severity Severity, char *Message, uint64_t Number) { - ConfigErrorFilenameAndLineNumber(Filename, LineNumber, Severity); + ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_CONFIG); fprintf(stderr, "%s%s%lu%s\n", Message, ColourStrings[CS_BLUE_BOLD], Number, ColourStrings[CS_END]); } @@ -1841,7 +1922,8 @@ void ConfigErrorExpectation(tokens *T, token_type GreaterExpectation, token_type LesserExpectation) { token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigErrorFilenameAndLineNumber(T->File.Path, This->LineNumber, S_ERROR); + string Filepath = Wrap0(T->File.Path); + ErrorFilenameAndLineNumber(&Filepath, This->LineNumber, S_ERROR, ED_CONFIG); fprintf(stderr, "Syntax error: Received "); if(This->Content.Base) @@ -1866,6 +1948,72 @@ ConfigErrorExpectation(tokens *T, token_type GreaterExpectation, token_type Less "\n"); } +void +IndexingError(string *Filename, uint64_t LineNumber, severity Severity, char *Message, string *Received) +{ + ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_INDEXING); + // TODO(matt): Typeset the Message? + fprintf(stderr, + "%s", Message); + if(Received) + { + PrintStringC(CS_MAGENTA_BOLD, *Received); + } + fprintf(stderr, "\n"); +} + +void +IndexingChronologyError(string *Filename, uint64_t LineNumber, char *ThisTimecode, char *PrevTimecode) +{ + severity Severity = S_ERROR; + ErrorFilenameAndLineNumber(Filename, LineNumber, Severity, ED_INDEXING); + fprintf(stderr, + "Timecode "); + PrintC(CS_BLUE_BOLD, ThisTimecode); + fprintf(stderr, " is chronologically earlier than previous timecode ("); + PrintC(CS_BLUE_BOLD, PrevTimecode); + fprintf(stderr, ")\n"); +} + +void +IndexingQuoteError(string *Filename, uint64_t LineNumber, string Author, uint64_t QuoteID) +{ + ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_INDEXING); + fprintf(stderr, "Quote not found: "); + Colourise(CS_MAGENTA_BOLD); + fprintf(stderr, "#"); + PrintString(Author); + fprintf(stderr, " %ld", QuoteID); + Colourise(CS_END); + fprintf(stderr, "\n"); +} + +void +IndexingErrorSizing(string *Filename, uint64_t LineNumber, char *Key, string Received, uint64_t MaxSize) +{ + ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_INDEXING); + fprintf(stderr, "%s value is too long (%lu/%lu characters): ", Key, Received.Length, MaxSize); + PrintStringC(CS_MAGENTA_BOLD, Received); + fprintf(stderr, "\n"); +} + +void +IndexingErrorCustomSizing(string *Filename, uint64_t LineNumber, int CustomIndex, string Received) +{ + ErrorFilenameAndLineNumber(Filename, LineNumber, S_ERROR, ED_INDEXING); + fprintf(stderr, "custom%d value is too long (%lu/%i characters): ", CustomIndex, Received.Length, CustomIndex < 12 ? MAX_CUSTOM_SNIPPET_SHORT_LENGTH : MAX_CUSTOM_SNIPPET_LONG_LENGTH); + PrintStringC(CS_MAGENTA_BOLD, Received); + fprintf(stderr, "\n"); + if(Received.Length < MAX_CUSTOM_SNIPPET_LONG_LENGTH) + { + fprintf(stderr, "Consider using custom12 to custom15, which can hold %d characters\n", MAX_CUSTOM_SNIPPET_LONG_LENGTH); + } + else + { + fprintf(stderr, "Consider using a bespoke template for longer amounts of localised information\n"); + } +} + typedef enum { // NOTE(matt): https://tools.ietf.org/html/rfc5424#section-6.2.1 @@ -1893,7 +2041,7 @@ char *LogLevelStrings[] = }; log_level -GetLogLevelFromString(char *Filename, token *T) +GetLogLevelFromString(string *Filename, token *T) { for(int i = 0; i < LOG_COUNT; ++i) { @@ -1910,14 +2058,19 @@ GetLogLevelFromString(char *Filename, token *T) return LOG_COUNT; } +void WaitForInput(void); // NOTE(matt): Forward declared + void FreeBuffer(buffer *Buffer) { - Free(Buffer->Location); + /* */ MEM_TEST_TOP("FreeBuffer"); + /* +MEM */ Free(Buffer->Location); + /* */ MEM_TEST_MID("FreeBuffer"); Buffer->Ptr = 0; Buffer->Size = 0; //Buffer->ID = 0; Buffer->IndentLevel = 0; + MEM_TEST_END("FreeBuffer"); } char *AssetTypeNames[] = @@ -1949,7 +2102,7 @@ typedef enum } genre; genre -GetGenreFromString(char *Filename, token *T) +GetGenreFromString(string *Filename, token *T) { for(int i = 0; i < GENRE_COUNT; ++i) { @@ -1982,7 +2135,7 @@ typedef enum } numbering_scheme; numbering_scheme -GetNumberingSchemeFromString(char *Filename, token *T) +GetNumberingSchemeFromString(string *Filename, token *T) { for(int i = 0; i < NS_COUNT; ++i) { @@ -2000,7 +2153,7 @@ GetNumberingSchemeFromString(char *Filename, token *T) } bool -GetBoolFromString(char *Filename, token *T) +GetBoolFromString(string *Filename, token *T) { if(!StringsDifferLv0(T->Content, "true") || !StringsDifferLv0(T->Content, "True") || @@ -2117,7 +2270,7 @@ IsNumber(char C) } int64_t -ParseArtVariantsString(char *Filename, token *ArtVariantsString) +ParseArtVariantsString(string *Filename, token *ArtVariantsString) { uint64_t Result = 0; bool Valid = TRUE; @@ -2176,7 +2329,7 @@ typedef enum } icon_type; icon_type -GetIconTypeFromString(char *Filename, token *T) +GetIconTypeFromString(string *Filename, token *T) { for(int i = 0; i < IT_COUNT; ++i) { @@ -2468,21 +2621,19 @@ typedef struct typedef struct asset { - asset_type Type; - char Filename[MAX_ASSET_FILENAME_LENGTH]; - int32_t Hash; - vec2 Dimensions; - sprite Sprite; - uint64_t Variants:63; - uint64_t Associated:1; - int32_t FilenameAt:29; - int32_t Known:1; - int32_t OffsetLandmarks:1; - int32_t DeferredUpdate:1; - uint32_t SearchLandmarkCount; - landmark *Search; - uint32_t PlayerLandmarkCount; - landmark *Player; + asset_type Type; + char Filename[MAX_ASSET_FILENAME_LENGTH]; + int32_t Hash; + vec2 Dimensions; + sprite Sprite; + uint64_t Variants:63; + uint64_t Associated:1; + int32_t FilenameAt:29; + int32_t Known:1; + int32_t OffsetLandmarks:1; + int32_t DeferredUpdate:1; + _memory_book(landmark) Search; + _memory_book(landmark) Player; } asset; asset BuiltinAssets[] = @@ -2787,6 +2938,23 @@ TrimString(string S, uint32_t CharsFromStart, uint32_t CharsFromEnd) return Result; } +string +TrimWhitespace(string S) +{ + string Result = S; + while(Result.Length > 0 && *Result.Base == ' ') + { + ++Result.Base; + --Result.Length; + } + + while(Result.Length > 0 && Result.Base[Result.Length - 1] == ' ') + { + --Result.Length; + } + return Result; +} + char *WatchTypeStrings[] = { "WT_HMML", @@ -2814,21 +2982,17 @@ typedef struct typedef struct { - int Descriptor; - string WatchedPath; - string TargetPath; - uint64_t FileCount; - uint64_t FileCapacity; - watch_file *Files; + int Descriptor; + string WatchedPath; + string TargetPath; + _memory_book(watch_file) Files; } watch_handle; typedef struct { - uint64_t Count; - uint64_t Capacity; - watch_handle *Handles; - memory_book Paths; - uint32_t DefaultEventsMask; + _memory_book(watch_handle) Handles; + memory_book Paths; + uint32_t DefaultEventsMask; } watch_handles; #define AFD 1 @@ -2848,6 +3012,37 @@ time_t LastQuoteFetch; #define GLOBAL_UPDATE_INTERVAL 1 // +void +WaitForInput() +{ + if(!Config || !Config->SuppressingPrompts) + { + fprintf(stderr, "Press Enter to continue...\n"); + getchar(); + } +} + +bool +SeekConfirmation(char *Message, bool Default) +{ + bool Result = Default; + if(!Config || !Config->SuppressingPrompts) + { + fprintf(stderr, "%s (%s)\n", Message, Default == TRUE ? "Y/n" : "y/N"); + switch(getchar()) + { + case 'n': + case 'N': + Result = FALSE; break; + case 'y': + case 'Y': + Result = TRUE; break; + default: break; + }; + } + return Result; +} + typedef struct { db_header_project *Project; @@ -2863,6 +3058,32 @@ typedef struct int16_t PrevIndex, PreDeletionThisIndex, ThisIndex, NextIndex; } neighbourhood; +typedef struct +{ + char *Name; + colour_code Colour; +} edit_type; + +typedef enum +{ + EDIT_INSERTION, + EDIT_APPEND, + EDIT_REINSERTION, + EDIT_DELETION, + EDIT_ADDITION, + EDIT_SKIP, +} edit_type_id; + +edit_type EditTypes[] = +{ + { "Inserted", CS_ADDITION }, + { "Appended", CS_ADDITION }, + { "Reinserted", CS_REINSERTION }, + { "Deleted", CS_DELETION }, + { "Added", CS_ADDITION }, + { "Skipped", CS_ERROR }, +}; + void LogUsage(buffer *Buffer) { @@ -2921,6 +3142,19 @@ LogError(int LogLevel, char *Format, ...) } } +void +LogEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTitle, bool Private) +{ + if(Private) + { + LogError(LOG_NOTICE, "Privately %s %.*s/%.*s", EditTypes[EditType].Name, (int)Lineage.Length, Lineage.Base, (int)EntryID.Length, EntryID.Base); + } + else + { + LogError(LOG_NOTICE, "%s %.*s/%.*s - %.*s", EditTypes[EditType].Name, (int)Lineage.Length, Lineage.Base, (int)EntryID.Length, EntryID.Base, (int)EntryTitle->Length, EntryTitle->Base); + } +} + typedef struct { buffer IncludesSearch; @@ -2953,7 +3187,7 @@ typedef struct char VODPlatform[16]; } buffers; -int +rc ClaimBuffer(buffer *Buffer, buffer_id ID, int Size) { if(MemoryArena.Ptr - MemoryArena.Location + Size > MemoryArena.Size) @@ -3048,67 +3282,33 @@ typedef enum SP_SEARCH = -1, } special_page_id; -// TODO(matt): Consider putting the ref_info and quote_info into linked lists on the heap, just to avoid all the hardcoded sizes - typedef struct { - char Date[32]; - char Text[512]; + uint32_t Date; + string Text; } quote_info; typedef struct { - char Timecode[8]; + char *Timecode; int Identifier; } identifier; -#define MAX_REF_IDENTIFIER_COUNT 64 typedef struct { - char RefTitle[620]; - char ID[512]; - char URL[512]; - char Source[256]; - identifier Identifier[MAX_REF_IDENTIFIER_COUNT]; - int IdentifierCount; + string RefTitle; + string URL; + string Source; + char *ID; + _memory_book(identifier) Identifier; } ref_info; typedef struct { - char Marker[32]; - char WrittenText[32]; + string Marker; + string WrittenText; } category_info; -typedef struct -{ - category_info Category[64]; - int Count; -} categories; - -typedef struct -{ - char *Name; - colour_code Colour; -} edit_type; - -typedef enum -{ - EDIT_INSERTION, - EDIT_APPEND, - EDIT_REINSERTION, - EDIT_DELETION, - EDIT_ADDITION, -} edit_type_id; - -edit_type EditTypes[] = -{ - { "Inserted", CS_ADDITION }, - { "Appended", CS_ADDITION }, - { "Reinserted", CS_REINSERTION }, - { "Deleted", CS_DELETION }, - { "Added", CS_ADDITION } -}; - #define CopyString(Dest, DestSize, Format, ...) CopyString_(__LINE__, (Dest), (DestSize), (Format), ##__VA_ARGS__) __attribute__ ((format (printf, 4, 5))) int @@ -3299,13 +3499,12 @@ CopyStringToBufferHTMLSafe_(int LineNumber, buffer *Dest, string String) #define CopyStringToBufferHTMLSafeBreakingOnSlash(Dest, String) CopyStringToBufferHTMLSafeBreakingOnSlash_(__LINE__, Dest, String) void -CopyStringToBufferHTMLSafeBreakingOnSlash_(int LineNumber, buffer *Dest, char *String) +CopyStringToBufferHTMLSafeBreakingOnSlash_(int LineNumber, buffer *Dest, string String) { - char *Start = String; - int Length = StringLength(String); - while(*String) + int Length = String.Length; + for(int i = 0; i < String.Length; ++i) { - switch(*String) + switch(String.Base[i]) { case '<': *Dest->Ptr++ = '&'; *Dest->Ptr++ = 'l'; *Dest->Ptr++ = 't'; *Dest->Ptr++ = ';'; Length += 3; break; case '>': *Dest->Ptr++ = '&'; *Dest->Ptr++ = 'g'; *Dest->Ptr++ = 't'; *Dest->Ptr++ = ';'; Length += 3; break; @@ -3313,14 +3512,13 @@ CopyStringToBufferHTMLSafeBreakingOnSlash_(int LineNumber, buffer *Dest, char *S case '\'': *Dest->Ptr++ = '&'; *Dest->Ptr++ = '#'; *Dest->Ptr++ = '3'; *Dest->Ptr++ = '9'; *Dest->Ptr++ = ';'; Length += 4; break; case '\"': *Dest->Ptr++ = '&'; *Dest->Ptr++ = 'q'; *Dest->Ptr++ = 'u'; *Dest->Ptr++ = 'o'; *Dest->Ptr++ = 't'; *Dest->Ptr++ = ';'; Length += 5; break; case '/': *Dest->Ptr++ = '/'; *Dest->Ptr++ = '&'; *Dest->Ptr++ = '#'; *Dest->Ptr++ = '8'; *Dest->Ptr++ = '2'; *Dest->Ptr++ = '0'; *Dest->Ptr++ = '3'; *Dest->Ptr++ = ';'; Length += 7; break; - default: *Dest->Ptr++ = *String; break; + default: *Dest->Ptr++ = String.Base[i]; break; } - ++String; } if(Dest->Ptr - Dest->Location >= Dest->Size) { fprintf(stderr, "CopyStringToBufferHTMLSafeBreakingOnSlash(%s) call on line %d cannot accommodate %d(+1)-character HTML-sanitised string:\n" - "%s\n", BufferIDStrings[Dest->ID], LineNumber, Length, Start); + "%.*s\n", BufferIDStrings[Dest->ID], LineNumber, Length, (int)String.Length, String.Base); __asm__("int3"); } *Dest->Ptr = '\0'; @@ -3415,23 +3613,25 @@ OffsetLandmarks(buffer *Dest, buffer *Src, page_type PageType) asset *Asset = GetPlaceInBook(&Assets, i); if(PageType == PAGE_PLAYER) { - for(int LandmarkIndex = 0; LandmarkIndex < Asset->PlayerLandmarkCount; ++LandmarkIndex) + for(int LandmarkIndex = 0; LandmarkIndex < Asset->Player.ItemCount; ++LandmarkIndex) { - if(Asset->Player[LandmarkIndex].BufferID == Src->ID) + landmark *This = GetPlaceInBook(&Asset->Player, LandmarkIndex); + if(This->BufferID == Src->ID) { - Asset->Player[LandmarkIndex].Offset += Dest->Ptr - Dest->Location; - Asset->Player[LandmarkIndex].BufferID = Dest->ID; + This->Offset += Dest->Ptr - Dest->Location; + This->BufferID = Dest->ID; } } } else { - for(int LandmarkIndex = 0; LandmarkIndex < Asset->SearchLandmarkCount; ++LandmarkIndex) + for(int LandmarkIndex = 0; LandmarkIndex < Asset->Search.ItemCount; ++LandmarkIndex) { - if(Asset->Search[LandmarkIndex].BufferID == Src->ID) + landmark *This = GetPlaceInBook(&Asset->Search, LandmarkIndex); + if(This->BufferID == Src->ID) { - Asset->Search[LandmarkIndex].Offset += Dest->Ptr - Dest->Location; - Asset->Search[LandmarkIndex].BufferID = Dest->ID; + This->Offset += Dest->Ptr - Dest->Location; + This->BufferID = Dest->ID; } } } @@ -3782,8 +3982,8 @@ InitTemplate(template *Template, string Location, template_type Type) fprintf(stderr, "%sPacking%s template: %s\n", ColourStrings[CS_ONGOING], ColourStrings[CS_END], Template->File.Path); ReadFileIntoBuffer(&Template->File); ClearTemplateMetadata(Template); - InitBook(&Template->Metadata.Tags, sizeof(tag_offset), 16, MBT_TAG_OFFSET); - InitBook(&Template->Metadata.NavBuffer, sizeof(navigation_buffer), 4, MBT_NAVIGATION_BUFFER); + Template->Metadata.Tags = InitBook(MBT_TAG_OFFSET, 16); + Template->Metadata.NavBuffer = InitBook(MBT_NAVIGATION_BUFFER, 4); } int @@ -3938,19 +4138,18 @@ ConstructURLPrefix(buffer *URLPrefix, asset_type AssetType, enum8(pages) PageTyp } } -// TODO(matt): Do a PushSpeaker() type of thing typedef struct { hsl_colour Colour; person *Person; - char *Abbreviation; + string Abbreviation; bool Seen; } speaker; typedef struct { - speaker *Speaker; - uint64_t Count; + _memory_book(speaker) Speakers; + memory_book Abbreviations; } speakers; enum @@ -4305,8 +4504,6 @@ PrintAssetAndLandmarks(db_asset *A, uint16_t *Index) "\n"); PrintAsset(A, Index); db_landmark *FirstLandmark = LocateFirstLandmark(A); - //Colourise(CS_BLACK_BOLD); fprintf(stderr, "%4u ", 0); Colourise(CS_END); - //PrintLandmark(FirstLandmark); for(uint16_t i = 0; i < A->LandmarkCount; ++i) { db_landmark *This = FirstLandmark + i; @@ -4318,7 +4515,6 @@ PrintAssetAndLandmarks(db_asset *A, uint16_t *Index) { PrintC(CS_BLACK_BOLD, " │ "); } - //Colourise(CS_BLACK_BOLD); fprintf(stderr, "%4u ", i); Colourise(CS_END); PrintLandmark(This, &i); } fprintf(stderr, "\n"); @@ -4369,23 +4565,15 @@ SkipAssetsBlock(db_block_assets *Block) typedef struct { uint64_t CurrentGeneration; - uint64_t Count; - uint32_t *EntriesInGeneration; + _memory_book(uint32_t) EntriesInGeneration; } project_generations; -void -PushGeneration(project_generations *G) -{ - G->EntriesInGeneration = Fit(G->EntriesInGeneration, sizeof(*G->EntriesInGeneration), G->Count, 4, TRUE); - ++G->Count; -} - db_project_index GetCurrentProjectIndex(project_generations *G) { db_project_index Result = {}; Result.Generation = G->CurrentGeneration; - Result.Index = G->EntriesInGeneration[G->CurrentGeneration]; + Result.Index = *(uint32_t *)GetPlaceInBook(&G->EntriesInGeneration, G->CurrentGeneration); return Result; } @@ -4394,15 +4582,16 @@ AddEntryToGeneration(project_generations *G, project *P) { if(G) { - if(G->Count <= G->CurrentGeneration) + if(G->EntriesInGeneration.ItemCount <= G->CurrentGeneration) { - PushGeneration(G); + MakeSpaceInBook(&G->EntriesInGeneration); } if(P) { P->Index = GetCurrentProjectIndex(G); } - ++G->EntriesInGeneration[G->CurrentGeneration]; + uint32_t *This = GetPlaceInBook(&G->EntriesInGeneration, G->CurrentGeneration); + ++*This; } } @@ -4421,18 +4610,13 @@ DecrementCurrentGeneration(project_generations *G) void PrintGenerations(project_generations *G, bool IndicateCurrentGeneration) { - for(uint64_t i = 0; i < G->Count; ++i) + for(uint64_t i = 0; i < G->EntriesInGeneration.ItemCount; ++i) { - fprintf(stderr, "%lu: %u%s\n", i, G->EntriesInGeneration[i], IndicateCurrentGeneration && i == G->CurrentGeneration ? " [Current Generation]" : ""); + uint32_t *This = GetPlaceInBook(&G->EntriesInGeneration, i); + fprintf(stderr, "%lu: %u%s\n", i, *This, IndicateCurrentGeneration && i == G->CurrentGeneration ? " [Current Generation]" : ""); } } -void -FreeGenerations(project_generations *G) -{ - FreeAndResetCount(G->EntriesInGeneration, G->Count); -} - void * SkipProject(db_header_project *Project) { @@ -4694,8 +4878,7 @@ void * PrintAssetsBlock_(db_block_assets *B, int LineNumber) { #if 0 - fprintf(stderr, "[%d] ", LineNumber); - PrintFunctionName("PrintAssetsBlock()"); + PrintLinedFunctionName(LineNumber, "PrintAssetsBlock()"); #endif if(!B) { B = LocateBlock(B_ASET); } PrintC(CS_BLUE_BOLD, "\n" @@ -4885,9 +5068,9 @@ PrintWatchHandle(watch_handle *W) PrintStringC(CS_GREEN_BOLD, W->TargetPath); } - for(int i = 0; i < W->FileCount; ++i) + for(int i = 0; i < W->Files.ItemCount; ++i) { - watch_file *This = W->Files + i; + watch_file *This = GetPlaceInBook(&W->Files, i); fprintf(stderr, "\n" " "); PrintWatchFile(This); @@ -4913,9 +5096,9 @@ PrintWatchHandles_(int LineNumber) }; fprintf(stderr, "\n" "%s%s%s [%i] PrintWatchHandles()", T.UpperLeftCorner, T.Horizontal, T.UpperRight, LineNumber); - for(int i = 0; i < WatchHandles.Count; ++i) + for(int i = 0; i < WatchHandles.Handles.ItemCount; ++i) { - watch_handle *This = WatchHandles.Handles + i; + watch_handle *This = GetPlaceInBook(&WatchHandles.Handles, i); fprintf(stderr, "\n"); PrintWatchHandle(This); } @@ -4937,9 +5120,9 @@ void PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extension, watch_type Type, project *Project, asset *Asset) { bool Required = TRUE; - for(int i = 0; i < Handle->FileCount; ++i) + for(int i = 0; i < Handle->Files.ItemCount; ++i) { - watch_file *This = Handle->Files + i; + watch_file *This = GetPlaceInBook(&Handle->Files, i); if(This->Type == Type) { if(Extension != EXT_NULL) @@ -4950,23 +5133,18 @@ PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extens break; } } - else + else if(StringsMatch(This->Path, Filepath)) { - if(StringsMatch(This->Path, Filepath)) - { - Required = FALSE; - EraseCurrentStringFromBook(&WatchHandles.Paths); - break; - } + Required = FALSE; + EraseCurrentStringFromBook(&WatchHandles.Paths); + break; } } - } if(Required) { - Handle->Files = FitShrinkable(Handle->Files, sizeof(*Handle->Files), Handle->FileCount, &Handle->FileCapacity, 16, TRUE); - watch_file *New = Handle->Files + Handle->FileCount; + watch_file *New = MakeSpaceInBook(&Handle->Files); if(Extension != EXT_NULL) { New->Extension = Extension; @@ -4978,7 +5156,6 @@ PushWatchFileUniquely(watch_handle *Handle, string Filepath, extension_id Extens New->Type = Type; New->Project = Project; New->Asset = Asset; - ++Handle->FileCount; } } @@ -5023,11 +5200,8 @@ WriteFilenameThenTargetPathIntoBook(memory_book *M, string Path, watch_type Type if(IsSymlink(OriginalPath0)) { - //fprintf(stderr, "%s\n", OriginalPath0); int PathLength = readlink(OriginalPath0, ResolvedSymlinkPath, 4096); FullPath = Wrap0i(ResolvedSymlinkPath, PathLength); - //PrintString(FullPath); - //fprintf(stderr, "\n"); } else { @@ -5171,11 +5345,12 @@ PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *P string TargetPath = FilenameAndDir.B; watch_handle *Watch = 0; - for(int i = 0; i < WatchHandles.Count; ++i) + for(int i = 0; i < WatchHandles.Handles.ItemCount; ++i) { - if(StringsMatch(TargetPath, WatchHandles.Handles[i].TargetPath)) + watch_handle *This = GetPlaceInBook(&WatchHandles.Handles, i); + if(StringsMatch(TargetPath, This->TargetPath)) { - Watch = WatchHandles.Handles + i; + Watch = This; EraseCurrentStringFromBook(&WatchHandles.Paths); break; } @@ -5183,8 +5358,8 @@ PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *P if(!Watch) { - WatchHandles.Handles = FitShrinkable(WatchHandles.Handles, sizeof(*WatchHandles.Handles), WatchHandles.Count, &WatchHandles.Capacity, 8, TRUE); - Watch = WatchHandles.Handles + WatchHandles.Count; + Watch = MakeSpaceInBook(&WatchHandles.Handles); + Watch->Files = InitBook(MBT_WATCH_FILE, 16); Watch->TargetPath = TargetPath; Watch->WatchedPath = GetNearestExistingPath(TargetPath); @@ -5194,7 +5369,6 @@ PushWatchHandle(string Path, extension_id Extension, watch_type Type, project *P CopyStringNoFormat(WatchablePath0, sizeof(WatchablePath0), Watch->WatchedPath); Watch->Descriptor = inotify_add_watch(inotifyInstance, WatchablePath0, WatchHandles.DefaultEventsMask); - ++WatchHandles.Count; //PrintWatchHandles(); } @@ -5205,9 +5379,9 @@ bool DescriptorIsRedundant(int Descriptor) { bool Result = TRUE; - for(int i = 0; i < WatchHandles.Count; ++i) + for(int i = 0; i < WatchHandles.Handles.ItemCount; ++i) { - watch_handle *This = WatchHandles.Handles + i; + watch_handle *This = GetPlaceInBook(&WatchHandles.Handles, i); if(Descriptor == This->Descriptor) { string WatchablePath = GetNearestExistingPath(This->TargetPath); @@ -5225,9 +5399,9 @@ int GetExistingWatchDescriptor(string Path) { int Result = -1; - for(int i = 0; i < WatchHandles.Count; ++i) + for(int i = 0; i < WatchHandles.Handles.ItemCount; ++i) { - watch_handle *This = WatchHandles.Handles + i; + watch_handle *This = GetPlaceInBook(&WatchHandles.Handles, i); if(StringsMatch(Path, This->WatchedPath)) { Result = This->Descriptor; @@ -5240,9 +5414,9 @@ GetExistingWatchDescriptor(string Path) void UpdateWatchHandles(int Descriptor) { - for(int i = 0; i < WatchHandles.Count; ++i) + for(int i = 0; i < WatchHandles.Handles.ItemCount; ++i) { - watch_handle *This = WatchHandles.Handles + i; + watch_handle *This = GetPlaceInBook(&WatchHandles.Handles, i); if(Descriptor == This->Descriptor) { string WatchablePath = GetNearestExistingPath(This->TargetPath); @@ -5321,6 +5495,7 @@ UpdateNeighbourhoodPointers(neighbourhood *N, file_signposts *S) int UpdateAssetInDB(asset *Asset) { + MEM_TEST_TOP("UpdateAssetInDB"); int AssetIndexInDB = SAI_UNSET; if(Config->QueryString.Length > 0) { @@ -5415,6 +5590,7 @@ UpdateAssetInDB(asset *Asset) Asset->Known = TRUE; } } + MEM_TEST_END("UpdateAssetInDB"); return AssetIndexInDB; } @@ -5436,17 +5612,15 @@ PushAssetLandmark(buffer *Dest, asset *Asset, int PageType, bool GrowableBuffer) if(PageType == PAGE_PLAYER) { - Asset->Player = Fit(Asset->Player, sizeof(*Asset->Player), Asset->PlayerLandmarkCount, 8, TRUE); - Asset->Player[Asset->PlayerLandmarkCount].Offset = Dest->Ptr - Dest->Location; - Asset->Player[Asset->PlayerLandmarkCount].BufferID = Dest->ID; - ++Asset->PlayerLandmarkCount; + landmark *This = MakeSpaceInBook(&Asset->Player); + This->Offset = Dest->Ptr - Dest->Location; + This->BufferID = Dest->ID; } else { - Asset->Search = Fit(Asset->Search, sizeof(*Asset->Search), Asset->SearchLandmarkCount, 8, TRUE); - Asset->Search[Asset->SearchLandmarkCount].Offset = Dest->Ptr - Dest->Location; - Asset->Search[Asset->SearchLandmarkCount].BufferID = Dest->ID; - ++Asset->SearchLandmarkCount; + landmark *This = MakeSpaceInBook(&Asset->Search); + This->Offset = Dest->Ptr - Dest->Location; + This->BufferID = Dest->ID; } if(GrowableBuffer) @@ -5469,8 +5643,8 @@ ResetAssetLandmarks(void) for(int AssetIndex = 0; AssetIndex < Assets.ItemCount; ++AssetIndex) { asset *A = GetPlaceInBook(&Assets, AssetIndex); - FreeAndResetCount(A->Player, A->PlayerLandmarkCount); - FreeAndResetCount(A->Search, A->SearchLandmarkCount); + FreeAndReinitialiseBook(&A->Player); + FreeAndReinitialiseBook(&A->Search); A->OffsetLandmarks = FALSE; } } @@ -5515,16 +5689,6 @@ UpdateAsset(asset *Asset, bool Defer) return AssetIndexInDB; } -//typedef struct -//{ -// vec2 TileDim; -// int32_t XLight; -// int32_t XDark; -// int32_t YNormal; -// int32_t YFocused; -// int32_t YDisabled; -//} sprite; - void ComputeSpriteData(asset *A) { @@ -5568,7 +5732,12 @@ PlaceAsset(string Filename, asset_type Type, uint64_t Variants, bool Associated, This->Associated = Associated; ClearCopyString(This->Filename, sizeof(This->Filename), "%.*s", (int)Filename.Length, Filename.Base); This->FilenameAt = FinalPathComponentPosition(Filename); - if(Position == Assets.ItemCount && !This->Known) { ++Assets.ItemCount; } + if(Position == Assets.ItemCount && !This->Known) + { + This->Player = InitBook(MBT_LANDMARK, 8); + This->Search = InitBook(MBT_LANDMARK, 8); + ++Assets.ItemCount; + } file File = {}; File.Path = ConstructAssetPath(&File, Filename, Type); @@ -5615,8 +5784,8 @@ FreeAssets(memory_book *A) for(int i = 0; i < A->ItemCount; ++i) { asset *Asset = GetPlaceInBook(&Assets, i); - FreeAndResetCount(Asset->Search, Asset->SearchLandmarkCount); - FreeAndResetCount(Asset->Player, Asset->PlayerLandmarkCount); + FreeAndReinitialiseBook(&Asset->Search); + FreeAndReinitialiseBook(&Asset->Player); } FreeBook(A); } @@ -5649,7 +5818,7 @@ InitBuiltinAssets(void) void InitAssets(void) { - InitBook(&Assets, sizeof(asset), 16, MBT_ASSET); + Assets = InitBook(MBT_ASSET, 16); InitBuiltinAssets(); db_block_assets *AssetsBlock = LocateBlock(B_ASET); if(AssetsBlock) @@ -5681,56 +5850,39 @@ ConstructResolvedAssetURL(buffer *Buffer, asset *Asset, enum8(pages) PageType) Free(ResolvablePath); } -void -ClearNullTerminatedString(char *String) +string +InitialString(memory_book *Abbreviations, string Src) { - while(*String) - { - *String++ = '\0'; - } -} - -char * -InitialString(char *Dest, string Src) -{ - Free(Dest); + ResetPen(Abbreviations); + string Result = {}; string Char = Wrap0i(Src.Base, 1); - ExtendString0(&Dest, Char); + Result = ExtendStringInBook(Abbreviations, Char); for(int i = 1; i < Src.Length; ++i) { if(Src.Base[i] == ' ' && i < Src.Length) { ++i; Char = Wrap0i(Src.Base + i, 1); - ExtendString0(&Dest, Char); + Result = ExtendStringInBook(Abbreviations, Char); } } - return Dest; + return Result; } -char * -GetFirstSubstring(char *Dest, string Src) +string +GetFirstSubstring(string Src) { - Free(Dest); - string Substring = {}; - Substring.Base = Src.Base; - for(int i = 0; i < Src.Length; ++i, ++Substring.Length) - { - if(Src.Base[i] == ' ') - { - ExtendString0(&Dest, Substring); - return Dest; - } - } - ExtendString0(&Dest, Substring); - return Dest; + string Result = Src; + for(Result.Length = 0; Result.Length < Src.Length && Result.Base[Result.Length] != ' '; ++Result.Length) { } + return Result; } -char * -InitialAndGetFinalString(char *Dest, string Src) +string +InitialAndGetFinalString(memory_book *Abbreviations, string Src) { - Free(Dest); + ResetPen(Abbreviations); + string Result = {}; int FinalStringBase; for(FinalStringBase = Src.Length; FinalStringBase > 0; --FinalStringBase) { @@ -5747,8 +5899,8 @@ InitialAndGetFinalString(char *Dest, string Src) if(FinalStringBase > 0) { string Initial = Wrap0i(Src.Base, 1); - ExtendString0(&Dest, Initial); - ExtendString0(&Dest, Wrap0(". ")); + Result = ExtendStringInBook(Abbreviations, Initial); + Result = ExtendStringInBook(Abbreviations, Wrap0(". ")); for(int i = 0; i < FinalStringBase; ++i) { @@ -5756,24 +5908,26 @@ InitialAndGetFinalString(char *Dest, string Src) { ++i; string Initial = Wrap0i(Src.Base + i, 1); - ExtendString0(&Dest, Initial); - ExtendString0(&Dest, Wrap0(". ")); + Result = ExtendStringInBook(Abbreviations, Initial); + Result = ExtendStringInBook(Abbreviations, Wrap0(". ")); } } } - ExtendString0(&Dest, FinalString); - return Dest; + Result = ExtendStringInBook(Abbreviations, FinalString); + return Result; } bool -AbbreviationsClash(speakers *Speakers) +AbbreviationsClash(memory_book *Speakers) { - for(int i = 0; i < Speakers->Count; ++i) + for(int i = 0; i < Speakers->ItemCount; ++i) { - for(int j = i + 1; j < Speakers->Count; ++j) + speaker *A = GetPlaceInBook(Speakers, i); + for(int j = i + 1; j < Speakers->ItemCount; ++j) { - if(!StringsDiffer0(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[j].Abbreviation)) + speaker *B = GetPlaceInBook(Speakers, j); + if(StringsMatch(A->Abbreviation, B->Abbreviation)) { return TRUE; } @@ -5787,36 +5941,40 @@ SortAndAbbreviateSpeakers(speakers *Speakers) { // TODO(matt): Handle Abbreviation in its new form as a char *, rather than a fixed-sized char[], so probably doing // MakeString0() or ExpandString0() or something - for(int i = 0; i < Speakers->Count; ++i) + for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) { - for(int j = i + 1; j < Speakers->Count; ++j) + speaker *A = GetPlaceInBook(&Speakers->Speakers, i); + for(int j = i + 1; j < Speakers->Speakers.ItemCount; ++j) { - if(StringsDiffer(Speakers->Speaker[i].Person->ID, Speakers->Speaker[j].Person->ID) > 0) + speaker *B = GetPlaceInBook(&Speakers->Speakers, j); + if(StringsDiffer(A->Person->ID, B->Person->ID) > 0) { - person *Temp = Speakers->Speaker[j].Person; - Speakers->Speaker[j].Person = Speakers->Speaker[i].Person; - Speakers->Speaker[i].Person = Temp; + person *Temp = B->Person; + B->Person = A->Person; + A->Person = Temp; break; } } } - for(int i = 0; i < Speakers->Count; ++i) + for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) { - StringToColourHash(&Speakers->Speaker[i].Colour, Speakers->Speaker[i].Person->ID); - Speakers->Speaker[i].Abbreviation = InitialString(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[i].Person->Name); + speaker *This = GetPlaceInBook(&Speakers->Speakers, i); + StringToColourHash(&This->Colour, This->Person->ID); + This->Abbreviation = InitialString(&Speakers->Abbreviations, This->Person->Name); } int Attempt = 0; - while(AbbreviationsClash(Speakers)) + while(AbbreviationsClash(&Speakers->Speakers)) { - for(int i = 0; i < Speakers->Count; ++i) + for(int i = 0; i < Speakers->Speakers.ItemCount; ++i) { + speaker *This = GetPlaceInBook(&Speakers->Speakers, i); switch(Attempt) { - case 0: Speakers->Speaker[i].Abbreviation = GetFirstSubstring(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[i].Person->Name); break; - case 1: Speakers->Speaker[i].Abbreviation = InitialAndGetFinalString(Speakers->Speaker[i].Abbreviation, Speakers->Speaker[i].Person->Name); break; - case 2: Free(Speakers->Speaker[i].Abbreviation); ExtendString0(&Speakers->Speaker[i].Abbreviation, Speakers->Speaker[i].Person->Name); break; + case 0: This->Abbreviation = GetFirstSubstring(This->Person->Name); break; + case 1: This->Abbreviation = InitialAndGetFinalString(&Speakers->Abbreviations, This->Person->Name); break; + case 2: This->Abbreviation = This->Person->Name; break; } } ++Attempt; @@ -6182,13 +6340,12 @@ PushIcon(buffer *Buffer, bool GrowableBuffer, icon_type IconType, string IconStr } void -PushCredentials(buffer *CreditsMenu, speakers *Speakers, person *Actor, role Role, bool *RequiresCineraJS) +PushCredentials(buffer *CreditsMenu, memory_book *Speakers, person *Actor, role Role, bool *RequiresCineraJS) { if(Role != R_INDEXER) { - Speakers->Speaker = Fit(Speakers->Speaker, sizeof(*Speakers->Speaker), Speakers->Count, 4, TRUE); - Speakers->Speaker[Speakers->Count].Person = Actor; - ++Speakers->Count; + speaker *This = MakeSpaceInBook(Speakers); + This->Person = Actor; } if(CreditsMenu->Ptr == CreditsMenu->Location) @@ -6242,24 +6399,12 @@ PushCredentials(buffer *CreditsMenu, speakers *Speakers, person *Actor, role Rol } void -FreeCredentials(speakers *S) +ErrorCredentials(string HMMLFilepath, string Actor, role Role) { - FreeAndResetCount(S->Speaker, S->Count); -} - -void -WaitForInput() -{ - fprintf(stderr, "Press Enter to continue...\n"); - getchar(); -} - -void -ErrorCredentials(string Actor, role Role) -{ - Colourise(CS_ERROR); - fprintf(stderr, "No credentials for %s %.*s\n", RoleStrings[Role], (int)Actor.Length, Actor.Base); - Colourise(CS_END); + ErrorFilenameAndLineNumber(&HMMLFilepath, 0, S_ERROR, ED_INDEXING); + fprintf(stderr, "No credentials for %s%s%s: %s%.*s%s\n", + ColourStrings[CS_YELLOW_BOLD], RoleStrings[Role], ColourStrings[CS_END], + ColourStrings[CS_MAGENTA_BOLD], (int)Actor.Length, Actor.Base, ColourStrings[CS_END]); fprintf(stderr, "Perhaps you'd like to add a new person to your config file, e.g.:\n" " person = \"%.*s\"\n" " {\n" @@ -6271,16 +6416,16 @@ ErrorCredentials(string Actor, role Role) } int -BuildCredits(buffer *CreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speakers, bool *RequiresCineraJS) +BuildCredits(string HMMLFilepath, buffer *CreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speakers, bool *RequiresCineraJS) { person *Host = GetPersonFromConfig(Wrap0(Metadata->member)); if(Host) { - PushCredentials(CreditsMenu, Speakers, Host, R_HOST, RequiresCineraJS); + PushCredentials(CreditsMenu, &Speakers->Speakers, Host, R_HOST, RequiresCineraJS); } else { - ErrorCredentials(Wrap0(Metadata->member), R_HOST); + ErrorCredentials(HMMLFilepath, Wrap0(Metadata->member), R_HOST); return CreditsError_NoCredentials; } @@ -6289,11 +6434,11 @@ BuildCredits(buffer *CreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speake person *CoHost = GetPersonFromConfig(Wrap0(Metadata->co_hosts[i])); if(CoHost) { - PushCredentials(CreditsMenu, Speakers, CoHost, R_COHOST, RequiresCineraJS); + PushCredentials(CreditsMenu, &Speakers->Speakers, CoHost, R_COHOST, RequiresCineraJS); } else { - ErrorCredentials(Wrap0(Metadata->co_hosts[i]), R_COHOST); + ErrorCredentials(HMMLFilepath, Wrap0(Metadata->co_hosts[i]), R_COHOST); return CreditsError_NoCredentials; } } @@ -6303,16 +6448,16 @@ BuildCredits(buffer *CreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speake person *Guest = GetPersonFromConfig(Wrap0(Metadata->guests[i])); if(Guest) { - PushCredentials(CreditsMenu, Speakers, Guest, R_GUEST, RequiresCineraJS); + PushCredentials(CreditsMenu, &Speakers->Speakers, Guest, R_GUEST, RequiresCineraJS); } else { - ErrorCredentials(Wrap0(Metadata->guests[i]), R_GUEST); + ErrorCredentials(HMMLFilepath, Wrap0(Metadata->guests[i]), R_GUEST); return CreditsError_NoCredentials; } } - if(Speakers->Count > 1) + if(Speakers->Speakers.ItemCount > 1) { SortAndAbbreviateSpeakers(Speakers); } @@ -6324,11 +6469,11 @@ BuildCredits(buffer *CreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speake person *Indexer = GetPersonFromConfig(Wrap0(Metadata->annotators[i])); if(Indexer) { - PushCredentials(CreditsMenu, Speakers, Indexer, R_INDEXER, RequiresCineraJS); + PushCredentials(CreditsMenu, &Speakers->Speakers, Indexer, R_INDEXER, RequiresCineraJS); } else { - ErrorCredentials(Wrap0(Metadata->annotators[i]), R_INDEXER); + ErrorCredentials(HMMLFilepath, Wrap0(Metadata->annotators[i]), R_INDEXER); return CreditsError_NoCredentials; } } @@ -6341,7 +6486,7 @@ BuildCredits(buffer *CreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speake " \n" " \n"); } - fprintf(stderr, "Missing \"indexer\" in the [video] node\n"); + IndexingError(&HMMLFilepath, 0, S_ERROR, "Missing \"indexer\" in the [video] node", 0); return CreditsError_NoIndexer; } @@ -6355,250 +6500,186 @@ BuildCredits(buffer *CreditsMenu, HMML_VideoMetaData *Metadata, speakers *Speake return RC_SUCCESS; } -enum -{ - REF_SITE = 1 << 0, - REF_PAGE = 1 << 1, - REF_URL = 1 << 2, - REF_TITLE = 1 << 3, - REF_ARTICLE = 1 << 4, - REF_AUTHOR = 1 << 5, - REF_EDITOR = 1 << 6, - REF_PUBLISHER = 1 << 7, - REF_ISBN = 1 << 8, -} reference_fields; - -int -BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMML_Reference *Ref, HMML_Annotation *Anno) -{ - if(Ref->isbn) - { - CopyString(ReferencesArray[UniqueRefs].ID, sizeof(ReferencesArray[UniqueRefs].ID), "%s", Ref->isbn); - if(!Ref->url) { CopyString(ReferencesArray[UniqueRefs].URL, sizeof(ReferencesArray[UniqueRefs].URL), "https://isbndb.com/book/%s", Ref->isbn); } - else { CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, sizeof(ReferencesArray[UniqueRefs].URL), Wrap0(Ref->url)); } - } - else if(Ref->url) - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].ID, sizeof(ReferencesArray[UniqueRefs].ID), Wrap0(Ref->url)); - CopyStringNoFormat(ReferencesArray[UniqueRefs].URL, sizeof(ReferencesArray[UniqueRefs].URL), Wrap0(Ref->url)); - } - else { return RC_INVALID_REFERENCE; } - - int Mask = 0; - if(Ref->site) { Mask |= REF_SITE; } - if(Ref->page) { Mask |= REF_PAGE; } - if(Ref->title) { Mask |= REF_TITLE; } - if(Ref->article) { Mask |= REF_ARTICLE; } - if(Ref->author) { Mask |= REF_AUTHOR; } - if(Ref->editor) { Mask |= REF_EDITOR; } - if(Ref->publisher) { Mask |= REF_PUBLISHER; } - - // TODO(matt): Consider handling the various combinations more flexibly, unless we defer this stuff until we have the - // reference store, in which we could optionally customise the display of each reference entry - switch(Mask) - { - case (REF_TITLE | REF_AUTHOR | REF_PUBLISHER): - { - CopyString(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), "%s (%s)", Ref->author, Ref->publisher); - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->title)); - } break; - case (REF_AUTHOR | REF_SITE | REF_PAGE): - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), Wrap0(Ref->site)); - CopyString(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), "%s: \"%s\"", Ref->author, Ref->page); - } break; - case (REF_PAGE | REF_TITLE): - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), Wrap0(Ref->title)); - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->page)); - } break; - case (REF_SITE | REF_PAGE): - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), Wrap0(Ref->site)); - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->page)); - } break; - case (REF_SITE | REF_TITLE): - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), Wrap0(Ref->site)); - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->title)); - } break; - case (REF_TITLE | REF_AUTHOR): - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), Wrap0(Ref->author)); - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->title)); - } break; - case (REF_ARTICLE | REF_AUTHOR): - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), Wrap0(Ref->author)); - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->article)); - } break; - case (REF_TITLE | REF_PUBLISHER): - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].Source, sizeof(ReferencesArray[UniqueRefs].Source), Wrap0(Ref->publisher)); - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->title)); - } break; - case REF_TITLE: - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->title)); - } break; - case REF_SITE: - { - CopyStringNoFormat(ReferencesArray[UniqueRefs].RefTitle, sizeof(ReferencesArray[UniqueRefs].RefTitle), Wrap0(Ref->site)); - } break; - default: return RC_INVALID_REFERENCE; break; - } - - CopyString(ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Timecode, sizeof(ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Timecode), "%s", Anno->time); - ReferencesArray[UniqueRefs].Identifier[ReferencesArray[UniqueRefs].IdentifierCount].Identifier = RefIdentifier; - return RC_SUCCESS; -} - void -InsertCategory(categories *GlobalTopics, categories *LocalTopics, categories *GlobalMedia, categories *LocalMedia, string Marker) +InsertCategory(_memory_book(category_info) *GlobalTopics, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *GlobalMedia, _memory_book(category_info) *LocalMedia, string Marker) { medium *Medium = GetMediumFromProject(CurrentProject, Marker); if(Medium) { int MediumIndex; - for(MediumIndex = 0; MediumIndex < LocalMedia->Count; ++MediumIndex) + bool MadeLocalSpace = FALSE; + for(MediumIndex = 0; MediumIndex < LocalMedia->ItemCount; ++MediumIndex) { - if(!StringsDifferLv0(Medium->ID, LocalMedia->Category[MediumIndex].Marker)) + category_info *This = GetPlaceInBook(LocalMedia, MediumIndex); + if(StringsMatch(Medium->ID, This->Marker)) { return; } - if((StringsDifferLv0(Medium->Name, LocalMedia->Category[MediumIndex].WrittenText)) < 0) + if((StringsDiffer(Medium->Name, This->WrittenText)) < 0) { + MakeSpaceInBook(LocalMedia); + MadeLocalSpace = TRUE; int CategoryCount; - for(CategoryCount = LocalMedia->Count; CategoryCount > MediumIndex; --CategoryCount) + for(CategoryCount = LocalMedia->ItemCount - 1; CategoryCount > MediumIndex; --CategoryCount) { - ClearCopyString(LocalMedia->Category[CategoryCount].Marker, sizeof(LocalMedia->Category[CategoryCount].Marker), "%s", LocalMedia->Category[CategoryCount-1].Marker); - ClearCopyString(LocalMedia->Category[CategoryCount].WrittenText, sizeof(LocalMedia->Category[CategoryCount].WrittenText), "%s", LocalMedia->Category[CategoryCount-1].WrittenText); + category_info *Src = GetPlaceInBook(LocalMedia, CategoryCount - 1); + category_info *Dest = GetPlaceInBook(LocalMedia, CategoryCount); + Dest->Marker = Src->Marker; + Dest->WrittenText = Src->WrittenText; } - ClearCopyString(LocalMedia->Category[CategoryCount].Marker, sizeof(LocalMedia->Category[CategoryCount].Marker), "%.*s", (int)Medium->ID.Length, Medium->ID.Base); - ClearCopyString(LocalMedia->Category[CategoryCount].WrittenText, sizeof(LocalMedia->Category[CategoryCount].WrittenText), "%.*s", (int)Medium->Name.Length, Medium->Name.Base); + category_info *New = GetPlaceInBook(LocalMedia, CategoryCount); + New->Marker = Medium->ID; + New->WrittenText = Medium->Name; break; } } - if(MediumIndex == LocalMedia->Count) + if(!MadeLocalSpace) { MakeSpaceInBook(LocalMedia); } + + if(MediumIndex == LocalMedia->ItemCount - 1) { - CopyString(LocalMedia->Category[MediumIndex].Marker, sizeof(LocalMedia->Category[MediumIndex].Marker), "%.*s", (int)Medium->ID.Length, Medium->ID.Base); - CopyString(LocalMedia->Category[MediumIndex].WrittenText, sizeof(LocalMedia->Category[MediumIndex].WrittenText), "%.*s", (int)Medium->Name.Length, Medium->Name.Base); + category_info *New = GetPlaceInBook(LocalMedia, MediumIndex); + New->Marker = Medium->ID; + New->WrittenText = Medium->Name; } - ++LocalMedia->Count; - - for(MediumIndex = 0; MediumIndex < GlobalMedia->Count; ++MediumIndex) + bool MadeGlobalSpace = FALSE; + for(MediumIndex = 0; MediumIndex < GlobalMedia->ItemCount; ++MediumIndex) { - if(!StringsDifferLv0(Medium->ID, GlobalMedia->Category[MediumIndex].Marker)) + category_info *This = GetPlaceInBook(GlobalMedia, MediumIndex); + if(StringsMatch(Medium->ID, This->Marker)) { return; } - if((StringsDifferLv0(Medium->Name, GlobalMedia->Category[MediumIndex].WrittenText)) < 0) + if((StringsDiffer(Medium->Name, This->WrittenText)) < 0) { + MakeSpaceInBook(GlobalMedia); + MadeGlobalSpace = TRUE; int CategoryCount; - for(CategoryCount = GlobalMedia->Count; CategoryCount > MediumIndex; --CategoryCount) + for(CategoryCount = GlobalMedia->ItemCount - 1; CategoryCount > MediumIndex; --CategoryCount) { - ClearCopyString(GlobalMedia->Category[CategoryCount].Marker, sizeof(GlobalMedia->Category[CategoryCount].Marker), "%s", GlobalMedia->Category[CategoryCount-1].Marker); - ClearCopyString(GlobalMedia->Category[CategoryCount].WrittenText, sizeof(GlobalMedia->Category[CategoryCount].WrittenText), "%s", GlobalMedia->Category[CategoryCount-1].WrittenText); + category_info *Src = GetPlaceInBook(GlobalMedia, CategoryCount - 1); + category_info *Dest = GetPlaceInBook(GlobalMedia, CategoryCount); + Dest->Marker = Src->Marker; + Dest->WrittenText = Src->WrittenText; } - ClearCopyString(GlobalMedia->Category[CategoryCount].Marker, sizeof(GlobalMedia->Category[CategoryCount].Marker), "%.*s", (int)Medium->ID.Length, Medium->ID.Base); - ClearCopyString(GlobalMedia->Category[CategoryCount].WrittenText, sizeof(GlobalMedia->Category[CategoryCount].WrittenText), "%.*s", (int)Medium->Name.Length, Medium->Name.Base); + category_info *New = GetPlaceInBook(GlobalMedia, CategoryCount); + New->Marker = Medium->ID; + New->WrittenText = Medium->Name; break; } } - if(MediumIndex == GlobalMedia->Count) - { - CopyString(GlobalMedia->Category[MediumIndex].Marker, sizeof(GlobalMedia->Category[MediumIndex].Marker), "%.*s", (int)Medium->ID.Length, Medium->ID.Base); - CopyString(GlobalMedia->Category[MediumIndex].WrittenText, sizeof(GlobalMedia->Category[MediumIndex].WrittenText), "%.*s", (int)Medium->Name.Length, Medium->Name.Base); - } + if(!MadeGlobalSpace) { MakeSpaceInBook(GlobalMedia); } - ++GlobalMedia->Count; + if(MediumIndex == GlobalMedia->ItemCount - 1) + { + category_info *New = GetPlaceInBook(GlobalMedia, MediumIndex); + New->Marker = Medium->ID; + New->WrittenText = Medium->Name; + } } else { + bool MadeLocalSpace = FALSE; int TopicIndex; - for(TopicIndex = 0; TopicIndex < LocalTopics->Count; ++TopicIndex) + for(TopicIndex = 0; TopicIndex < LocalTopics->ItemCount; ++TopicIndex) { - if(!StringsDifferLv0(Marker, LocalTopics->Category[TopicIndex].Marker)) + category_info *This = GetPlaceInBook(LocalTopics, TopicIndex); + if(StringsMatch(Marker, This->Marker)) { return; } - if((StringsDifferLv0(Marker, LocalTopics->Category[TopicIndex].Marker)) < 0) + if((StringsDiffer(Marker, This->Marker)) < 0) { int CategoryCount; - for(CategoryCount = LocalTopics->Count; CategoryCount > TopicIndex; --CategoryCount) + MakeSpaceInBook(LocalTopics); + MadeLocalSpace = TRUE; + for(CategoryCount = LocalTopics->ItemCount - 1; CategoryCount > TopicIndex; --CategoryCount) { - ClearCopyString(LocalTopics->Category[CategoryCount].Marker, sizeof(LocalTopics->Category[CategoryCount].Marker), "%s", LocalTopics->Category[CategoryCount-1].Marker); + category_info *Src = GetPlaceInBook(LocalTopics, CategoryCount - 1); + category_info *Dest = GetPlaceInBook(LocalTopics, CategoryCount); + Dest->Marker = Src->Marker; } - ClearCopyString(LocalTopics->Category[CategoryCount].Marker, sizeof(LocalTopics->Category[CategoryCount].Marker), "%.*s", (int)Marker.Length, Marker.Base); + category_info *New = GetPlaceInBook(LocalTopics, CategoryCount); + New->Marker = Marker; break; } } - if(TopicIndex == LocalTopics->Count) + if(!MadeLocalSpace) { MakeSpaceInBook(LocalTopics); } + + if(TopicIndex == LocalTopics->ItemCount - 1) { - CopyString(LocalTopics->Category[TopicIndex].Marker, sizeof(LocalTopics->Category[TopicIndex].Marker), "%.*s", (int)Marker.Length, Marker.Base); + category_info *New = GetPlaceInBook(LocalTopics, TopicIndex); + New->Marker = Marker; } - ++LocalTopics->Count; - - for(TopicIndex = 0; TopicIndex < GlobalTopics->Count; ++TopicIndex) + bool MadeGlobalSpace = FALSE; + for(TopicIndex = 0; TopicIndex < GlobalTopics->ItemCount; ++TopicIndex) { - if(!StringsDifferLv0(Marker, GlobalTopics->Category[TopicIndex].Marker)) + category_info *This = GetPlaceInBook(GlobalTopics, TopicIndex); + if(StringsMatch(Marker, This->Marker)) { return; } // NOTE(matt): This successfully sorts "nullTopic" at the end, but maybe figure out a more general way to force the // order of stuff, perhaps blocks of dudes that should sort to the start / end - if(((StringsDifferLv0(Marker, GlobalTopics->Category[TopicIndex].Marker)) < 0 || !StringsDiffer0(GlobalTopics->Category[TopicIndex].Marker, "nullTopic"))) + if(((StringsDiffer(Marker, This->Marker)) < 0 || StringsMatch(This->Marker, Wrap0("nullTopic")))) { - if(StringsDifferLv0(Marker, "nullTopic")) // NOTE(matt): This test (with the above || condition) forces nullTopic never to be inserted, only appended + if(StringsDiffer(Marker, Wrap0("nullTopic"))) // NOTE(matt): This test (with the above || condition) forces nullTopic never to be inserted, only appended { + MakeSpaceInBook(GlobalTopics); + MadeGlobalSpace = TRUE; int CategoryCount; - for(CategoryCount = GlobalTopics->Count; CategoryCount > TopicIndex; --CategoryCount) + for(CategoryCount = GlobalTopics->ItemCount - 1; CategoryCount > TopicIndex; --CategoryCount) { - ClearCopyString(GlobalTopics->Category[CategoryCount].Marker, sizeof(GlobalTopics->Category[CategoryCount].Marker), "%s", GlobalTopics->Category[CategoryCount-1].Marker); + category_info *Src = GetPlaceInBook(GlobalTopics, CategoryCount - 1); + category_info *Dest = GetPlaceInBook(GlobalTopics, CategoryCount); + Dest->Marker = Src->Marker; } - ClearCopyString(GlobalTopics->Category[CategoryCount].Marker, sizeof(GlobalTopics->Category[CategoryCount].Marker), "%.*s", (int)Marker.Length, Marker.Base); + category_info *New = GetPlaceInBook(GlobalTopics, CategoryCount); + New->Marker = Marker; break; } } } - if(TopicIndex == GlobalTopics->Count) - { - CopyString(GlobalTopics->Category[TopicIndex].Marker, sizeof(GlobalTopics->Category[TopicIndex].Marker), "%.*s", (int)Marker.Length, Marker.Base); - } + if(!MadeGlobalSpace) { MakeSpaceInBook(GlobalTopics); } - ++GlobalTopics->Count; + if(TopicIndex == GlobalTopics->ItemCount - 1) + { + category_info *New = GetPlaceInBook(GlobalTopics, TopicIndex); + New->Marker = Marker; + } } } void -BuildTimestampClass(buffer *TimestampClass, categories *LocalTopics, categories *LocalMedia, string DefaultMedium) +BuildTimestampClass(buffer *TimestampClass, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *LocalMedia, string DefaultMedium) { - if(LocalTopics->Count == 1 && !StringsDiffer0(LocalTopics->Category[0].Marker, "nullTopic")) + category_info *FirstLocalTopic = GetPlaceInBook(LocalTopics, 0); + if(LocalTopics->ItemCount == 1 && StringsMatch(FirstLocalTopic->Marker, Wrap0("nullTopic"))) { // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(LocalTopics->Category[0].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", LocalTopics->Category[0].Marker); + char SanitisedMarker[FirstLocalTopic->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)FirstLocalTopic->Marker.Length, FirstLocalTopic->Marker.Base); SanitisePunctuation(SanitisedMarker); CopyStringToBuffer(TimestampClass, " cat_%s", SanitisedMarker); } else { - for(int i = 0; i < LocalTopics->Count; ++i) + for(int i = 0; i < LocalTopics->ItemCount; ++i) { + category_info *This = GetPlaceInBook(LocalTopics, i); // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(LocalTopics->Category[i].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", LocalTopics->Category[i].Marker); + char SanitisedMarker[This->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); SanitisePunctuation(SanitisedMarker); CopyStringToBuffer(TimestampClass, " cat_%s", @@ -6606,24 +6687,26 @@ BuildTimestampClass(buffer *TimestampClass, categories *LocalTopics, categories } } - if(LocalMedia->Count == 1 && !StringsDifferLv0(DefaultMedium, LocalMedia->Category[0].Marker)) + category_info *FirstLocalMedium = GetPlaceInBook(LocalMedia, 0); + if(LocalMedia->ItemCount == 1 && StringsMatch(DefaultMedium, FirstLocalMedium->Marker)) { // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(LocalMedia->Category[0].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", LocalMedia->Category[0].Marker); + char SanitisedMarker[FirstLocalMedium->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)FirstLocalMedium->Marker.Length, FirstLocalMedium->Marker.Base); SanitisePunctuation(SanitisedMarker); CopyStringToBuffer(TimestampClass, " %s", SanitisedMarker); } else { - for(int i = 0; i < LocalMedia->Count; ++i) + for(int i = 0; i < LocalMedia->ItemCount; ++i) { + category_info *This = GetPlaceInBook(LocalMedia, i); // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(LocalMedia->Category[i].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", LocalMedia->Category[i].Marker); + char SanitisedMarker[This->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); SanitisePunctuation(SanitisedMarker); - medium *Medium = GetMediumFromProject(CurrentProject, Wrap0i(LocalMedia->Category[i].Marker, sizeof(LocalMedia->Category[i].Marker))); + medium *Medium = GetMediumFromProject(CurrentProject, This->Marker); if(Medium) { if(Medium->Hidden) @@ -6642,44 +6725,50 @@ BuildTimestampClass(buffer *TimestampClass, categories *LocalTopics, categories } void -BuildCategoryIcons(buffer *CategoryIcons, categories *LocalTopics, categories *LocalMedia, string DefaultMedium, bool *RequiresCineraJS) +BuildCategoryIcons(buffer *CategoryIcons, _memory_book(category_info) *LocalTopics, _memory_book(category_info) *LocalMedia, string DefaultMedium, bool *RequiresCineraJS) { bool CategoriesSpan = FALSE; - if(!(LocalTopics->Count == 1 && !StringsDiffer0(LocalTopics->Category[0].Marker, "nullTopic") - && LocalMedia->Count == 1 && !StringsDifferLv0(DefaultMedium, LocalMedia->Category[0].Marker))) + category_info *FirstLocalTopic = GetPlaceInBook(LocalTopics, 0); + category_info *FirstLocalMedium = GetPlaceInBook(LocalMedia, 0); + if(!(LocalTopics->ItemCount == 1 && StringsMatch(FirstLocalTopic->Marker, Wrap0("nullTopic")) + && LocalMedia->ItemCount == 1 && StringsMatch(DefaultMedium, FirstLocalMedium->Marker))) { CategoriesSpan = TRUE; CopyStringToBuffer(CategoryIcons, ""); } - if(!(LocalTopics->Count == 1 && !StringsDiffer0(LocalTopics->Category[0].Marker, "nullTopic"))) + if(!(LocalTopics->ItemCount == 1 && StringsMatch(FirstLocalTopic->Marker, Wrap0("nullTopic")))) { - for(int i = 0; i < LocalTopics->Count; ++i) + for(int i = 0; i < LocalTopics->ItemCount; ++i) { + category_info *This = GetPlaceInBook(LocalTopics, i); // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(LocalTopics->Category[i].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", LocalTopics->Category[i].Marker); + char SanitisedMarker[This->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); SanitisePunctuation(SanitisedMarker); - CopyStringToBuffer(CategoryIcons, "
", - LocalTopics->Category[i].Marker, + CopyStringToBuffer(CategoryIcons, "
", + (int)This->Marker.Length, This->Marker.Base, SanitisedMarker); } } - if(!(LocalMedia->Count == 1 && !StringsDifferLv0(DefaultMedium, LocalMedia->Category[0].Marker))) + if(!(LocalMedia->ItemCount == 1 && StringsMatch(DefaultMedium, FirstLocalMedium->Marker))) { - for(int i = 0; i < LocalMedia->Count; ++i) + for(int i = 0; i < LocalMedia->ItemCount; ++i) { + category_info *This = GetPlaceInBook(LocalMedia, i); // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(LocalMedia->Category[i].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", LocalMedia->Category[i].Marker); + char SanitisedMarker[This->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); SanitisePunctuation(SanitisedMarker); - medium *Medium = GetMediumFromProject(CurrentProject, Wrap0i(LocalMedia->Category[i].Marker, sizeof(LocalMedia->Category[i].Marker))); + medium *Medium = GetMediumFromProject(CurrentProject, This->Marker); if(Medium && !Medium->Hidden) { - CopyStringToBuffer(CategoryIcons, "
", LocalMedia->Category[i].WrittenText, LocalMedia->Category[i].Marker); + CopyStringToBuffer(CategoryIcons, "
", + (int)This->WrittenText.Length, This->WrittenText.Base, + (int)This->Marker.Length, This->Marker.Base); PushIcon(CategoryIcons, FALSE, Medium->IconType, Medium->Icon, Medium->IconAsset, Medium->IconVariants, PAGE_PLAYER, RequiresCineraJS); @@ -6722,6 +6811,7 @@ CurlIntoBuffer(char *InPtr, size_t CharLength, size_t Chars, char **OutputPtr) CURLcode CurlQuotes(buffer *QuoteStaging, char *QuotesURL) { + MEM_TEST_TOP("CurlQuotes()"); fprintf(stderr, "%sFetching%s quotes: %s\n", ColourStrings[CS_ONGOING], ColourStrings[CS_END], QuotesURL); CURLcode Result = CURLE_FAILED_INIT; @@ -6730,12 +6820,17 @@ CurlQuotes(buffer *QuoteStaging, char *QuotesURL) curl_easy_setopt(curl, CURLOPT_WRITEDATA, &QuoteStaging->Ptr); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, CurlIntoBuffer); curl_easy_setopt(curl, CURLOPT_URL, QuotesURL); - if((Result = curl_easy_perform(curl))) + /* */ MEM_TEST_MID("CurlQuotes()"); + /* +MEM */ Result = curl_easy_perform(curl); + /* */ MEM_TEST_MID("CurlQuotes()"); + if(Result) { fprintf(stderr, "%s\n", curl_easy_strerror(Result)); } curl_easy_cleanup(curl); + curl = 0; } + MEM_TEST_END("CurlQuotes()"); return Result; } @@ -6754,8 +6849,35 @@ GetStringFromBufferT(buffer *B, char Terminator) return Result; } +string +UnixTimeToDateString(memory_book *Book, int64_t Time) +{ + string Result = {}; + + // NOTE(matt): Stack-string + char DayString[3] = { }; + strftime(DayString, 3, "%e", gmtime(&Time)); + + Result = WriteStringInBook(Book, TrimWhitespace(Wrap0(DayString))); + + int Day = String0ToInt(DayString); + + // NOTE(matt): Stack-string + if(DayString[1] == '1' && Day != 11) { Result = ExtendStringInBook(Book, Wrap0("st ")); } + else if(DayString[1] == '2' && Day != 12) { Result = ExtendStringInBook(Book, Wrap0("nd ")); } + else if(DayString[1] == '3' && Day != 13) { Result = ExtendStringInBook(Book, Wrap0("rd ")); } + else { Result = ExtendStringInBook(Book, Wrap0("th ")); } + + // NOTE(matt): Stack-string + char MonthYear[32] = {}; + strftime(MonthYear, 32, "%B, %Y", gmtime(&Time)); + Result = ExtendStringInBook(Book, Wrap0(MonthYear)); + + return Result; +} + rc -SearchQuotes(buffer *QuoteStaging, int CacheSize, quote_info *Info, int ID) +SearchQuotes(memory_book *Strings, buffer *QuoteStaging, int CacheSize, quote_info *Info, int ID) { rc Result = RC_UNFOUND; QuoteStaging->Ptr = QuoteStaging->Location; @@ -6769,26 +6891,12 @@ SearchQuotes(buffer *QuoteStaging, int CacheSize, quote_info *Info, int ID) string InTime = GetStringFromBufferT(QuoteStaging, ','); QuoteStaging->Ptr += InTime.Length + 1; // Skip past the ',' - long int Time = StringToInt(InTime); - // NOTE(matt): Stack-string - char DayString[3] = { 0 }; - strftime(DayString, 3, "%d", gmtime(&Time)); - int Day = String0ToInt(DayString); + Info->Date = StringToInt(InTime); - // NOTE(matt): Stack-string - char DaySuffix[3]; if(DayString[1] == '1' && Day != 11) { CopyString(DaySuffix, sizeof(DaySuffix), "st"); } - else if(DayString[1] == '2' && Day != 12) { CopyString(DaySuffix, sizeof(DaySuffix), "nd"); } - else if(DayString[1] == '3' && Day != 13) { CopyString(DaySuffix, sizeof(DaySuffix), "rd"); } - else { CopyString(DaySuffix, sizeof(DaySuffix), "th"); } + string Text = GetStringFromBufferT(QuoteStaging, '\n'); + Info->Text = WriteStringInBook(Strings, Text); - // NOTE(matt): Stack-string - char MonthYear[32]; - strftime(MonthYear, 32, "%B, %Y", gmtime(&Time)); - CopyString(Info->Date, sizeof(Info->Date), "%d%s %s", Day, DaySuffix, MonthYear); - - CopyStringNoFormatT(Info->Text, sizeof(Info->Text), QuoteStaging->Ptr, '\n'); - - Result = RC_FOUND; + Result = RC_SUCCESS; break; } else @@ -6804,8 +6912,9 @@ SearchQuotes(buffer *QuoteStaging, int CacheSize, quote_info *Info, int ID) } rc -BuildQuote(quote_info *Info, string Speaker, int ID, bool ShouldFetchQuotes) +BuildQuote(memory_book *Strings, quote_info *Info, string Speaker, int ID, bool ShouldFetchQuotes) { + MEM_TEST_TOP("BuildQuote()"); rc Result = RC_SUCCESS; // TODO(matt): Generally sanitise this function, e.g. using MakeString0(), curling in to a growing buffer, etc. // NOTE(matt): Stack-string @@ -6815,43 +6924,40 @@ BuildQuote(quote_info *Info, string Speaker, int ID, bool ShouldFetchQuotes) char QuoteCachePath[256] = {}; CopyString(QuoteCachePath, sizeof(QuoteCachePath), "%s/%.*s", QuoteCacheDir, (int)Speaker.Length, Speaker.Base); - FILE *QuoteCache; // NOTE(matt): Stack-string char QuotesURL[256] = {}; // TODO(matt): Make the URL configurable and also handle the case in which the .raw isn't available CopyString(QuotesURL, sizeof(QuotesURL), "https://dev.abaines.me.uk/quotes/%.*s.raw", (int)Speaker.Length, Speaker.Base); - bool CacheAvailable = FALSE; - if(!(QuoteCache = fopen(QuoteCachePath, "a+"))) + bool CacheAvailable = FALSE; + FILE *QuoteCache = fopen(QuoteCachePath, "a+"); + if(QuoteCache) { - if(MakeDir(Wrap0i(QuoteCacheDir, sizeof(QuoteCacheDir)))) - { - CacheAvailable = TRUE; - } - if(!(QuoteCache = fopen(QuoteCachePath, "a+"))) - { - fprintf(stderr, "Unable to open quote cache %s: %s\n", QuoteCachePath, strerror(errno)); - } - else - { - CacheAvailable = TRUE; - } + CacheAvailable = TRUE; } else { - CacheAvailable = TRUE; + MakeDir(Wrap0i(QuoteCacheDir, sizeof(QuoteCacheDir))); + QuoteCache = fopen(QuoteCachePath, "a+"); + if(QuoteCache) + { + CacheAvailable = TRUE; + } + else + { + // TODO(matt): SystemError(); + fprintf(stderr, "Unable to open quote cache %s: %s\n", QuoteCachePath, strerror(errno)); + } } buffer QuoteStaging = {}; QuoteStaging.ID = BID_QUOTE_STAGING; QuoteStaging.Size = Kilobytes(256); - if(!(QuoteStaging.Location = malloc(QuoteStaging.Size))) - { - fclose(QuoteCache); - Result = RC_ERROR_MEMORY; - } + QuoteStaging.Location = malloc(QuoteStaging.Size); + QuoteStaging.Ptr = QuoteStaging.Location; + int CacheSize = 0; - if(Result != RC_ERROR_MEMORY) + if(QuoteStaging.Location) { #if DEBUG_MEM FILE *MemLog = fopen("/home/matt/cinera_mem", "a+"); @@ -6860,161 +6966,179 @@ BuildQuote(quote_info *Info, string Speaker, int ID, bool ShouldFetchQuotes) printf(" Allocated QuoteStaging (%ld)\n", QuoteStaging.Size); #endif - QuoteStaging.Ptr = QuoteStaging.Location; - - if(CacheAvailable) + if(!ShouldFetchQuotes) { - fseek(QuoteCache, 0, SEEK_END); - int FileSize = ftell(QuoteCache); - fseek(QuoteCache, 0, SEEK_SET); - - fread(QuoteStaging.Location, FileSize, 1, QuoteCache); - fclose(QuoteCache); - - if(ShouldFetchQuotes || SearchQuotes(&QuoteStaging, FileSize, Info, ID) == RC_UNFOUND) + if(CacheAvailable) { - if(CurlQuotes(&QuoteStaging, QuotesURL) == CURLE_OK) - { - if(!(QuoteCache = fopen(QuoteCachePath, "w"))) - { - perror(QuoteCachePath); - } - fwrite(QuoteStaging.Location, QuoteStaging.Ptr - QuoteStaging.Location, 1, QuoteCache); - fclose(QuoteCache); + fseek(QuoteCache, 0, SEEK_END); + CacheSize = ftell(QuoteCache); + fseek(QuoteCache, 0, SEEK_SET); - int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; - QuoteStaging.Ptr = QuoteStaging.Location; - Result = SearchQuotes(&QuoteStaging, CacheSize, Info, ID); - } - else + fread(QuoteStaging.Location, CacheSize, 1, QuoteCache); + fclose(QuoteCache); + rc SearchQuotesResult = SearchQuotes(Strings, &QuoteStaging, CacheSize, Info, ID); + if(SearchQuotesResult == RC_UNFOUND) { - Result = RC_UNFOUND; + ShouldFetchQuotes = TRUE; } } - } - else - { - if(CurlQuotes(&QuoteStaging, QuotesURL) == CURLE_OK) + else { - int CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; + ShouldFetchQuotes = TRUE; + } + } + + if(ShouldFetchQuotes) + { + /* */ MEM_TEST_MID("BuildQuote()"); + /* +MEM */ CURLcode CurlQuotesResult = CurlQuotes(&QuoteStaging, QuotesURL); + /* */ MEM_TEST_MID("BuildQuote()"); + if(CurlQuotesResult == CURLE_OK) + { + LastQuoteFetch = time(0); + CacheSize = QuoteStaging.Ptr - QuoteStaging.Location; QuoteStaging.Ptr = QuoteStaging.Location; - Result = SearchQuotes(&QuoteStaging, CacheSize, Info, ID); + Result = SearchQuotes(Strings, &QuoteStaging, CacheSize, Info, ID); } else { Result = RC_UNFOUND; } + + if(CacheAvailable) + { + QuoteCache = fopen(QuoteCachePath, "w"); + fwrite(QuoteStaging.Location, CacheSize, 1, QuoteCache); + fclose(QuoteCache); + } } FreeBuffer(&QuoteStaging); } + else + { + Result = RC_ERROR_MEMORY; + if(CacheAvailable) { fclose(QuoteCache); } + } + + MEM_TEST_END("BuildQuote()"); return Result; } rc GenerateTopicColours(neighbourhood *N, string Topic) { - // TODO(matt): Maybe straighten out the return code situation? + rc Result = RC_SUCCESS; // NOTE(matt): Stack-string char SanitisedTopic[Topic.Length + 1]; CopyString(SanitisedTopic, sizeof(SanitisedTopic), "%.*s", (int)Topic.Length, Topic.Base); SanitisePunctuation(SanitisedTopic); medium *Medium = GetMediumFromProject(CurrentProject, Topic); - if(Medium) + if(!Medium) { - return RC_NOOP; - } + file Topics = {}; + Topics.Path = 0; + Topics.Buffer.ID = BID_TOPICS; - file Topics = {}; - Topics.Buffer.ID = BID_TOPICS; - - if(Config->CSSDir.Length > 0) - { - Topics.Path = MakeString0("lslss", &Config->AssetsRootDir, "/", &Config->CSSDir, "/", BuiltinAssets[ASSET_CSS_TOPICS].Filename); - } - else - { - Topics.Path = MakeString0("lss", &Config->AssetsRootDir, "/", BuiltinAssets[ASSET_CSS_TOPICS].Filename); - } - - char *Ptr = Topics.Path + StringLength(Topics.Path) - 1; - while(*Ptr != '/') - { - --Ptr; - } - *Ptr = '\0'; - DIR *CSSDirHandle; // TODO(matt): open() - if(!(CSSDirHandle = opendir(Topics.Path))) - { - if(!MakeDir(Wrap0(Topics.Path))) + if(Config->CSSDir.Length > 0) { - LogError(LOG_ERROR, "Unable to create directory %s: %s", Topics.Path, strerror(errno)); - fprintf(stderr, "Unable to create directory %s: %s\n", Topics.Path, strerror(errno)); - return RC_ERROR_DIRECTORY; - }; - } - closedir(CSSDirHandle); - *Ptr = '/'; - - ReadFileIntoBuffer(&Topics); - - bool Exists = FALSE; - while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size) - { - Topics.Buffer.Ptr += StringLength(".category."); - if(!StringsDifferT(SanitisedTopic, Topics.Buffer.Ptr, ' ')) - { - Exists = TRUE; - break; - } - while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size && *Topics.Buffer.Ptr != '\n') - { - ++Topics.Buffer.Ptr; - } - ++Topics.Buffer.Ptr; - } - - if(!Exists) - { - Topics.Handle = fopen(Topics.Path, "a+"); - if(!StringsDifferLv0(Topic, "nullTopic")) - { - fprintf(Topics.Handle, ".category.%s { border: 1px solid transparent; background: transparent; }\n", - SanitisedTopic); + Topics.Path = MakeString0("lslss", &Config->AssetsRootDir, "/", &Config->CSSDir, "/", BuiltinAssets[ASSET_CSS_TOPICS].Filename); } else { - hsl_colour Colour; - StringToColourHash(&Colour, Topic); - fprintf(Topics.Handle, ".category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); }\n", - SanitisedTopic, Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness); + Topics.Path = MakeString0("lss", &Config->AssetsRootDir, "/", BuiltinAssets[ASSET_CSS_TOPICS].Filename); } + char *Ptr = Topics.Path + StringLength(Topics.Path) - 1; + while(*Ptr != '/') + { + --Ptr; + } + *Ptr = '\0'; + DIR *CSSDirHandle = opendir(Topics.Path); // TODO(matt): open() + if(!CSSDirHandle) + { + if(!MakeDir(Wrap0(Topics.Path))) + { + LogError(LOG_ERROR, "Unable to create directory %s: %s", Topics.Path, strerror(errno)); + fprintf(stderr, "Unable to create directory %s: %s\n", Topics.Path, strerror(errno)); + Result = RC_ERROR_DIRECTORY; + }; + } + closedir(CSSDirHandle); + + if(Result == RC_SUCCESS) + { + *Ptr = '/'; + + ReadFileIntoBuffer(&Topics); + + bool Exists = FALSE; + while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size) + { + Topics.Buffer.Ptr += StringLength(".category."); + if(!StringsDifferT(SanitisedTopic, Topics.Buffer.Ptr, ' ')) + { + Exists = TRUE; + break; + } + while(Topics.Buffer.Ptr - Topics.Buffer.Location < Topics.Buffer.Size && *Topics.Buffer.Ptr != '\n') + { + ++Topics.Buffer.Ptr; + } + ++Topics.Buffer.Ptr; + } + + if(!Exists) + { + Topics.Handle = fopen(Topics.Path, "a+"); + if(Topics.Handle) + { + if(StringsMatch(Topic, Wrap0("nullTopic"))) + { + fprintf(Topics.Handle, ".category.%s { border: 1px solid transparent; background: transparent; }\n", + SanitisedTopic); + } + else + { + hsl_colour Colour; + StringToColourHash(&Colour, Topic); + fprintf(Topics.Handle, ".category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); }\n", + SanitisedTopic, Colour.Hue, Colour.Saturation, Colour.Lightness, Colour.Hue, Colour.Saturation, Colour.Lightness); + } + #if DEBUG_MEM - MemLog = fopen("/home/matt/cinera_mem", "a+"); - fprintf(MemLog, " Freed Topics (%ld)\n", Topics.Buffer.Size); - fclose(MemLog); - printf(" Freed Topics (%ld)\n", Topics.Buffer.Size); + MemLog = fopen("/home/matt/cinera_mem", "a+"); + fprintf(MemLog, " Freed Topics (%ld)\n", Topics.Buffer.Size); + fclose(MemLog); + printf(" Freed Topics (%ld)\n", Topics.Buffer.Size); #endif - fclose(Topics.Handle); + fclose(Topics.Handle); - asset *Asset = GetPlaceInBook(&Assets, ASSET_CSS_TOPICS); - if(Asset->Known) - { - // NOTE(matt): We may index this out directly because InitBuiltinAssets() places it in its own known slot - UpdateAsset(Asset, TRUE); - UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); - } - else - { - asset *CSSTopics = BuiltinAssets + ASSET_CSS_TOPICS; - PlaceAsset(Wrap0(CSSTopics->Filename), CSSTopics->Type, CSSTopics->Variants, CSSTopics->Associated, ASSET_CSS_TOPICS); + asset *Asset = GetPlaceInBook(&Assets, ASSET_CSS_TOPICS); + if(Asset->Known) + { + // NOTE(matt): We may index this out directly because InitBuiltinAssets() places it in its own known slot + UpdateAsset(Asset, TRUE); + UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); + } + else + { + asset *CSSTopics = BuiltinAssets + ASSET_CSS_TOPICS; + PlaceAsset(Wrap0(CSSTopics->Filename), CSSTopics->Type, CSSTopics->Variants, CSSTopics->Associated, ASSET_CSS_TOPICS); + } + } + else + { + perror(Topics.Path); + Result = RC_ERROR_FILE; + } + } } + FreeFile(&Topics); } - - FreeFile(&Topics); - return RC_SUCCESS; + return Result; } void @@ -7755,7 +7879,7 @@ ParseNavigationTag(template *Template, template_tag_code TagCode, project *Proje return Result; } -int +rc PackTemplate(template *Template, string Location, template_type Type, project *Project) { // TODO(matt): Record line numbers and contextual information: @@ -7994,7 +8118,7 @@ ConstructPlayerURL(buffer *PlayerURL, db_header_project *P, string EntryOutput) } medium * -MediumExists(string Medium) +MediumExists(string HMMLFilepath, string Medium) { medium *Result = GetMediumFromProject(CurrentProject, Medium); if(!Result) @@ -8013,7 +8137,8 @@ MediumExists(string Medium) .Separator = "•", }; - fprintf(stderr, "Specified default medium \"%.*s\" not available. Valid media are:\n", (int)Medium.Length, Medium.Base); + IndexingError(&HMMLFilepath, 0, S_ERROR, "Specified default medium not available: ", &Medium); + fprintf(stderr, "Valid media are:\n"); for(int i = 0; i < CurrentProject->Medium.ItemCount; ++i) { medium *This = GetPlaceInBook(&CurrentProject->Medium, i); @@ -8535,8 +8660,7 @@ ExamineDB(void) #define HMMLCleanup() \ DeclaimPlayerBuffers(&PlayerBuffers); \ DeclaimIndexBuffers(&IndexBuffers); \ - DeclaimMenuBuffers(&MenuBuffers); \ - hmml_free(&HMML) + DeclaimMenuBuffers(&MenuBuffers) bool VideoIsPrivate(char *VideoID) @@ -8597,16 +8721,26 @@ VideoIsPrivate(char *VideoID) } speaker * -GetSpeaker(speakers Speakers, string Username) +GetSpeaker(memory_book *Speakers, string Username) { - for(int i = 0; i < Speakers.Count; ++i) + speaker *Result = 0; + for(int i = 0; i < Speakers->ItemCount; ++i) { - if(!StringsDifferCaseInsensitive(Speakers.Speaker[i].Person->ID, Username)) + speaker *This = GetPlaceInBook(Speakers, i); + if(!StringsDifferCaseInsensitive(This->Person->ID, Username)) { - return &Speakers.Speaker[i]; + Result = This; + break; } } - return 0; + return Result; +} + +void +FreeSpeakers(speakers *Speakers) +{ + FreeBook(&Speakers->Speakers); + FreeBook(&Speakers->Abbreviations); } bool @@ -8733,7 +8867,7 @@ void PrintLineageAndEntryID(string Lineage, string EntryID, bool AppendNewline) { PrintLineage(Lineage, FALSE); - fprintf(stderr, "/");//%.*s - %s\n", + fprintf(stderr, "/"); PrintStringC(CS_MAGENTA, EntryID); if(AppendNewline) { fprintf(stderr, "\n"); } } @@ -8749,6 +8883,20 @@ PrintLineageAndEntry(string Lineage, string EntryID, string EntryTitle, bool App if(AppendNewline) { fprintf(stderr, "\n"); } } +void +PrintEdit(edit_type_id EditType, string Lineage, string EntryID, string *EntryTitle, bool Private, bool AppendNewline) +{ + fprintf(stderr, "%s%s%s%s ", ColourStrings[EditTypes[EditType].Colour], Private ? "Privately " : "", EditTypes[EditType].Name, ColourStrings[CS_END]); + if(EntryTitle) + { + PrintLineageAndEntry(CurrentProject->Lineage, EntryID, *EntryTitle, AppendNewline); + } + else + { + PrintLineageAndEntryID(CurrentProject->Lineage, EntryID, AppendNewline); + } +} + rc DeletePlayerPageFromFilesystem(string BaseDir, string PlayerLocation, string EntryOutput, bool Relocating, bool Echo) { @@ -8782,656 +8930,417 @@ DeletePlayerPageFromFilesystem(string BaseDir, string PlayerLocation, string Ent return RC_SUCCESS; } -int -HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, neighbourhood *N) +speakers +InitSpeakers() { - RewindCollationBuffers(CollationBuffers); - Clear(CollationBuffers->Custom0, sizeof(CollationBuffers->Custom0)); - Clear(CollationBuffers->Custom1, sizeof(CollationBuffers->Custom1)); - Clear(CollationBuffers->Custom2, sizeof(CollationBuffers->Custom2)); - Clear(CollationBuffers->Custom3, sizeof(CollationBuffers->Custom3)); - Clear(CollationBuffers->Custom4, sizeof(CollationBuffers->Custom4)); - Clear(CollationBuffers->Custom5, sizeof(CollationBuffers->Custom5)); - Clear(CollationBuffers->Custom6, sizeof(CollationBuffers->Custom6)); - Clear(CollationBuffers->Custom7, sizeof(CollationBuffers->Custom7)); - Clear(CollationBuffers->Custom8, sizeof(CollationBuffers->Custom8)); - Clear(CollationBuffers->Custom9, sizeof(CollationBuffers->Custom9)); - Clear(CollationBuffers->Custom10, sizeof(CollationBuffers->Custom10)); - Clear(CollationBuffers->Custom11, sizeof(CollationBuffers->Custom11)); - Clear(CollationBuffers->Custom12, sizeof(CollationBuffers->Custom12)); - Clear(CollationBuffers->Custom13, sizeof(CollationBuffers->Custom13)); - Clear(CollationBuffers->Custom14, sizeof(CollationBuffers->Custom14)); - Clear(CollationBuffers->Custom15, sizeof(CollationBuffers->Custom15)); - Clear(CollationBuffers->Title, sizeof(CollationBuffers->Title)); - Clear(CollationBuffers->URLPlayer, sizeof(CollationBuffers->URLPlayer)); - Clear(CollationBuffers->URLSearch, sizeof(CollationBuffers->URLSearch)); - Clear(CollationBuffers->VODPlatform, sizeof(CollationBuffers->VODPlatform)); + speakers Result = {}; + Result.Speakers = InitBook(MBT_SPEAKER, 4); + Result.Abbreviations = InitBookOfStrings(64); + return Result; +} - // TODO(matt): Throughout this function dispense with Filename, in favour of the passed in BaseFilename, or Filepath? - // - // TODO(matt): A "MakeString0OnStack()" sort of function? - // NOTE(matt): Stack-string - int NullTerminationBytes = 1; - char Filename[BaseFilename.Length + ExtensionStrings[EXT_HMML].Length + NullTerminationBytes]; - char *P = Filename; - P += CopyStringToBarePtr(P, BaseFilename); - P += CopyStringToBarePtr(P, ExtensionStrings[EXT_HMML]); - *P = '\0'; +rc +ClaimMenuIndexAndPlayerBuffers(menu_buffers *MenuBuffers, index_buffers *IndexBuffers, player_buffers *PlayerBuffers) +{ + rc Result = RC_SUCCESS; - // NOTE(matt): Stack-string - char Filepath[CurrentProject->HMMLDir.Length + StringLength("/") + BaseFilename.Length + ExtensionStrings[EXT_HMML].Length + NullTerminationBytes]; - P = Filepath; - P += CopyStringToBarePtr(P, CurrentProject->HMMLDir); - P += CopyStringToBarePtr(P, Wrap0("/")); - P += CopyStringToBarePtr(P, Wrap0(Filename)); - *P = '\0'; + if(ClaimBuffer(&MenuBuffers->Quote, BID_MENU_BUFFERS_QUOTE, Kilobytes(32)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&MenuBuffers->Reference, BID_MENU_BUFFERS_REFERENCE, Kilobytes(32)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&MenuBuffers->Filter, BID_MENU_BUFFERS_FILTER, Kilobytes(16)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&MenuBuffers->FilterTopics, BID_MENU_BUFFERS_FILTER_TOPICS, Kilobytes(8)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&MenuBuffers->FilterMedia, BID_MENU_BUFFERS_FILTER_MEDIA, Kilobytes(8)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&MenuBuffers->Credits, BID_MENU_BUFFERS_CREDITS, Kilobytes(8)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; - FILE *InFile = fopen(Filepath, "r"); - if(!InFile) + // NOTE(matt): Tree structure of IndexBuffers dependencies + // Master + // Header + // Class + // Data + // Text + // CategoryIcons + + if(ClaimBuffer(&IndexBuffers->Master, BID_INDEX_BUFFERS_MASTER, Kilobytes(8)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&IndexBuffers->Header, BID_INDEX_BUFFERS_HEADER, Kilobytes(1)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&IndexBuffers->Class, BID_INDEX_BUFFERS_CLASS, 256) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&IndexBuffers->Data, BID_INDEX_BUFFERS_DATA, Kilobytes(1)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&IndexBuffers->Text, BID_INDEX_BUFFERS_TEXT, Kilobytes(4)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&IndexBuffers->CategoryIcons, BID_INDEX_BUFFERS_CATEGORY_ICONS, Kilobytes(1)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + + if(ClaimBuffer(&PlayerBuffers->Menus, BID_PLAYER_BUFFERS_MENUS, Kilobytes(32)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&PlayerBuffers->Main, BID_PLAYER_BUFFERS_MAIN, Kilobytes(512)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + if(ClaimBuffer(&PlayerBuffers->Script, BID_PLAYER_BUFFERS_SCRIPT, Kilobytes(8)) == RC_ARENA_FULL) { Result = RC_ARENA_FULL; }; + + return Result; +} + +enum +{ + REF_SITE = 1 << 0, + REF_PAGE = 1 << 1, + REF_URL = 1 << 2, + REF_TITLE = 1 << 3, + REF_ARTICLE = 1 << 4, + REF_AUTHOR = 1 << 5, + REF_EDITOR = 1 << 6, + REF_PUBLISHER = 1 << 7, + REF_ISBN = 1 << 8, +} reference_fields; + +ref_info * +BuildReference(memory_book *Strings, _memory_book(ref_info) *ReferencesArray, HMML_Reference *Ref, rc *ReturnCode) +{ + ref_info *Result = 0; + char *ID = 0; + if(Ref->isbn) { - LogError(LOG_ERROR, "Unable to open %s: %s", Filename, strerror(errno)); - fprintf(stderr, "Unable to open %s: %s\n", Filename, strerror(errno)); - return RC_ERROR_FILE; + ID = Ref->isbn; + } + else if(Ref->url) + { + ID = Ref->url; } - HMML_Output HMML = hmml_parse_file(InFile); - fclose(InFile); - - // TODO(matt): Use the HMML.output here if it is set, else do the GetBaseFilename0() thing - // - // Once we have the idea of an HMML.output, exhaustively make sure we're using the right thing in the right - // place, i.e. BaseFilename vs Output - if(HMML.well_formed) + if(ID) { - bool HaveErrors = FALSE; - - if(BaseFilename.Length > MAX_BASE_FILENAME_LENGTH) + ref_info *This = MakeSpaceInBook(ReferencesArray); + This->Identifier = InitBook(MBT_IDENTIFIER, 4); + // TODO(matt): Just point into the Ref directly, rather than copying strings? + if(Ref->isbn) { - fprintf(stderr, "%sBase filename \"%.*s\" is too long (%ld/%d characters)%s\n", ColourStrings[CS_ERROR], (int)BaseFilename.Length, BaseFilename.Base, BaseFilename.Length, MAX_BASE_FILENAME_LENGTH, ColourStrings[CS_END]); - HaveErrors = TRUE; + This->ID = Ref->isbn; + if(!Ref->url) { This->URL = WriteStringInBook(Strings, Wrap0("https://isbndb.com/book/")); This->URL = ExtendStringInBook(Strings, Wrap0(Ref->isbn)); } + else { This->URL = WriteStringInBook(Strings, Wrap0(Ref->url)); } + } + else if(Ref->url) + { + This->ID = Ref->url; + This->URL = WriteStringInBook(Strings, Wrap0(Ref->url)); } - if(!HMML.metadata.title) - { - fprintf(stderr, "Please set the title attribute in the [video] node of your .hmml file\n"); - HaveErrors = TRUE; - } - else if(StringLength(HMML.metadata.title) > MAX_TITLE_LENGTH) - { - fprintf(stderr, "%sVideo title \"%s\" is too long (%ld/%d characters)%s\n", ColourStrings[CS_ERROR], HMML.metadata.title, StringLength(HMML.metadata.title), MAX_TITLE_LENGTH, ColourStrings[CS_END]); - HaveErrors = TRUE; - } - else - { - ClearCopyStringNoFormat(CollationBuffers->Title, sizeof(CollationBuffers->Title), Wrap0(HMML.metadata.title)); - } + int Mask = 0; + if(Ref->site) { Mask |= REF_SITE; } + if(Ref->page) { Mask |= REF_PAGE; } + if(Ref->title) { Mask |= REF_TITLE; } + if(Ref->article) { Mask |= REF_ARTICLE; } + if(Ref->author) { Mask |= REF_AUTHOR; } + if(Ref->editor) { Mask |= REF_EDITOR; } + if(Ref->publisher) { Mask |= REF_PUBLISHER; } - if(!HMML.metadata.member) + // TODO(matt): Consider handling the various combinations more flexibly, unless we defer this stuff until we have the + // reference store, in which we could optionally customise the display of each reference entry + Result = This; + switch(Mask) { - fprintf(stderr, "Please set the member attribute in the [video] node of your .hmml file\n"); - HaveErrors = TRUE; - } - else if(!GetPersonFromConfig(Wrap0(HMML.metadata.member))) - { - ErrorCredentials(Wrap0(HMML.metadata.member), R_HOST); - HaveErrors = TRUE; - } - - if(!HMML.metadata.id) - { - fprintf(stderr, "Please set the id attribute in the [video] node of your .hmml file\n"); - HaveErrors = TRUE; - } - else - { - CopyString(CollationBuffers->VideoID, sizeof(CollationBuffers->VideoID), "%s", HMML.metadata.id); - } - - string VODPlatform = {}; - if(HMML.metadata.vod_platform) - { - VODPlatform = Wrap0(HMML.metadata.vod_platform); - } - else if(CurrentProject->VODPlatform.Length > 0) - { - VODPlatform = CurrentProject->VODPlatform; - } - - if(VODPlatform.Length == 0) - { - fprintf(stderr, "Please configure or set the vod_platform attribute in the [video] node of your .hmml file\n"); - HaveErrors = TRUE; - } - else - { - CopyString(CollationBuffers->VODPlatform, sizeof(CollationBuffers->VODPlatform), "%s", HMML.metadata.vod_platform); - } - - buffer URLPlayer; - ClaimBuffer(&URLPlayer, BID_URL_PLAYER, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); - ConstructPlayerURL(&URLPlayer, N->Project, HMML.metadata.output ? Wrap0(HMML.metadata.output) : BaseFilename); - CopyString(CollationBuffers->URLPlayer, sizeof(CollationBuffers->URLPlayer), "%s", URLPlayer.Location); - DeclaimBuffer(&URLPlayer); - - medium *DefaultMedium = CurrentProject->DefaultMedium; - if(HMML.metadata.medium && !(DefaultMedium = MediumExists(Wrap0(HMML.metadata.medium)))) - { - HaveErrors = TRUE; - } - if(!DefaultMedium) - { - fprintf(stderr, "No default_medium set in config, or medium set in the .hmml video node\n"); - HaveErrors = TRUE; - } - - string ProjectTitle; - if(CurrentProject->HTMLTitle.Length) - { - ProjectTitle = CurrentProject->HTMLTitle; - } - else - { - ProjectTitle = CurrentProject->Title; - } - - // TODO(matt): Handle the art and art_variants once .hmml supports them - - // TODO(matt): Consider simply making these as buffers and claiming the necessary amount for them - // The nice thing about doing it this way, though, is that it encourages bespoke template use, which should - // usually be the more convenient way for people to write greater amounts of localised information - for(int CustomIndex = 0; CustomIndex < HMML_CUSTOM_ATTR_COUNT; ++CustomIndex) - { - if(HMML.metadata.custom[CustomIndex]) - { - int LengthOfString = StringLength(HMML.metadata.custom[CustomIndex]); - if(LengthOfString > (CustomIndex < 12 ? MAX_CUSTOM_SNIPPET_SHORT_LENGTH : MAX_CUSTOM_SNIPPET_LONG_LENGTH)) + case (REF_TITLE | REF_AUTHOR | REF_PUBLISHER): { - fprintf(stderr, "%sCustom string %d \"%s%s%s\" is too long (%d/%d characters)%s\n", - ColourStrings[CS_ERROR], CustomIndex, ColourStrings[CS_END], HMML.metadata.custom[CustomIndex], ColourStrings[CS_ERROR], - LengthOfString, CustomIndex < 12 ? MAX_CUSTOM_SNIPPET_SHORT_LENGTH : MAX_CUSTOM_SNIPPET_LONG_LENGTH, ColourStrings[CS_END]); - if(LengthOfString < MAX_CUSTOM_SNIPPET_LONG_LENGTH) + //CopyString(This->Source, sizeof(This->Source), "%s (%s)", Ref->author, Ref->publisher); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->author)); + This->Source = ExtendStringInBook(Strings, Wrap0(" (")); + This->Source = ExtendStringInBook(Strings, Wrap0(Ref->publisher)); + This->Source = ExtendStringInBook(Strings, Wrap0(")")); + + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->title)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->title)); + } break; + case (REF_AUTHOR | REF_SITE | REF_PAGE): + { + //CopyStringNoFormat(This->Source, sizeof(This->Source), Wrap0(Ref->site)); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->site)); + //CopyString(This->RefTitle, sizeof(This->RefTitle), "%s: \"%s\"", Ref->author, Ref->page); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->author)); + This->RefTitle = ExtendStringInBook(Strings, Wrap0(": \"")); + This->RefTitle = ExtendStringInBook(Strings, Wrap0(Ref->page)); + This->RefTitle = ExtendStringInBook(Strings, Wrap0("\"")); + } break; + case (REF_PAGE | REF_TITLE): + { + //CopyStringNoFormat(This->Source, sizeof(This->Source), Wrap0(Ref->title)); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->title)); + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->page)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->page)); + } break; + case (REF_SITE | REF_PAGE): + { + //CopyStringNoFormat(This->Source, sizeof(This->Source), Wrap0(Ref->site)); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->site)); + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->page)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->page)); + } break; + case (REF_SITE | REF_TITLE): + { + //CopyStringNoFormat(This->Source, sizeof(This->Source), Wrap0(Ref->site)); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->site)); + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->title)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->title)); + } break; + case (REF_TITLE | REF_AUTHOR): + { + //CopyStringNoFormat(This->Source, sizeof(This->Source), Wrap0(Ref->author)); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->author)); + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->title)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->title)); + } break; + case (REF_ARTICLE | REF_AUTHOR): + { + //CopyStringNoFormat(This->Source, sizeof(This->Source), Wrap0(Ref->author)); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->author)); + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->article)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->article)); + } break; + case (REF_TITLE | REF_PUBLISHER): + { + //CopyStringNoFormat(This->Source, sizeof(This->Source), Wrap0(Ref->publisher)); + This->Source = WriteStringInBook(Strings, Wrap0(Ref->publisher)); + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->title)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->title)); + } break; + case REF_TITLE: + { + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->title)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->title)); + } break; + case REF_SITE: + { + //CopyStringNoFormat(This->RefTitle, sizeof(This->RefTitle), Wrap0(Ref->site)); + This->RefTitle = WriteStringInBook(Strings, Wrap0(Ref->site)); + } break; + default: Result = 0; *ReturnCode = RC_UNHANDLED_REF_COMBO; break; + } + } + else + { + *ReturnCode = RC_INVALID_REFERENCE; + } + return Result; +} + +ref_info * +GetReference(_memory_book(ref_info) *ReferencesArray, HMML_Reference *Reference) +{ + ref_info *Result = 0; + + char *ID = 0; + if(Reference->isbn) { ID = Reference->isbn; } + else if(Reference->url) { ID = Reference->url; } + + if(ID) + { + for(int i = 0; i < ReferencesArray->ItemCount; ++i) + { + ref_info *This = GetPlaceInBook(ReferencesArray, i); + if(!StringsDiffer0(ID, This->ID)) + { + Result = This; + break; + } + } + } + + return Result; +} + +ref_info * +GetOrBuildReference(memory_book *Strings, memory_book *ReferencesArray, HMML_Reference *Reference, rc *ReturnCode) +{ + ref_info *Result = GetReference(ReferencesArray, Reference); + if(!Result) + { + Result = BuildReference(Strings, ReferencesArray, Reference, ReturnCode); + } + return Result; +} + +void +FreeReferences(_memory_book(ref_info) *References) +{ + for(int i = 0; i < References->ItemCount; ++i) + { + ref_info *This = GetPlaceInBook(References, i); + FreeBook(&This->Identifier); + } + FreeBook(References); +} + +rc +ProcessTimecode(buffers *CollationBuffers, neighbourhood *N, string Filepath, memory_book *Strings, + menu_buffers *MenuBuffers, index_buffers *IndexBuffers, player_buffers *PlayerBuffers, + medium *DefaultMedium, speakers *Speakers, string Author, + _memory_book(ref_info) *ReferencesArray, + bool *HasQuoteMenu, bool *HasReferenceMenu, bool *HasFilterMenu, bool *RequiresCineraJS, + int *QuoteIdentifier, int *RefIdentifier, + _memory_book(category_info) *Topics, _memory_book(category_info) *Media, + HMML_Annotation *Anno, char **PreviousTimestamp) +{ + MEM_TEST_TOP("ProcessTimecode"); + // TODO(matt): Introduce and use a SystemError() in here + rc Result = RC_SUCCESS; + + if(!*PreviousTimestamp || TimecodeToSeconds(Anno->time) >= TimecodeToSeconds(*PreviousTimestamp)) + { + *PreviousTimestamp = Anno->time; + + memory_book LocalTopics = InitBook(MBT_CATEGORY_INFO, 8); + memory_book LocalMedia = InitBook(MBT_CATEGORY_INFO, 8); + + quote_info QuoteInfo = { }; + + bool HasQuote = FALSE; + bool HasReference = FALSE; + + RewindBuffer(&IndexBuffers->Master); + RewindBuffer(&IndexBuffers->Header); + RewindBuffer(&IndexBuffers->Class); + RewindBuffer(&IndexBuffers->Data); + RewindBuffer(&IndexBuffers->Text); + RewindBuffer(&IndexBuffers->CategoryIcons); + + CopyStringToBuffer(&IndexBuffers->Header, + "
time)); + + CopyStringToBuffer(&IndexBuffers->Class, + " class=\"marker"); + + speaker *Speaker = GetSpeaker(&Speakers->Speakers, Anno->author ? Wrap0(Anno->author) : CurrentProject->StreamUsername.Length > 0 ? CurrentProject->StreamUsername : CurrentProject->Owner->ID); + if(!IsCategorisedAFK(Anno)) + { + if(Speakers->Speakers.ItemCount > 1 && Speaker && !IsCategorisedAuthored(Anno)) + { + string DisplayName = !Speaker->Seen ? Speaker->Person->Name : Speaker->Abbreviation; + + CopyStringToBuffer(&IndexBuffers->Text, + "%.*s: ", + Speaker->Colour.Hue, + Speaker->Colour.Saturation, + + (int)DisplayName.Length, DisplayName.Base); + + Speaker->Seen = TRUE; + } + else if(Anno->author) + { + if(!*HasFilterMenu) + { + *HasFilterMenu = TRUE; + } + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("authored")); + hsl_colour AuthorColour; + StringToColourHash(&AuthorColour, Wrap0(Anno->author)); + // TODO(matt): That EDITION_NETWORK site database API-polling stuff + CopyStringToBuffer(&IndexBuffers->Text, + "%s ", + AuthorColour.Hue, AuthorColour.Saturation, + Anno->author); + } + } + + char *InPtr = Anno->text; + + int MarkerIndex = 0, RefIndex = 0; + while(*InPtr || RefIndex < Anno->reference_count) + { + if(MarkerIndex < Anno->marker_count && + InPtr - Anno->text == Anno->markers[MarkerIndex].offset) + { + char *Readable = Anno->markers[MarkerIndex].parameter + ? Anno->markers[MarkerIndex].parameter + : Anno->markers[MarkerIndex].marker; + HMML_MarkerType Type = Anno->markers[MarkerIndex].type; + if(Type == HMML_CATEGORY) + { + Result = GenerateTopicColours(N, Wrap0(Anno->markers[MarkerIndex].marker)); + if(Result == RC_SUCCESS) { - fprintf(stderr, "Consider using custom12 to custom15, which can hold %d characters\n", MAX_CUSTOM_SNIPPET_LONG_LENGTH); + if(!*HasFilterMenu) + { + *HasFilterMenu = TRUE; + } + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Anno->markers[MarkerIndex].marker)); + CopyStringToBuffer(&IndexBuffers->Text, "%.*s", (int)StringLength(Readable), InPtr); } else { - fprintf(stderr, "Consider using a bespoke template for longer amounts of localised information\n"); - } - HaveErrors = TRUE; - } - else - { - switch(CustomIndex) - { - case 0: CopyStringNoFormat(CollationBuffers->Custom0, sizeof(CollationBuffers->Custom0), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 1: CopyStringNoFormat(CollationBuffers->Custom1, sizeof(CollationBuffers->Custom1), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 2: CopyStringNoFormat(CollationBuffers->Custom2, sizeof(CollationBuffers->Custom2), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 3: CopyStringNoFormat(CollationBuffers->Custom3, sizeof(CollationBuffers->Custom3), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 4: CopyStringNoFormat(CollationBuffers->Custom4, sizeof(CollationBuffers->Custom4), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 5: CopyStringNoFormat(CollationBuffers->Custom5, sizeof(CollationBuffers->Custom5), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 6: CopyStringNoFormat(CollationBuffers->Custom6, sizeof(CollationBuffers->Custom6), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 7: CopyStringNoFormat(CollationBuffers->Custom7, sizeof(CollationBuffers->Custom7), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 8: CopyStringNoFormat(CollationBuffers->Custom8, sizeof(CollationBuffers->Custom8), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 9: CopyStringNoFormat(CollationBuffers->Custom9, sizeof(CollationBuffers->Custom9), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 10: CopyStringNoFormat(CollationBuffers->Custom10, sizeof(CollationBuffers->Custom10), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 11: CopyStringNoFormat(CollationBuffers->Custom11, sizeof(CollationBuffers->Custom11), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 12: CopyStringNoFormat(CollationBuffers->Custom12, sizeof(CollationBuffers->Custom12), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 13: CopyStringNoFormat(CollationBuffers->Custom13, sizeof(CollationBuffers->Custom13), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 14: CopyStringNoFormat(CollationBuffers->Custom14, sizeof(CollationBuffers->Custom14), Wrap0(HMML.metadata.custom[CustomIndex])); break; - case 15: CopyStringNoFormat(CollationBuffers->Custom15, sizeof(CollationBuffers->Custom15), Wrap0(HMML.metadata.custom[CustomIndex])); break; - } - } - } - } - - if(!HaveErrors) - { - if(!CurrentProject->DenyBespokeTemplates && HMML.metadata.template) - { - switch(PackTemplate(BespokeTemplate, Wrap0(HMML.metadata.template), TEMPLATE_BESPOKE, CurrentProject)) - { - 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(HaveErrors) - { - fprintf(stderr, "%sSkipping%s %.*s", ColourStrings[CS_ERROR], ColourStrings[CS_END], (int)BaseFilename.Length, BaseFilename.Base); - if(HMML.metadata.title) { fprintf(stderr, " - %s", HMML.metadata.title); } - fprintf(stderr, "\n"); - hmml_free(&HMML); - return RC_ERROR_HMML; - } - - string OutputLocation = {}; - if(HMML.metadata.output) - { - OutputLocation = Wrap0(HMML.metadata.output); - } - else - { - OutputLocation = BaseFilename; - } - - ClearCopyStringNoFormat(N->WorkingThis.OutputLocation, sizeof(N->WorkingThis.OutputLocation), OutputLocation); - - if(N->This) - { - string OldOutputLocation = Wrap0i(N->This->OutputLocation, sizeof(N->This->OutputLocation)); - string NewOutputLocation = Wrap0i(N->WorkingThis.OutputLocation, sizeof(N->WorkingThis.OutputLocation)); - if(StringsDiffer(OldOutputLocation, NewOutputLocation)) - { - DeletePlayerPageFromFilesystem(CurrentProject->BaseDir, CurrentProject->PlayerLocation, OldOutputLocation, FALSE, TRUE); - } - } - - // TODO(matt): Handle art and art_variants, once .hmml supports them -#if DEBUG - printf( - "================================================================================\n" - "%s\n" - "================================================================================\n", - Filename); -#endif - // NOTE(matt): Tree structure of "global" buffer dependencies - // Master - // IncludesPlayer - // Menus - // MenuBuffers->Quote - // MenuBuffers->Reference - // MenuBuffers->Filter - // MenuBuffers->FilterTopics - // MenuBuffers->FilterMedia - // MenuBuffers->Credits - // Player - // Script - - - menu_buffers MenuBuffers = {}; - index_buffers IndexBuffers = {}; - player_buffers PlayerBuffers = {}; - - if(ClaimBuffer(&MenuBuffers.Quote, BID_MENU_BUFFERS_QUOTE, Kilobytes(32)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&MenuBuffers.Reference, BID_MENU_BUFFERS_REFERENCE, Kilobytes(32)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&MenuBuffers.Filter, BID_MENU_BUFFERS_FILTER, Kilobytes(16)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&MenuBuffers.FilterTopics, BID_MENU_BUFFERS_FILTER_TOPICS, Kilobytes(8)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&MenuBuffers.FilterMedia, BID_MENU_BUFFERS_FILTER_MEDIA, Kilobytes(8)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&MenuBuffers.Credits, BID_MENU_BUFFERS_CREDITS, Kilobytes(8)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - - // NOTE(matt): Tree structure of IndexBuffers dependencies - // Master - // Header - // Class - // Data - // Text - // CategoryIcons - - if(ClaimBuffer(&IndexBuffers.Master, BID_INDEX_BUFFERS_MASTER, Kilobytes(8)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&IndexBuffers.Header, BID_INDEX_BUFFERS_HEADER, Kilobytes(1)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&IndexBuffers.Class, BID_INDEX_BUFFERS_CLASS, 256) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&IndexBuffers.Data, BID_INDEX_BUFFERS_DATA, Kilobytes(1)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&IndexBuffers.Text, BID_INDEX_BUFFERS_TEXT, Kilobytes(4)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&IndexBuffers.CategoryIcons, BID_INDEX_BUFFERS_CATEGORY_ICONS, Kilobytes(1)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - - if(ClaimBuffer(&PlayerBuffers.Menus, BID_PLAYER_BUFFERS_MENUS, Kilobytes(32)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&PlayerBuffers.Main, BID_PLAYER_BUFFERS_MAIN, Kilobytes(512)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - if(ClaimBuffer(&PlayerBuffers.Script, BID_PLAYER_BUFFERS_SCRIPT, Kilobytes(8)) == RC_ARENA_FULL) { HMMLCleanup(); return RC_ARENA_FULL; }; - - ref_info ReferencesArray[200] = { }; - categories Topics = { }; - categories Media = { }; - - bool HasQuoteMenu = FALSE; - bool HasReferenceMenu = FALSE; - bool HasFilterMenu = FALSE; - - int QuoteIdentifier = 0x3b1; - int RefIdentifier = 1; - int UniqueRefs = 0; - - CopyStringToBuffer(&PlayerBuffers.Menus, - "
\n" - " ", (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base); - CopyStringToBufferHTMLSafe(&PlayerBuffers.Menus, Wrap0(HMML.metadata.title)); - CopyStringToBuffer(&PlayerBuffers.Menus, "\n"); - - CopyStringToBuffer(&PlayerBuffers.Main, - "
\n" - "
\n" - "
\n", HMML.metadata.id, (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base); - - if(N) - { - N->WorkingThis.LinkOffsets.PrevStart = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location); - if((N->Prev && N->Prev->Size) || (N->Next && N->Next->Size)) - { - if(N->Prev && N->Prev->Size) - { - // TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here - buffer PreviousPlayerURL; - ClaimBuffer(&PreviousPlayerURL, BID_PREVIOUS_PLAYER_URL, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); - ConstructPlayerURL(&PreviousPlayerURL, N->Project, Wrap0i(N->Prev->OutputLocation, sizeof(N->Prev->OutputLocation))); - CopyStringToBuffer(&PlayerBuffers.Main, - "
Previous: '%s'
\n", - PreviousPlayerURL.Location, - N->Prev->Title); - - DeclaimBuffer(&PreviousPlayerURL); - } - else - { - CopyStringToBuffer(&PlayerBuffers.Main, - "
Welcome to %.*s
\n", (int)ProjectTitle.Length, ProjectTitle.Base); - } - } - N->WorkingThis.LinkOffsets.PrevEnd = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location - N->WorkingThis.LinkOffsets.PrevStart); - } - - CopyStringToBuffer(&PlayerBuffers.Main, - "
\n"); - - speakers Speakers = { }; - bool RequiresCineraJS = FALSE; - switch(BuildCredits(&MenuBuffers.Credits, &HMML.metadata, &Speakers, &RequiresCineraJS)) - { - case CreditsError_NoHost: - case CreditsError_NoIndexer: - case CreditsError_NoCredentials: - fprintf(stderr, "%sSkipping%s %.*s - %s\n", ColourStrings[CS_ERROR], ColourStrings[CS_END], (int)BaseFilename.Length, BaseFilename.Base, HMML.metadata.title); - HMMLCleanup(); - return RC_ERROR_HMML; - } - - bool PrivateVideo = FALSE; - if(N->WorkingThis.Size == 0 && !CurrentProject->IgnorePrivacy) - { - if(VideoIsPrivate(HMML.metadata.id)) - { - // TODO(matt): Actually generate these guys, just putting them in a secret location - N->WorkingThis.LinkOffsets.PrevStart = 0; - N->WorkingThis.LinkOffsets.PrevEnd = 0; - PrivateVideo = TRUE; - HMMLCleanup(); - return RC_PRIVATE_VIDEO; - } - } - - if(!PrivateVideo) - { - CopyStringToBuffer(&CollationBuffers->SearchEntry, "name: \""); - CopyStringToBuffer(&CollationBuffers->SearchEntry, "%.*s", (int)OutputLocation.Length, OutputLocation.Base); - - CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"\n" - "title: \""); - CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, Wrap0(HMML.metadata.title)); - CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"\n" - "markers:\n"); - } - -#if DEBUG - printf("\n\n --- Entering Timestamps Loop ---\n\n\n\n"); -#endif - - int PreviousTimecode = 0; - for(int TimestampIndex = 0; TimestampIndex < HMML.annotation_count; ++TimestampIndex) - { -#if DEBUG - printf("%d\n", TimestampIndex); -#endif - - HMML_Annotation *Anno = HMML.annotations + TimestampIndex; - - if(TimecodeToSeconds(Anno->time) < PreviousTimecode) - { - fprintf(stderr, "%s:%d: Timecode %s is chronologically before previous timecode\n" - "%sSkipping%s %.*s - %s\n", - Filename, Anno->line, Anno->time, - ColourStrings[CS_ERROR], ColourStrings[CS_END], (int)BaseFilename.Length, BaseFilename.Base, HMML.metadata.title); - HMMLCleanup(); - return RC_ERROR_HMML; - } - PreviousTimecode = TimecodeToSeconds(Anno->time); - - categories LocalTopics = { }; - categories LocalMedia = { }; - bool HasQuote = FALSE; - bool HasReference = FALSE; - - quote_info QuoteInfo = { }; - - RewindBuffer(&IndexBuffers.Master); - RewindBuffer(&IndexBuffers.Header); - RewindBuffer(&IndexBuffers.Class); - RewindBuffer(&IndexBuffers.Data); - RewindBuffer(&IndexBuffers.Text); - RewindBuffer(&IndexBuffers.CategoryIcons); - - CopyStringToBuffer(&IndexBuffers.Header, - "
time)); - - CopyStringToBuffer(&IndexBuffers.Class, - " class=\"marker"); - - speaker *Speaker = GetSpeaker(Speakers, Anno->author ? Wrap0(Anno->author) : CurrentProject->StreamUsername.Length > 0 ? CurrentProject->StreamUsername : CurrentProject->Owner->ID); - if(!IsCategorisedAFK(Anno)) - { - if(Speakers.Count > 1 && Speaker && !IsCategorisedAuthored(Anno)) - { - string DisplayName = !Speaker->Seen ? Speaker->Person->Name : Wrap0(Speaker->Abbreviation); - - CopyStringToBuffer(&IndexBuffers.Text, - "%.*s: ", - Speaker->Colour.Hue, - Speaker->Colour.Saturation, - - (int)DisplayName.Length, DisplayName.Base); - - Speaker->Seen = TRUE; - } - else if(Anno->author) - { - if(!HasFilterMenu) - { - HasFilterMenu = TRUE; } - InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, Wrap0("authored")); - hsl_colour AuthorColour; - StringToColourHash(&AuthorColour, Wrap0(Anno->author)); + } + else // NOTE(matt): HMML_MEMBER / HMML_PROJECT + { // TODO(matt): That EDITION_NETWORK site database API-polling stuff - CopyStringToBuffer(&IndexBuffers.Text, - "%s ", - AuthorColour.Hue, AuthorColour.Saturation, - Anno->author); + hsl_colour Colour; + StringToColourHash(&Colour, Wrap0(Anno->markers[MarkerIndex].marker)); + CopyStringToBuffer(&IndexBuffers->Text, + "%.*s", + Anno->markers[MarkerIndex].type == HMML_MEMBER ? "member" : "project", + Colour.Hue, Colour.Saturation, + (int)StringLength(Readable), InPtr); } + + InPtr += StringLength(Readable); + ++MarkerIndex; } - char *InPtr = Anno->text; - - int MarkerIndex = 0, RefIndex = 0; - while(*InPtr || RefIndex < Anno->reference_count) + if(Result == RC_SUCCESS) { - if(MarkerIndex < Anno->marker_count && - InPtr - Anno->text == Anno->markers[MarkerIndex].offset) - { - char *Readable = Anno->markers[MarkerIndex].parameter - ? Anno->markers[MarkerIndex].parameter - : Anno->markers[MarkerIndex].marker; - switch(Anno->markers[MarkerIndex].type) - { - case HMML_MEMBER: - case HMML_PROJECT: - { - // TODO(matt): That EDITION_NETWORK site database API-polling stuff - hsl_colour Colour; - StringToColourHash(&Colour, Wrap0(Anno->markers[MarkerIndex].marker)); - CopyStringToBuffer(&IndexBuffers.Text, - "%.*s", - Anno->markers[MarkerIndex].type == HMML_MEMBER ? "member" : "project", - Colour.Hue, Colour.Saturation, - (int)StringLength(Readable), InPtr); - - } break; - case HMML_CATEGORY: - { - switch(GenerateTopicColours(N, Wrap0(Anno->markers[MarkerIndex].marker))) - { - case RC_SUCCESS: - case RC_NOOP: - break; - case RC_ERROR_FILE: - case RC_ERROR_MEMORY: - HMMLCleanup(); - return RC_ERROR_FATAL; - default: break; - }; - if(!HasFilterMenu) - { - HasFilterMenu = TRUE; - } - InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, Wrap0(Anno->markers[MarkerIndex].marker)); - CopyStringToBuffer(&IndexBuffers.Text, "%.*s", (int)StringLength(Readable), InPtr); - } break; - case HMML_MARKER_COUNT: break; - } - InPtr += StringLength(Readable); - ++MarkerIndex; - } - while(RefIndex < Anno->reference_count && InPtr - Anno->text == Anno->references[RefIndex].offset) { HMML_Reference *CurrentRef = Anno->references + RefIndex; - if(!HasReferenceMenu) + if(!*HasReferenceMenu) { - CopyStringToBuffer(&MenuBuffers.Reference, + CopyStringToBuffer(&MenuBuffers->Reference, "
\n" " References ▼\n" "
\n"); - if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, CurrentRef, Anno) == RC_INVALID_REFERENCE) - { - LogError(LOG_ERROR, "Reference combination processing failed: %s:%d", Filename, Anno->line); - fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n" - "\n" - "Either tweak your timestamp, or contact miblodelcarpio@gmail.com\n" - "mentioning the ref node you want to write and how you want it to\n" - "appear in the references menu\n", Filename, Anno->line); - HMMLCleanup(); - return RC_INVALID_REFERENCE; - } - ++ReferencesArray[RefIdentifier - 1].IdentifierCount; - ++UniqueRefs; + *HasReferenceMenu = TRUE; + } - HasReferenceMenu = TRUE; + ref_info *This = GetOrBuildReference(Strings, ReferencesArray, CurrentRef, &Result); + if(This) + { + identifier *New = MakeSpaceInBook(&This->Identifier); + New->Timecode = Anno->time; + New->Identifier = *RefIdentifier; } else { - for(int i = 0; i < UniqueRefs; ++i) + switch(Result) { - if(ReferencesArray[i].IdentifierCount == MAX_REF_IDENTIFIER_COUNT) - { - LogError(LOG_EMERGENCY, "REF_MAX_IDENTIFIER (%d) reached. Contact miblodelcarpio@gmail.com", MAX_REF_IDENTIFIER_COUNT); - fprintf(stderr, "%s:%d: Too many timecodes associated with one reference (increase REF_MAX_IDENTIFIER)\n", Filename, Anno->line); - HMMLCleanup(); - return RC_ERROR_MAX_REFS; - } - if(CurrentRef->isbn) - { - if(!StringsDiffer0(CurrentRef->isbn, ReferencesArray[i].ID)) + case RC_INVALID_REFERENCE: { - CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, sizeof(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode), "%s", Anno->time); - ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier; - ++ReferencesArray[i].IdentifierCount; - goto AppendedIdentifier; - } - } - else if(CurrentRef->url) - { - if(!StringsDiffer0(CurrentRef->url, ReferencesArray[i].ID)) + IndexingError(&Filepath, Anno->line, S_ERROR, + "Invalid [ref]. Please set either a \"url\" or \"isbn\"", + 0); + } break; + case RC_UNHANDLED_REF_COMBO: { - CopyString(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode, sizeof(ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Timecode), "%s", Anno->time); - ReferencesArray[i].Identifier[ReferencesArray[i].IdentifierCount].Identifier = RefIdentifier; - ++ReferencesArray[i].IdentifierCount; - goto AppendedIdentifier; - } - } - else - { - LogError(LOG_ERROR, "Reference missing ISBN or URL: %s:%d", Filename, Anno->line); - fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Filename, Anno->line); - HMMLCleanup(); - return RC_INVALID_REFERENCE; - } - } - - if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, CurrentRef, Anno) == RC_INVALID_REFERENCE) - { - LogError(LOG_ERROR, "Reference combination processing failed: %s:%d", Filename, Anno->line); - fprintf(stderr, "%s:%d: Cannot process new combination of reference info\n" - "\n" - "Either tweak your timestamp, or contact miblodelcarpio@gmail.com\n" - "mentioning the ref node you want to write and how you want it to\n" - "appear in the references menu\n", Filename, Anno->line); - HMMLCleanup(); - return RC_INVALID_REFERENCE; - } - ++ReferencesArray[UniqueRefs].IdentifierCount; - ++UniqueRefs; - } -AppendedIdentifier: - if(!HasReference) - { - if(CurrentRef->isbn) - { - CopyStringToBuffer(&IndexBuffers.Data, " data-ref=\"%s", CurrentRef->isbn); - } - else if(CurrentRef->url) - { - CopyStringToBuffer(&IndexBuffers.Data, " data-ref=\"%s", CurrentRef->url); - } - else - { - LogError(LOG_ERROR, "Reference missing ISBN or URL: %s:%d", Filename, Anno->line); - fprintf(stderr, "%s:%d: Reference must have an ISBN or URL\n", Filename, Anno->line); - HMMLCleanup(); - return RC_INVALID_REFERENCE; - } - - HasReference = TRUE; - } - else - { - if(CurrentRef->isbn) - { - CopyStringToBuffer(&IndexBuffers.Data, ",%s", CurrentRef->isbn); - } - else if(CurrentRef->url) - { - CopyStringToBuffer(&IndexBuffers.Data, ",%s", CurrentRef->url); - } - else - { - LogError(LOG_ERROR, "Reference missing ISBN or URL: %s:%d", Filename, Anno->line); - fprintf(stderr, "%s:%d: Reference must have an ISBN or URL", Filename, Anno->line); - HMMLCleanup(); - return RC_INVALID_REFERENCE; + IndexingError(&Filepath, Anno->line, S_ERROR, + "Cannot process new combination of reference info\n" + "\n" + "Either tweak your timestamp, or contact miblodelcarpio@gmail.com\n" + "mentioning the ref node you want to write and how you want it to\n" + "appear in the references menu", + 0); + } break; + default: break; } + break; // NOTE(matt): Out of the while() } - CopyStringToBuffer(&IndexBuffers.Text, "%s%d", + CopyStringToBuffer(&IndexBuffers->Data, "%s%s", !HasReference ? " data-ref=\"" : "," , This->ID); + HasReference = TRUE; + + CopyStringToBuffer(&IndexBuffers->Text, "%s%d", Anno->references[RefIndex].offset == Anno->references[RefIndex-1].offset ? "," : "", - RefIdentifier); + *RefIdentifier); ++RefIndex; - ++RefIdentifier; + ++*RefIdentifier; + } + + if(Result != RC_SUCCESS) + { + break; } if(*InPtr) @@ -9439,122 +9348,115 @@ AppendedIdentifier: switch(*InPtr) { case '<': - CopyStringToBuffer(&IndexBuffers.Text, "<"); + CopyStringToBuffer(&IndexBuffers->Text, "<"); break; case '>': - CopyStringToBuffer(&IndexBuffers.Text, ">"); + CopyStringToBuffer(&IndexBuffers->Text, ">"); break; case '&': - CopyStringToBuffer(&IndexBuffers.Text, "&"); + CopyStringToBuffer(&IndexBuffers->Text, "&"); break; case '\"': - CopyStringToBuffer(&IndexBuffers.Text, """); + CopyStringToBuffer(&IndexBuffers->Text, """); break; case '\'': - CopyStringToBuffer(&IndexBuffers.Text, "'"); + CopyStringToBuffer(&IndexBuffers->Text, "'"); break; default: - *IndexBuffers.Text.Ptr++ = *InPtr; - *IndexBuffers.Text.Ptr = '\0'; + *IndexBuffers->Text.Ptr++ = *InPtr; + *IndexBuffers->Text.Ptr = '\0'; break; } ++InPtr; } } + } + if(Result == RC_SUCCESS) + { if(Anno->is_quote) { - if(!HasQuoteMenu) + if(!*HasQuoteMenu) { - CopyStringToBuffer(&MenuBuffers.Quote, + CopyStringToBuffer(&MenuBuffers->Quote, "
\n" " Quotes ▼\n" "
\n"); - HasQuoteMenu = TRUE; + *HasQuoteMenu = TRUE; } if(!HasReference) { - CopyStringToBuffer(&IndexBuffers.Data, " data-ref=\"&#%d;", QuoteIdentifier); + CopyStringToBuffer(&IndexBuffers->Data, " data-ref=\"&#%d;", *QuoteIdentifier); } else { - CopyStringToBuffer(&IndexBuffers.Data, ",&#%d;", QuoteIdentifier); + CopyStringToBuffer(&IndexBuffers->Data, ",&#%d;", *QuoteIdentifier); } HasQuote = TRUE; - string Speaker = {}; - if(Anno->quote.author) { Speaker = Wrap0(Anno->quote.author); } - else if(HMML.metadata.stream_username) { Speaker = Wrap0(HMML.metadata.stream_username); } - else if(CurrentProject->StreamUsername.Length > 0) { Speaker = CurrentProject->StreamUsername; } - else { Speaker = CurrentProject->Owner->ID; } - bool ShouldFetchQuotes = FALSE; if(Config->CacheDir.Length == 0 || time(0) - LastQuoteFetch > 60*60) { ShouldFetchQuotes = TRUE; - LastQuoteFetch = time(0); } - if(BuildQuote(&QuoteInfo, - Speaker, - Anno->quote.id, ShouldFetchQuotes) == RC_UNFOUND) + /* */ MEM_TEST_MID("ProcessTimecode()"); + /* +MEM */ Result = BuildQuote(Strings, &QuoteInfo, Author, Anno->quote.id, ShouldFetchQuotes); + /* */ MEM_TEST_MID("ProcessTimecode()"); + if(Result == RC_SUCCESS) { - LogError(LOG_ERROR, "Quote #%.*s %d not found: %s:%d", (int)Speaker.Length, Speaker.Base, Anno->quote.id, Filename, Anno->line); + CopyStringToBuffer(&MenuBuffers->Quote, + " \n" + " \n" + " \n" + "
Quote %d
\n" + "
", + (int)Author.Length, Author.Base, + Anno->quote.id, + *QuoteIdentifier, + Anno->quote.id); - fprintf(stderr, "Quote #%.*s %d not found\n" - "%sSkipping%s %.*s - %s\n", - (int)Speaker.Length, Speaker.Base, Anno->quote.id, - ColourStrings[CS_ERROR], ColourStrings[CS_END], (int)BaseFilename.Length, BaseFilename.Base, HMML.metadata.title); - HMMLCleanup(); - return RC_ERROR_QUOTE; + CopyStringToBufferHTMLSafe(&MenuBuffers->Quote, QuoteInfo.Text); + + string DateString = UnixTimeToDateString(Strings, QuoteInfo.Date); + CopyStringToBuffer(&MenuBuffers->Quote, "
\n" + " \n" + "
\n" + "
\n" + " [&#%d;]%s\n" + "
\n" + "
\n" + "
\n", + (int)Author.Length, Author.Base, + (int)DateString.Length, DateString.Base, // TODO(matt): Convert Unixtime to date-string + TimecodeToSeconds(Anno->time), + *QuoteIdentifier, + Anno->time); + if(!Anno->text[0]) + { + CopyStringToBuffer(&IndexBuffers->Text, "“"); + CopyStringToBufferHTMLSafe(&IndexBuffers->Text, QuoteInfo.Text); + CopyStringToBuffer(&IndexBuffers->Text, "”"); + } + CopyStringToBuffer(&IndexBuffers->Text, "&#%d;", *QuoteIdentifier); + ++*QuoteIdentifier; } - - CopyStringToBuffer(&MenuBuffers.Quote, - " \n" - " \n" - " \n" - "
Quote %d
\n" - "
", - (int)Speaker.Length, Speaker.Base, - Anno->quote.id, - QuoteIdentifier, - Anno->quote.id); - - CopyStringToBufferHTMLSafe(&MenuBuffers.Quote, Wrap0i(QuoteInfo.Text, sizeof(QuoteInfo.Text))); - - CopyStringToBuffer(&MenuBuffers.Quote, "
\n" - " \n" - "
\n" - "
\n" - " [&#%d;]%s\n" - "
\n" - "
\n" - "
\n", - (int)Speaker.Length, Speaker.Base, - QuoteInfo.Date, - TimecodeToSeconds(Anno->time), - QuoteIdentifier, - Anno->time); - if(!Anno->text[0]) + else if(Result == RC_UNFOUND) { - CopyStringToBuffer(&IndexBuffers.Text, "“"); - CopyStringToBufferHTMLSafe(&IndexBuffers.Text, Wrap0i(QuoteInfo.Text, sizeof(QuoteInfo.Text))); - CopyStringToBuffer(&IndexBuffers.Text, "”"); + IndexingQuoteError(&Filepath, Anno->line, Author, Anno->quote.id); } - CopyStringToBuffer(&IndexBuffers.Text, "&#%d;", QuoteIdentifier); - ++QuoteIdentifier; } - if(!PrivateVideo) + if(Result == RC_SUCCESS) { CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"%d\": \"", TimecodeToSeconds(Anno->time)); if(Anno->is_quote && !Anno->text[0]) { CopyStringToBuffer(&CollationBuffers->SearchEntry, "\u201C"); - CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, Wrap0i(QuoteInfo.Text, sizeof(QuoteInfo.Text))); + CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, QuoteInfo.Text); CopyStringToBuffer(&CollationBuffers->SearchEntry, "\u201D"); } else @@ -9562,631 +9464,1040 @@ AppendedIdentifier: CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, Wrap0(Anno->text)); } CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"\n"); - } - while(MarkerIndex < Anno->marker_count) - { - switch(GenerateTopicColours(N, Wrap0(Anno->markers[MarkerIndex].marker))) + while(MarkerIndex < Anno->marker_count) { - case RC_SUCCESS: - case RC_NOOP: + Result = GenerateTopicColours(N, Wrap0(Anno->markers[MarkerIndex].marker)); + if(Result == RC_SUCCESS) + { + if(!*HasFilterMenu) + { + *HasFilterMenu = TRUE; + } + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0(Anno->markers[MarkerIndex].marker)); + ++MarkerIndex; + } + else + { break; - case RC_ERROR_FILE: - case RC_ERROR_MEMORY: - HMMLCleanup(); - return RC_ERROR_FATAL; - default: break; + } } - if(!HasFilterMenu) + + if(Result == RC_SUCCESS) { - HasFilterMenu = TRUE; + if(LocalTopics.ItemCount == 0) + { + Result = GenerateTopicColours(N, Wrap0("nullTopic")); + if(Result == RC_SUCCESS) + { + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, Wrap0("nullTopic")); + } + } + + if(Result == RC_SUCCESS) + { + if(LocalMedia.ItemCount == 0) + { + InsertCategory(Topics, &LocalTopics, Media, &LocalMedia, DefaultMedium->ID); + } + + BuildTimestampClass(&IndexBuffers->Class, &LocalTopics, &LocalMedia, DefaultMedium->ID); + CopyLandmarkedBuffer(&IndexBuffers->Header, &IndexBuffers->Class, 0, PAGE_PLAYER); + + if(HasQuote || HasReference) + { + CopyStringToBuffer(&IndexBuffers->Data, "\""); + CopyLandmarkedBuffer(&IndexBuffers->Header, &IndexBuffers->Data, 0, PAGE_PLAYER); + } + CopyStringToBuffer(&IndexBuffers->Header, ">\n"); + + CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Header, 0, PAGE_PLAYER); + CopyStringToBuffer(&IndexBuffers->Master, + "
%s", + Anno->time); + + CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); + + if(LocalTopics.ItemCount > 0) + { + BuildCategoryIcons(&IndexBuffers->Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, RequiresCineraJS); + } + + CopyStringToBuffer(&IndexBuffers->Master, "
\n" + "
\n" + "
%s", + Anno->time); + + CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); + + if(LocalTopics.ItemCount > 0) + { + BuildCategoryIcons(&IndexBuffers->Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, RequiresCineraJS); + } + + CopyStringToBuffer(&IndexBuffers->Master, "
\n" + "
\n" + "
\n" + "
%s", + Anno->time); + + CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->Text, 0, PAGE_PLAYER); + + if(LocalTopics.ItemCount > 0) + { + //CopyLandmarkedBuffer(&IndexBuffers->Master, &IndexBuffers->CategoryIcons, PAGE_PLAYER); + BuildCategoryIcons(&IndexBuffers->Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, RequiresCineraJS); + } + + CopyStringToBuffer(&IndexBuffers->Master, "
\n" + "
\n" + "
\n"); + + CopyLandmarkedBuffer(&PlayerBuffers->Main, &IndexBuffers->Master, 0, PAGE_PLAYER); + } } - InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, Wrap0(Anno->markers[MarkerIndex].marker)); - ++MarkerIndex; } - - if(LocalTopics.Count == 0) - { - switch(GenerateTopicColours(N, Wrap0("nullTopic"))) - { - case RC_SUCCESS: - case RC_NOOP: - break; - case RC_ERROR_FILE: - case RC_ERROR_MEMORY: - HMMLCleanup(); - return RC_ERROR_FATAL; - default: break; - }; - InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, Wrap0("nullTopic")); - } - - if(LocalMedia.Count == 0) - { - InsertCategory(&Topics, &LocalTopics, &Media, &LocalMedia, DefaultMedium->ID); - } - - BuildTimestampClass(&IndexBuffers.Class, &LocalTopics, &LocalMedia, DefaultMedium->ID); - CopyLandmarkedBuffer(&IndexBuffers.Header, &IndexBuffers.Class, 0, PAGE_PLAYER); - - if(HasQuote || HasReference) - { - CopyStringToBuffer(&IndexBuffers.Data, "\""); - CopyLandmarkedBuffer(&IndexBuffers.Header, &IndexBuffers.Data, 0, PAGE_PLAYER); - } - CopyStringToBuffer(&IndexBuffers.Header, ">\n"); - - CopyLandmarkedBuffer(&IndexBuffers.Master, &IndexBuffers.Header, 0, PAGE_PLAYER); - CopyStringToBuffer(&IndexBuffers.Master, - "
%s", - Anno->time); - - CopyLandmarkedBuffer(&IndexBuffers.Master, &IndexBuffers.Text, 0, PAGE_PLAYER); - - if(LocalTopics.Count > 0) - { - BuildCategoryIcons(&IndexBuffers.Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, &RequiresCineraJS); - } - - CopyStringToBuffer(&IndexBuffers.Master, "
\n" - "
\n" - "
%s", - Anno->time); - - CopyLandmarkedBuffer(&IndexBuffers.Master, &IndexBuffers.Text, 0, PAGE_PLAYER); - - if(LocalTopics.Count > 0) - { - BuildCategoryIcons(&IndexBuffers.Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, &RequiresCineraJS); - } - - CopyStringToBuffer(&IndexBuffers.Master, "
\n" - "
\n" - "
\n" - "
%s", - Anno->time); - - CopyLandmarkedBuffer(&IndexBuffers.Master, &IndexBuffers.Text, 0, PAGE_PLAYER); - - if(LocalTopics.Count > 0) - { - //CopyLandmarkedBuffer(&IndexBuffers.Master, &IndexBuffers.CategoryIcons, PAGE_PLAYER); - BuildCategoryIcons(&IndexBuffers.Master, &LocalTopics, &LocalMedia, DefaultMedium->ID, &RequiresCineraJS); - } - - CopyStringToBuffer(&IndexBuffers.Master, "
\n" - "
\n" - "
\n"); - - CopyLandmarkedBuffer(&PlayerBuffers.Main, &IndexBuffers.Master, 0, PAGE_PLAYER); } - DeclaimIndexBuffers(&IndexBuffers); + FreeBook(&LocalTopics); + FreeBook(&LocalMedia); + } + else + { + IndexingChronologyError(&Filepath, Anno->line, Anno->time, *PreviousTimestamp); + Result = RC_ERROR_HMML; + } + MEM_TEST_END("ProcessTimecode"); + return Result; +} - FreeCredentials(&Speakers); +rc +HMMLToBuffers(buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, neighbourhood *N) +{ + MEM_TEST_TOP("HMMLToBuffers"); + rc Result = RC_SUCCESS; - if(!PrivateVideo) + // TODO(matt): A "MakeString0OnStack()" sort of function? + // NOTE(matt): Stack-string + int NullTerminationBytes = 1; + char Filepath[CurrentProject->HMMLDir.Length + StringLength("/") + BaseFilename.Length + ExtensionStrings[EXT_HMML].Length + NullTerminationBytes]; + char *P = Filepath; + P += CopyStringToBarePtr(P, CurrentProject->HMMLDir); + P += CopyStringToBarePtr(P, Wrap0("/")); + P += CopyStringToBarePtr(P, BaseFilename); + P += CopyStringToBarePtr(P, ExtensionStrings[EXT_HMML]); + *P = '\0'; + + string FilepathL = Wrap0(Filepath); + + FILE *InFile = fopen(Filepath, "r"); + if(!InFile) + { + sleep(1); + InFile = fopen(Filepath, "r"); + } + + if(InFile) + { + HMML_Output HMML = hmml_parse_file(InFile); + fclose(InFile); + + if(HMML.well_formed) { - CopyStringToBuffer(&CollationBuffers->SearchEntry, "---\n"); - N->WorkingThis.Size = CollationBuffers->SearchEntry.Ptr - CollationBuffers->SearchEntry.Location; - } + string Title = {}; + // TODO(matt): Somehow generate private entries, just putting them in a secret location? + + // NOTE(matt): WorkingThis.Size > 0 means that this entry was previously processed as non-private. Would we rather + // reregister such an entry as newly private? This test lets us avoid calling VideoIsPrivate() for all + // entries, on every cinera invocation, when !CurrentProject->IgnorePrivacy + if(N->WorkingThis.Size > 0 || CurrentProject->IgnorePrivacy || !VideoIsPrivate(HMML.metadata.id)) + { + RewindCollationBuffers(CollationBuffers); + Clear(CollationBuffers->Custom0, sizeof(CollationBuffers->Custom0)); + Clear(CollationBuffers->Custom1, sizeof(CollationBuffers->Custom1)); + Clear(CollationBuffers->Custom2, sizeof(CollationBuffers->Custom2)); + Clear(CollationBuffers->Custom3, sizeof(CollationBuffers->Custom3)); + Clear(CollationBuffers->Custom4, sizeof(CollationBuffers->Custom4)); + Clear(CollationBuffers->Custom5, sizeof(CollationBuffers->Custom5)); + Clear(CollationBuffers->Custom6, sizeof(CollationBuffers->Custom6)); + Clear(CollationBuffers->Custom7, sizeof(CollationBuffers->Custom7)); + Clear(CollationBuffers->Custom8, sizeof(CollationBuffers->Custom8)); + Clear(CollationBuffers->Custom9, sizeof(CollationBuffers->Custom9)); + Clear(CollationBuffers->Custom10, sizeof(CollationBuffers->Custom10)); + Clear(CollationBuffers->Custom11, sizeof(CollationBuffers->Custom11)); + Clear(CollationBuffers->Custom12, sizeof(CollationBuffers->Custom12)); + Clear(CollationBuffers->Custom13, sizeof(CollationBuffers->Custom13)); + Clear(CollationBuffers->Custom14, sizeof(CollationBuffers->Custom14)); + Clear(CollationBuffers->Custom15, sizeof(CollationBuffers->Custom15)); + Clear(CollationBuffers->Title, sizeof(CollationBuffers->Title)); + Clear(CollationBuffers->URLPlayer, sizeof(CollationBuffers->URLPlayer)); + Clear(CollationBuffers->URLSearch, sizeof(CollationBuffers->URLSearch)); + Clear(CollationBuffers->VODPlatform, sizeof(CollationBuffers->VODPlatform)); + + if(BaseFilename.Length > MAX_BASE_FILENAME_LENGTH) + { + IndexingErrorSizing(&FilepathL, 0, "Base filename", BaseFilename, MAX_BASE_FILENAME_LENGTH); + Result = RC_ERROR_HMML; + } + + if(!HMML.metadata.title) + { + IndexingError(&FilepathL, 0, S_ERROR, "The [video] node lacks a \"title\"", 0); + Result = RC_ERROR_HMML; + } + else if(StringLength(HMML.metadata.title) > MAX_TITLE_LENGTH) + { + IndexingErrorSizing(&FilepathL, 0, "Video title", Wrap0(HMML.metadata.title), MAX_TITLE_LENGTH); + Result = RC_ERROR_HMML; + } + else + { + ClearCopyStringNoFormat(CollationBuffers->Title, sizeof(CollationBuffers->Title), Wrap0(HMML.metadata.title)); + Title = Wrap0(HMML.metadata.title); + } + + if(!HMML.metadata.member) + { + IndexingError(&FilepathL, 0, S_ERROR, "The [video] node lacks a \"member\"", 0); + Result = RC_ERROR_HMML; + } + else if(!GetPersonFromConfig(Wrap0(HMML.metadata.member))) + { + ErrorCredentials(FilepathL, Wrap0(HMML.metadata.member), R_HOST); + Result = RC_ERROR_HMML; + } + + if(!HMML.metadata.id) + { + IndexingError(&FilepathL, 0, S_ERROR, "The [video] node lacks an \"id\"", 0); + Result = RC_ERROR_HMML; + } + else + { + CopyString(CollationBuffers->VideoID, sizeof(CollationBuffers->VideoID), "%s", HMML.metadata.id); + } + + string VODPlatform = {}; + if(HMML.metadata.vod_platform) + { + VODPlatform = Wrap0(HMML.metadata.vod_platform); + } + else if(CurrentProject->VODPlatform.Length > 0) + { + VODPlatform = CurrentProject->VODPlatform; + } + + if(VODPlatform.Length == 0) + { + IndexingError(&FilepathL, 0, S_ERROR, "The [video] node lacks an \"vod_platform\"", 0); + Result = RC_ERROR_HMML; + } + else + { + CopyString(CollationBuffers->VODPlatform, sizeof(CollationBuffers->VODPlatform), "%s", HMML.metadata.vod_platform); + } + + buffer URLPlayer = {}; + ClaimBuffer(&URLPlayer, BID_URL_PLAYER, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); + ConstructPlayerURL(&URLPlayer, N->Project, HMML.metadata.output ? Wrap0(HMML.metadata.output) : BaseFilename); + CopyString(CollationBuffers->URLPlayer, sizeof(CollationBuffers->URLPlayer), "%s", URLPlayer.Location); + DeclaimBuffer(&URLPlayer); + + medium *DefaultMedium = CurrentProject->DefaultMedium; + if(HMML.metadata.medium) + { + DefaultMedium = MediumExists(FilepathL, Wrap0(HMML.metadata.medium)); + if(!DefaultMedium && CurrentProject->DefaultMedium) + { + fprintf(stderr, "Falling back to the default_medium set in the config: "); + PrintStringCN(CS_MAGENTA_BOLD, CurrentProject->DefaultMedium->ID, FALSE, TRUE); + DefaultMedium = CurrentProject->DefaultMedium; + } + } + + if(!DefaultMedium) + { + IndexingError(&FilepathL, 0, S_ERROR, "No default_medium set in config, or available medium set in the [video] node", 0); + Result = RC_ERROR_HMML; + } + + string ProjectTitle; + if(CurrentProject->HTMLTitle.Length) + { + ProjectTitle = CurrentProject->HTMLTitle; + } + else + { + ProjectTitle = CurrentProject->Title; + } + + // TODO(matt): Handle the art and art_variants once .hmml supports them + + // TODO(matt): Consider simply making these as buffers and claiming the necessary amount for them + // The nice thing about doing it this way, though, is that it encourages bespoke template use, which should + // usually be the more convenient way for people to write greater amounts of localised information + for(int CustomIndex = 0; CustomIndex < HMML_CUSTOM_ATTR_COUNT; ++CustomIndex) + { + if(HMML.metadata.custom[CustomIndex]) + { + if(StringLength(HMML.metadata.custom[CustomIndex]) > (CustomIndex < 12 ? MAX_CUSTOM_SNIPPET_SHORT_LENGTH : MAX_CUSTOM_SNIPPET_LONG_LENGTH)) + { + IndexingErrorCustomSizing(&FilepathL, 0, CustomIndex, Wrap0(HMML.metadata.custom[CustomIndex])); + Result = RC_ERROR_HMML; + } + else + { + switch(CustomIndex) + { + case 0: CopyStringNoFormat(CollationBuffers->Custom0, sizeof(CollationBuffers->Custom0), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 1: CopyStringNoFormat(CollationBuffers->Custom1, sizeof(CollationBuffers->Custom1), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 2: CopyStringNoFormat(CollationBuffers->Custom2, sizeof(CollationBuffers->Custom2), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 3: CopyStringNoFormat(CollationBuffers->Custom3, sizeof(CollationBuffers->Custom3), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 4: CopyStringNoFormat(CollationBuffers->Custom4, sizeof(CollationBuffers->Custom4), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 5: CopyStringNoFormat(CollationBuffers->Custom5, sizeof(CollationBuffers->Custom5), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 6: CopyStringNoFormat(CollationBuffers->Custom6, sizeof(CollationBuffers->Custom6), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 7: CopyStringNoFormat(CollationBuffers->Custom7, sizeof(CollationBuffers->Custom7), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 8: CopyStringNoFormat(CollationBuffers->Custom8, sizeof(CollationBuffers->Custom8), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 9: CopyStringNoFormat(CollationBuffers->Custom9, sizeof(CollationBuffers->Custom9), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 10: CopyStringNoFormat(CollationBuffers->Custom10, sizeof(CollationBuffers->Custom10), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 11: CopyStringNoFormat(CollationBuffers->Custom11, sizeof(CollationBuffers->Custom11), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 12: CopyStringNoFormat(CollationBuffers->Custom12, sizeof(CollationBuffers->Custom12), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 13: CopyStringNoFormat(CollationBuffers->Custom13, sizeof(CollationBuffers->Custom13), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 14: CopyStringNoFormat(CollationBuffers->Custom14, sizeof(CollationBuffers->Custom14), Wrap0(HMML.metadata.custom[CustomIndex])); break; + case 15: CopyStringNoFormat(CollationBuffers->Custom15, sizeof(CollationBuffers->Custom15), Wrap0(HMML.metadata.custom[CustomIndex])); break; + } + } + } + } + + if(Result == RC_SUCCESS && !CurrentProject->DenyBespokeTemplates && HMML.metadata.template) + { + Result = PackTemplate(BespokeTemplate, Wrap0(HMML.metadata.template), TEMPLATE_BESPOKE, CurrentProject); + } + + if(Result == RC_SUCCESS) + { + string OutputLocation = {}; + if(HMML.metadata.output) + { + OutputLocation = Wrap0(HMML.metadata.output); + } + else + { + OutputLocation = BaseFilename; + } + + ClearCopyStringNoFormat(N->WorkingThis.OutputLocation, sizeof(N->WorkingThis.OutputLocation), OutputLocation); + + if(N->This) + { + string OldOutputLocation = Wrap0i(N->This->OutputLocation, sizeof(N->This->OutputLocation)); + string NewOutputLocation = Wrap0i(N->WorkingThis.OutputLocation, sizeof(N->WorkingThis.OutputLocation)); + if(StringsDiffer(OldOutputLocation, NewOutputLocation)) + { + DeletePlayerPageFromFilesystem(CurrentProject->BaseDir, CurrentProject->PlayerLocation, OldOutputLocation, FALSE, TRUE); + } + } + + // TODO(matt): Handle art and art_variants, once .hmml supports them +#if DEBUG + printf( + "================================================================================\n" + "%s\n" + "================================================================================\n", + Filename); +#endif + // NOTE(matt): Tree structure of "global" buffer dependencies + // Master + // IncludesPlayer + // Menus + // MenuBuffers->Quote + // MenuBuffers->Reference + // MenuBuffers->Filter + // MenuBuffers->FilterTopics + // MenuBuffers->FilterMedia + // MenuBuffers->Credits + // Player + // Script + + + menu_buffers MenuBuffers = {}; + index_buffers IndexBuffers = {}; + player_buffers PlayerBuffers = {}; + + if(ClaimMenuIndexAndPlayerBuffers(&MenuBuffers, &IndexBuffers, &PlayerBuffers) == RC_SUCCESS) + { + memory_book Strings = InitBookOfStrings(Kilobytes(4)); + _memory_book(ref_info) ReferencesArray = InitBook(MBT_REF_INFO, 8); + speakers Speakers = InitSpeakers(); + memory_book Topics = InitBook(MBT_CATEGORY_INFO, 8); + memory_book Media = InitBook(MBT_CATEGORY_INFO, 8); + + bool HasQuoteMenu = FALSE; + bool HasReferenceMenu = FALSE; + bool HasFilterMenu = FALSE; + + int QuoteIdentifier = 0x3b1; + int RefIdentifier = 1; + + CopyStringToBuffer(&PlayerBuffers.Menus, + "
\n" + " ", (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base); + CopyStringToBufferHTMLSafe(&PlayerBuffers.Menus, Wrap0(HMML.metadata.title)); + CopyStringToBuffer(&PlayerBuffers.Menus, "\n"); + + CopyStringToBuffer(&PlayerBuffers.Main, + "
\n" + "
\n" + "
\n", HMML.metadata.id, (int)CurrentProject->Theme.Length, CurrentProject->Theme.Base); + + if(N) + { + N->WorkingThis.LinkOffsets.PrevStart = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location); + if((N->Prev && N->Prev->Size) || (N->Next && N->Next->Size)) + { + if(N->Prev && N->Prev->Size) + { + // TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here + buffer PreviousPlayerURL = {}; + ClaimBuffer(&PreviousPlayerURL, BID_PREVIOUS_PLAYER_URL, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); + ConstructPlayerURL(&PreviousPlayerURL, N->Project, Wrap0i(N->Prev->OutputLocation, sizeof(N->Prev->OutputLocation))); + CopyStringToBuffer(&PlayerBuffers.Main, + "
Previous: '%s'
\n", + PreviousPlayerURL.Location, + N->Prev->Title); + + DeclaimBuffer(&PreviousPlayerURL); + } + else + { + CopyStringToBuffer(&PlayerBuffers.Main, + "
Welcome to %.*s
\n", (int)ProjectTitle.Length, ProjectTitle.Base); + } + } + N->WorkingThis.LinkOffsets.PrevEnd = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location - N->WorkingThis.LinkOffsets.PrevStart); + } + + CopyStringToBuffer(&PlayerBuffers.Main, + "
\n"); + + bool RequiresCineraJS = FALSE; + Result = BuildCredits(FilepathL, &MenuBuffers.Credits, &HMML.metadata, &Speakers, &RequiresCineraJS); + if(Result == RC_SUCCESS) + { + CopyStringToBuffer(&CollationBuffers->SearchEntry, "name: \""); + CopyStringToBuffer(&CollationBuffers->SearchEntry, "%.*s", (int)OutputLocation.Length, OutputLocation.Base); + + CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"\n" + "title: \""); + CopyStringToBufferNoFormat(&CollationBuffers->SearchEntry, Wrap0(HMML.metadata.title)); + CopyStringToBuffer(&CollationBuffers->SearchEntry, "\"\n" + "markers:\n"); #if DEBUG - printf("\n\n --- End of Timestamps Loop ---\n\n\n\n"); + printf("\n\n --- Entering Timestamps Loop ---\n\n\n\n"); #endif - if(HasQuoteMenu) - { - CopyStringToBuffer(&MenuBuffers.Quote, - "
\n" - "
\n"); - CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Quote, 0, PAGE_PLAYER); - } - if(HasReferenceMenu) - { - for(int i = 0; i < UniqueRefs; ++i) - { - CopyStringToBuffer(&MenuBuffers.Reference, - " \n" - " \n", - ReferencesArray[i].ID, - ReferencesArray[i].URL); + char *PreviousTimecode = 0; - if(*ReferencesArray[i].Source) - { - CopyStringToBuffer(&MenuBuffers.Reference, - "
"); - CopyStringToBufferHTMLSafeBreakingOnSlash(&MenuBuffers.Reference, ReferencesArray[i].Source); - CopyStringToBuffer(&MenuBuffers.Reference, "
\n" - "
"); - CopyStringToBufferHTMLSafeBreakingOnSlash(&MenuBuffers.Reference, ReferencesArray[i].RefTitle); - CopyStringToBuffer(&MenuBuffers.Reference, "
\n"); - } - else - { - CopyStringToBuffer(&MenuBuffers.Reference, - "
"); - CopyStringToBufferHTMLSafeBreakingOnSlash(&MenuBuffers.Reference, ReferencesArray[i].RefTitle); - CopyStringToBuffer(&MenuBuffers.Reference, "
\n"); - } - CopyStringToBuffer(&MenuBuffers.Reference, - "
\n"); + for(int TimestampIndex = 0; TimestampIndex < HMML.annotation_count; ++TimestampIndex) + { + // TODO(matt): Thoroughly test this reorganisation + // + // Make sure to: + // • verify that the Privacy thing remains okay + // • manage that memory + // +#if DEBUG + printf("%d\n", TimestampIndex); +#endif - for(int j = 0; j < ReferencesArray[i].IdentifierCount;) - { - CopyStringToBuffer(&MenuBuffers.Reference, - "
\n "); - for(int k = 0; k < 3 && j < ReferencesArray[i].IdentifierCount; ++k, ++j) - { - CopyStringToBuffer(&MenuBuffers.Reference, - "[%d]%s", - TimecodeToSeconds(ReferencesArray[i].Identifier[j].Timecode), - ReferencesArray[i].Identifier[j].Identifier, - ReferencesArray[i].Identifier[j].Timecode); + HMML_Annotation *Anno = HMML.annotations + TimestampIndex; + + string Author = {}; + if(Anno->quote.author) { Author = Wrap0(Anno->quote.author); } + else if(HMML.metadata.stream_username) { Author = Wrap0(HMML.metadata.stream_username); } + else if(CurrentProject->StreamUsername.Length > 0) { Author = CurrentProject->StreamUsername; } + else { Author = CurrentProject->Owner->ID; } + + /* */ MEM_TEST_MID("HMMLToBuffers"); + /* +MEM */ Result = ProcessTimecode(CollationBuffers, N, Wrap0(Filepath), &Strings, + /* */ &MenuBuffers, &IndexBuffers, &PlayerBuffers, + /* */ DefaultMedium, &Speakers, Author, &ReferencesArray, + /* */ &HasQuoteMenu, &HasReferenceMenu, &HasFilterMenu, &RequiresCineraJS, + /* */ &QuoteIdentifier, &RefIdentifier, + /* */ &Topics, &Media, + /* */ Anno, &PreviousTimecode); + /* */ MEM_TEST_MID("HMMLToBuffers"); + if(Result != RC_SUCCESS) + { + break; + } + } + + if(Result == RC_SUCCESS) + { + + DeclaimIndexBuffers(&IndexBuffers); + + CopyStringToBuffer(&CollationBuffers->SearchEntry, "---\n"); + N->WorkingThis.Size = CollationBuffers->SearchEntry.Ptr - CollationBuffers->SearchEntry.Location; + +#if DEBUG + printf("\n\n --- End of Timestamps Loop ---\n\n\n\n"); +#endif + if(HasQuoteMenu) + { + CopyStringToBuffer(&MenuBuffers.Quote, + "
\n" + "
\n"); + CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Quote, 0, PAGE_PLAYER); + } + + if(HasReferenceMenu) + { + for(int i = 0; i < ReferencesArray.ItemCount; ++i) + { + ref_info *This = GetPlaceInBook(&ReferencesArray, i); + CopyStringToBuffer(&MenuBuffers.Reference, + " \n" + " \n", + This->ID, + (int)This->URL.Length, This->URL.Base); + + if(This->Source.Length > 0) + { + CopyStringToBuffer(&MenuBuffers.Reference, + "
"); + CopyStringToBufferHTMLSafeBreakingOnSlash(&MenuBuffers.Reference, This->Source); + CopyStringToBuffer(&MenuBuffers.Reference, "
\n"); + } + + CopyStringToBuffer(&MenuBuffers.Reference, + "
"); + CopyStringToBufferHTMLSafeBreakingOnSlash(&MenuBuffers.Reference, This->RefTitle); + CopyStringToBuffer(&MenuBuffers.Reference, "
\n"); + + CopyStringToBuffer(&MenuBuffers.Reference, + "
\n"); + + for(int j = 0; j < This->Identifier.ItemCount;) + { + CopyStringToBuffer(&MenuBuffers.Reference, + "
\n "); + for(int k = 0; k < 3 && j < This->Identifier.ItemCount; ++k, ++j) + { + identifier *ThisIdentifier = GetPlaceInBook(&This->Identifier, j); + CopyStringToBuffer(&MenuBuffers.Reference, + "[%d]%s", + TimecodeToSeconds(ThisIdentifier->Timecode), + ThisIdentifier->Identifier, + ThisIdentifier->Timecode); + } + CopyStringToBuffer(&MenuBuffers.Reference, "\n" + "
\n"); + } + + CopyStringToBuffer(&MenuBuffers.Reference, + "
\n"); + } + + CopyStringToBuffer(&MenuBuffers.Reference, + "
\n" + "
\n"); + CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Reference, 0, PAGE_PLAYER); + } + + if(HasFilterMenu) + { + buffer URL = {}; + asset *FilterImage = GetAsset(Wrap0(BuiltinAssets[ASSET_IMG_FILTER].Filename), ASSET_IMG); + ConstructResolvedAssetURL(&URL, FilterImage, PAGE_PLAYER); + CopyStringToBuffer(&MenuBuffers.Filter, + "
\n" + " \n" + "
\n" + "
Filter mode:
\n" + "
\n"); + + if(Topics.ItemCount > 0) + { + CopyStringToBuffer(&MenuBuffers.Filter, + "
\n" + "
Topics
\n"); + for(int i = 0; i < Topics.ItemCount; ++i) + { + category_info *This = GetPlaceInBook(&Topics, i); + // NOTE(matt): Stack-string + char SanitisedMarker[This->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); + SanitisePunctuation(SanitisedMarker); + + bool NullTopic = StringsMatch(This->Marker, Wrap0("nullTopic")); + CopyStringToBuffer(&MenuBuffers.FilterTopics, + "
\n" + " %.*s\n" + "
\n", + + NullTopic ? "title=\"Timestamps that don't fit into the above topic(s) may be filtered using this pseudo-topic\" " : "", + SanitisedMarker, + SanitisedMarker, + NullTopic ? (int)StringLength("(null topic)") : (int)This->Marker.Length, + NullTopic ? "(null topic)" : This->Marker.Base); + } + CopyStringToBuffer(&MenuBuffers.FilterTopics, + "
\n"); + CopyLandmarkedBuffer(&MenuBuffers.Filter, &MenuBuffers.FilterTopics, 0, PAGE_PLAYER); + } + + if(Media.ItemCount > 0) + { + CopyStringToBuffer(&MenuBuffers.FilterMedia, + "
\n" + "
Media
\n"); + for(int i = 0; i < Media.ItemCount; ++i) + { + category_info *This = GetPlaceInBook(&Media, i); + // NOTE(matt): Stack-string + char SanitisedMarker[This->Marker.Length + 1]; + CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%.*s", (int)This->Marker.Length, This->Marker.Base); + SanitisePunctuation(SanitisedMarker); + + medium *Medium = GetMediumFromProject(CurrentProject, This->Marker); + CopyStringToBuffer(&MenuBuffers.FilterMedia, + "
\n" + " ", + SanitisedMarker, Medium->Hidden ? " off" : ""); + + + PushIcon(&MenuBuffers.FilterMedia, FALSE, Medium->IconType, Medium->Icon, Medium->IconAsset, Medium->IconVariants, PAGE_PLAYER, &RequiresCineraJS); + + CopyStringToBuffer(&MenuBuffers.FilterMedia, "%.*s%s\n" + "
\n", + (int)Medium->Name.Length, Medium->Name.Base, + Medium == DefaultMedium ? "🟉" : ""); + } + CopyStringToBuffer(&MenuBuffers.FilterMedia, + "
\n"); + CopyLandmarkedBuffer(&MenuBuffers.Filter, &MenuBuffers.FilterMedia, 0, PAGE_PLAYER); + } + + CopyStringToBuffer(&MenuBuffers.Filter, + "
\n" + "
\n" + "
\n"); + + CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Filter, 0, PAGE_PLAYER); + + } + + CopyStringToBuffer(&PlayerBuffers.Menus, + "
\n" + "
🎭
\n" + "
\n" + "
🏟
\n" + "
\n" + "
\n" + "
\n" + " 🔗\n" + " \n" + "
\n"); + + bool HasCreditsMenu = MenuBuffers.Credits.Ptr > MenuBuffers.Credits.Location; + if(HasCreditsMenu) + { + CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Credits, 0, PAGE_PLAYER); + } + + CopyStringToBuffer(&PlayerBuffers.Menus, + "
\n" + " ?\n" + "
\n" + " ?

Keyboard Navigation

\n" + "\n" + "

Global Keys

\n" + " [, < / ], > Jump to previous / next episode
\n" + " W, K, P / S, J, N Jump to previous / next marker
\n" + " t / T Toggle theatre / SUPERtheatre mode
\n" + " V Revert filter to original state Y Select link (requires manual Ctrl-c)\n", + + HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable"); + + CopyStringToBuffer(&PlayerBuffers.Menus, + "\n" + "

Menu toggling

\n" + " q Quotes\n" + " r References\n" + " f Filter\n" + " y Link\n" + " c Credits\n", + + HasQuoteMenu ? "" : " unavailable", HasQuoteMenu ? "" : " unavailable", + HasReferenceMenu ? "" : " unavailable", HasReferenceMenu ? "" : " unavailable", + HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", + HasCreditsMenu ? "" : " unavailable", HasCreditsMenu ? "" : " unavailable"); + + CopyStringToBuffer(&PlayerBuffers.Menus, + "\n" + "

In-Menu Movement

\n" + "
\n" + "
\n" + "
\n" + " a\n" + "
\n" + "
\n" + " w
\n" + " s\n" + "
\n" + "
\n" + " d\n" + "
\n" + "
\n" + "
\n" + " h\n" + " j\n" + " k\n" + " l\n" + "
\n" + "
\n" + "
\n" + " \n" + "
\n" + "
\n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + "
\n" + "
\n" + "
\n" + "
\n"); + + CopyStringToBuffer(&PlayerBuffers.Menus, + "

%sQuotes %sand%s References%s Menus%s

\n" + " Enter Jump to timecode
\n", + // Q R + // + // 0 0

Quotes and References Menus

+ // 0 1

Quotes and References Menus

+ // 1 0

Quotes and References Menus

+ // 1 1

Quotes and References Menus

+ + HasQuoteMenu ? "" : "", + HasQuoteMenu && !HasReferenceMenu ? "" : "", + !HasQuoteMenu && HasReferenceMenu ? "" : "", + HasQuoteMenu && !HasReferenceMenu ? "" : "", + !HasQuoteMenu && !HasReferenceMenu ? "" : "", + HasQuoteMenu || HasReferenceMenu ? "" : " unavailable", HasQuoteMenu || HasReferenceMenu ? "" : " unavailable"); + + CopyStringToBuffer(&PlayerBuffers.Menus, + "\n" + "

%sQuotes%s,%s References %sand%s Credits%s Menus%s

" + " o Open URL (in new tab)\n", + // Q R C + // + // 0 0 0

Quotes, References and Credits Menus

+ // 0 0 1

Quotes, References and Credits Menus

+ // 0 1 0

Quotes, References and Credits Menus

+ // 0 1 1

Quotes, References and Credits Menus

+ // 1 0 0

Quotes, References and Credits Menus

+ // 1 0 1

Quotes, References and Credits Menus

+ // 1 1 0

Quotes, References and Credits Menus

+ // 1 1 1

Quotes, References and Credits Menus

+ + /* 0 */ HasQuoteMenu ? "" : "", + /* 1 */ HasQuoteMenu && !HasReferenceMenu ? "" : "", + /* 2 */ !HasQuoteMenu && HasReferenceMenu ? "" : "", + /* 3 */ HasReferenceMenu && !HasCreditsMenu ? "" : HasQuoteMenu && !HasReferenceMenu && HasCreditsMenu ? "" : "", + /* 4 */ !HasQuoteMenu && !HasReferenceMenu && HasCreditsMenu ? "" : "", + /* 5 */ !HasCreditsMenu && (HasQuoteMenu || HasReferenceMenu) ? "" : "", + /* 6 */ !HasQuoteMenu && !HasReferenceMenu && !HasCreditsMenu ? "" : "", + + HasQuoteMenu || HasReferenceMenu || HasCreditsMenu ? "" : " unavailable", + HasQuoteMenu || HasReferenceMenu || HasCreditsMenu ? "" : " unavailable"); + + CopyStringToBuffer(&PlayerBuffers.Menus, + "\n" + "

%sFilter Menu%s

\n" + " x, Space Toggle category and focus next
\n" + " X, ShiftSpace Toggle category and focus previous
\n" + " v Invert topics / media as per focus\n" + "\n" + "

%sFilter and%s Link Menus

\n" + " z Toggle %sfilter /%s linking mode\n", + + HasFilterMenu ? "" : "", HasFilterMenu ? "" : "", + HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", + HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", + HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", + HasFilterMenu ? "" : "", HasFilterMenu ? "" : "", + HasFilterMenu ? "" : "", HasFilterMenu ? "" : ""); + + CopyStringToBuffer(&PlayerBuffers.Menus, + "\n" + "

%sCredits Menu%s

\n" + " Enter Open URL (in new tab)
\n", + + HasCreditsMenu ? "" : "", HasCreditsMenu ? "" : "", + HasCreditsMenu ? "" : " unavailable", HasCreditsMenu ? "" : " unavailable"); + + CopyStringToBuffer(&PlayerBuffers.Menus, + "
\n" + "
\n" + + "
"); + + CopyStringToBuffer(&PlayerBuffers.Main, + "
\n"); + + if(N) + { + N->WorkingThis.LinkOffsets.NextStart = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location - (N->WorkingThis.LinkOffsets.PrevStart + N->WorkingThis.LinkOffsets.PrevEnd)); + if((N->Prev && N->Prev->Size) || (N->Next && N->Next->Size)) + { + if(N->Next && N->Next->Size > 0) + { + // TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here + buffer NextPlayerURL = {}; + ClaimBuffer(&NextPlayerURL, BID_NEXT_PLAYER_URL, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); + ConstructPlayerURL(&NextPlayerURL, N->Project, Wrap0i(N->Next->OutputLocation, sizeof(N->Next->OutputLocation))); + CopyStringToBuffer(&PlayerBuffers.Main, + "
Next: '%s'
\n", + NextPlayerURL.Location, + N->Next->Title); + + DeclaimBuffer(&NextPlayerURL); + } + else + { + CopyStringToBuffer(&PlayerBuffers.Main, + "
You have arrived at the (current) end of %.*s
\n", (int)ProjectTitle.Length, ProjectTitle.Base); + } + } + N->WorkingThis.LinkOffsets.NextEnd = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location - (N->WorkingThis.LinkOffsets.PrevStart + N->WorkingThis.LinkOffsets.PrevEnd + N->WorkingThis.LinkOffsets.NextStart)); + } + + CopyStringToBuffer(&PlayerBuffers.Main, + "
\n" + "
"); + + buffer URLSearch = {}; + ClaimBuffer(&URLSearch, BID_URL_SEARCH, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1); + ConstructSearchURL(&URLSearch, CurrentProject); + CopyString(CollationBuffers->URLSearch, sizeof(CollationBuffers->URLSearch), "%s", URLSearch.Location); + DeclaimBuffer(&URLSearch); + + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\n" + " \n", + CINERA_APP_VERSION.Major, + CINERA_APP_VERSION.Minor, + CINERA_APP_VERSION.Patch); + + if(Topics.ItemCount || Media.ItemCount) + { + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + " 0) + { + for(int i = 0; i < Topics.ItemCount; ++i) + { + category_info *This = GetPlaceInBook(&Topics, i); + if(StringsMatch(This->Marker, Wrap0("nullTopic"))) + { + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, "%.*s, ", (int)This->Marker.Length, This->Marker.Base); + } + } + } + + if(Media.ItemCount > 0) + { + for(int i = 0; i < Media.ItemCount; ++i) + { + category_info *This = GetPlaceInBook(&Media, i); + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, "%.*s, ", (int)This->WrittenText.Length, This->WrittenText.Base); + } + } + + CollationBuffers->IncludesPlayer.Ptr -= 2; + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, "\">\n"); + } + + buffer URL = {}; + URL.ID = BID_URL; + + asset *CSSCinera = GetAsset(Wrap0(BuiltinAssets[ASSET_CSS_CINERA].Filename), ASSET_CSS); + ConstructResolvedAssetURL(&URL, CSSCinera, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\n" + " IncludesPlayer, CSSCinera, PAGE_PLAYER, 0); + + string ThemeFilename = MakeString("sls", "cinera__", &CurrentProject->Theme, ".css"); + asset *CSSTheme = GetAsset(ThemeFilename, ASSET_CSS); + FreeString(&ThemeFilename); + ConstructResolvedAssetURL(&URL, CSSTheme, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\">\n" + " IncludesPlayer, CSSTheme, PAGE_PLAYER, 0); + + asset *CSSTopics = GetAsset(Wrap0(BuiltinAssets[ASSET_CSS_TOPICS].Filename), ASSET_CSS); + ConstructResolvedAssetURL(&URL, CSSTopics, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\">\n" + " IncludesPlayer, CSSTopics, PAGE_PLAYER, 0); + + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\">"); + + if(BespokeTemplate->Metadata.Tags.ItemCount > 0) + { + if(BespokeTemplate->Metadata.RequiresCineraJS) + { + RequiresCineraJS = TRUE; + } + } + else + { + if(CurrentProject->PlayerTemplate.Metadata.RequiresCineraJS) + { + RequiresCineraJS = TRUE; + } + } + + asset *JSCineraPre = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_CINERA_PRE].Filename), ASSET_JS); + ConstructResolvedAssetURL(&URL, JSCineraPre, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\n "); + + if(RequiresCineraJS) + { + asset *JSCineraPost = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_CINERA_POST].Filename), ASSET_JS); + ConstructResolvedAssetURL(&URL, JSCineraPost, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\n "); + } + + asset *JSPlayerPre = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_PLAYER_PRE].Filename), ASSET_JS); + ConstructResolvedAssetURL(&URL, JSPlayerPre, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->IncludesPlayer, + "\n "); + + asset *JSPlayerPost = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_PLAYER_POST].Filename), ASSET_JS); + ConstructResolvedAssetURL(&URL, JSPlayerPost, PAGE_PLAYER); + CopyStringToBuffer(&PlayerBuffers.Script, + ""); + + CopyStringToBuffer(&CollationBuffers->Player, "
\n" + " "); + CopyLandmarkedBuffer(&CollationBuffers->Player, &PlayerBuffers.Menus, 0, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->Player, "\n" + " "); + CopyLandmarkedBuffer(&CollationBuffers->Player, &PlayerBuffers.Main, &N->WorkingThis.LinkOffsets.PrevStart, PAGE_PLAYER); + CopyStringToBuffer(&CollationBuffers->Player, "\n" + " "); + CopyStringToBuffer(&CollationBuffers->Player, "
\n" + " "); + CopyLandmarkedBuffer(&CollationBuffers->Player, &PlayerBuffers.Script, 0, PAGE_PLAYER); + + // NOTE(matt): Tree structure of "global" buffer dependencies + // MenuBuffers.Credits + // MenuBuffers.FilterMedia + // MenuBuffers.FilterTopics + // MenuBuffers.Filter + // MenuBuffers.Reference + // MenuBuffers.Quote + DeclaimMenuBuffers(&MenuBuffers); + DeclaimPlayerBuffers(&PlayerBuffers); + } + } + FreeBook(&Strings); + FreeReferences(&ReferencesArray); + FreeSpeakers(&Speakers); + FreeBook(&Topics); + FreeBook(&Media); } - CopyStringToBuffer(&MenuBuffers.Reference, "\n" - "
\n"); - } - - CopyStringToBuffer(&MenuBuffers.Reference, - " \n"); - } - - CopyStringToBuffer(&MenuBuffers.Reference, - "
\n" - "
\n"); - CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Reference, 0, PAGE_PLAYER); - } - - if(HasFilterMenu) - { - buffer URL; - asset *FilterImage = GetAsset(Wrap0(BuiltinAssets[ASSET_IMG_FILTER].Filename), ASSET_IMG); - ConstructResolvedAssetURL(&URL, FilterImage, PAGE_PLAYER); - CopyStringToBuffer(&MenuBuffers.Filter, - "
\n" - " \n" - "
\n" - "
Filter mode:
\n" - "
\n"); - - if(Topics.Count > 0) - { - CopyStringToBuffer(&MenuBuffers.Filter, - "
\n" - "
Topics
\n"); - for(int i = 0; i < Topics.Count; ++i) - { - // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(Topics.Category[i].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", Topics.Category[i].Marker); - SanitisePunctuation(SanitisedMarker); - - bool NullTopic = !StringsDiffer0(Topics.Category[i].Marker, "nullTopic"); - CopyStringToBuffer(&MenuBuffers.FilterTopics, - "
\n" - " %s\n" - "
\n", - - NullTopic ? "title=\"Timestamps that don't fit into the above topic(s) may be filtered using this pseudo-topic\" " : "", - SanitisedMarker, - SanitisedMarker, - NullTopic ? "(null topic)" : Topics.Category[i].Marker); - } - CopyStringToBuffer(&MenuBuffers.FilterTopics, - "
\n"); - CopyLandmarkedBuffer(&MenuBuffers.Filter, &MenuBuffers.FilterTopics, 0, PAGE_PLAYER); - } - - if(Media.Count > 0) - { - CopyStringToBuffer(&MenuBuffers.FilterMedia, - "
\n" - "
Media
\n"); - for(int i = 0; i < Media.Count; ++i) - { - // NOTE(matt): Stack-string - char SanitisedMarker[StringLength(Media.Category[i].Marker) + 1]; - CopyString(SanitisedMarker, sizeof(SanitisedMarker), "%s", Media.Category[i].Marker); - SanitisePunctuation(SanitisedMarker); - - medium *Medium = GetMediumFromProject(CurrentProject, Wrap0i(Media.Category[i].Marker, sizeof(Media.Category[i].Marker))); - CopyStringToBuffer(&MenuBuffers.FilterMedia, - "
\n" - " ", - SanitisedMarker, Medium->Hidden ? " off" : ""); - - - PushIcon(&MenuBuffers.FilterMedia, FALSE, Medium->IconType, Medium->Icon, Medium->IconAsset, Medium->IconVariants, PAGE_PLAYER, &RequiresCineraJS); - - CopyStringToBuffer(&MenuBuffers.FilterMedia, "%.*s%s\n" - "
\n", - (int)Medium->Name.Length, Medium->Name.Base, - Medium == DefaultMedium ? "🟉" : ""); - } - CopyStringToBuffer(&MenuBuffers.FilterMedia, - "
\n"); - CopyLandmarkedBuffer(&MenuBuffers.Filter, &MenuBuffers.FilterMedia, 0, PAGE_PLAYER); - } - - CopyStringToBuffer(&MenuBuffers.Filter, - "
\n" - "
\n" - "
\n"); - - CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Filter, 0, PAGE_PLAYER); - - } - - CopyStringToBuffer(&PlayerBuffers.Menus, - "
\n" - "
🎭
\n" - "
\n" - "
🏟
\n" - "
\n" - "
\n" - "
\n" - " 🔗\n" - " \n" - "
\n"); - - bool HasCreditsMenu = MenuBuffers.Credits.Ptr > MenuBuffers.Credits.Location; - if(HasCreditsMenu) - { - CopyLandmarkedBuffer(&PlayerBuffers.Menus, &MenuBuffers.Credits, 0, PAGE_PLAYER); - } - - CopyStringToBuffer(&PlayerBuffers.Menus, - "
\n" - " ?\n" - "
\n" - " ?

Keyboard Navigation

\n" - "\n" - "

Global Keys

\n" - " [, < / ], > Jump to previous / next episode
\n" - " W, K, P / S, J, N Jump to previous / next marker
\n" - " t / T Toggle theatre / SUPERtheatre mode
\n" - " V Revert filter to original state Y Select link (requires manual Ctrl-c)\n", - - HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable"); - - CopyStringToBuffer(&PlayerBuffers.Menus, - "\n" - "

Menu toggling

\n" - " q Quotes\n" - " r References\n" - " f Filter\n" - " y Link\n" - " c Credits\n", - - HasQuoteMenu ? "" : " unavailable", HasQuoteMenu ? "" : " unavailable", - HasReferenceMenu ? "" : " unavailable", HasReferenceMenu ? "" : " unavailable", - HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", - HasCreditsMenu ? "" : " unavailable", HasCreditsMenu ? "" : " unavailable"); - - CopyStringToBuffer(&PlayerBuffers.Menus, - "\n" - "

In-Menu Movement

\n" - "
\n" - "
\n" - "
\n" - " a\n" - "
\n" - "
\n" - " w
\n" - " s\n" - "
\n" - "
\n" - " d\n" - "
\n" - "
\n" - "
\n" - " h\n" - " j\n" - " k\n" - " l\n" - "
\n" - "
\n" - "
\n" - " \n" - "
\n" - "
\n" - "
\n" - " \n" - "
\n" - "
\n" - " \n" - "
\n" - "
\n" - "
\n" - "
\n"); - - CopyStringToBuffer(&PlayerBuffers.Menus, - "

%sQuotes %sand%s References%s Menus%s

\n" - " Enter Jump to timecode
\n", - // Q R - // - // 0 0

Quotes and References Menus

- // 0 1

Quotes and References Menus

- // 1 0

Quotes and References Menus

- // 1 1

Quotes and References Menus

- - HasQuoteMenu ? "" : "", - HasQuoteMenu && !HasReferenceMenu ? "" : "", - !HasQuoteMenu && HasReferenceMenu ? "" : "", - HasQuoteMenu && !HasReferenceMenu ? "" : "", - !HasQuoteMenu && !HasReferenceMenu ? "" : "", - HasQuoteMenu || HasReferenceMenu ? "" : " unavailable", HasQuoteMenu || HasReferenceMenu ? "" : " unavailable"); - - CopyStringToBuffer(&PlayerBuffers.Menus, - "\n" - "

%sQuotes%s,%s References %sand%s Credits%s Menus%s

" - " o Open URL (in new tab)\n", - // Q R C - // - // 0 0 0

Quotes, References and Credits Menus

- // 0 0 1

Quotes, References and Credits Menus

- // 0 1 0

Quotes, References and Credits Menus

- // 0 1 1

Quotes, References and Credits Menus

- // 1 0 0

Quotes, References and Credits Menus

- // 1 0 1

Quotes, References and Credits Menus

- // 1 1 0

Quotes, References and Credits Menus

- // 1 1 1

Quotes, References and Credits Menus

- - /* 0 */ HasQuoteMenu ? "" : "", - /* 1 */ HasQuoteMenu && !HasReferenceMenu ? "" : "", - /* 2 */ !HasQuoteMenu && HasReferenceMenu ? "" : "", - /* 3 */ HasReferenceMenu && !HasCreditsMenu ? "" : HasQuoteMenu && !HasReferenceMenu && HasCreditsMenu ? "" : "", - /* 4 */ !HasQuoteMenu && !HasReferenceMenu && HasCreditsMenu ? "" : "", - /* 5 */ !HasCreditsMenu && (HasQuoteMenu || HasReferenceMenu) ? "" : "", - /* 6 */ !HasQuoteMenu && !HasReferenceMenu && !HasCreditsMenu ? "" : "", - - HasQuoteMenu || HasReferenceMenu || HasCreditsMenu ? "" : " unavailable", - HasQuoteMenu || HasReferenceMenu || HasCreditsMenu ? "" : " unavailable"); - - CopyStringToBuffer(&PlayerBuffers.Menus, - "\n" - "

%sFilter Menu%s

\n" - " x, Space Toggle category and focus next
\n" - " X, ShiftSpace Toggle category and focus previous
\n" - " v Invert topics / media as per focus\n" - "\n" - "

%sFilter and%s Link Menus

\n" - " z Toggle %sfilter /%s linking mode\n", - - HasFilterMenu ? "" : "", HasFilterMenu ? "" : "", - HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", - HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", - HasFilterMenu ? "" : " unavailable", HasFilterMenu ? "" : " unavailable", - HasFilterMenu ? "" : "", HasFilterMenu ? "" : "", - HasFilterMenu ? "" : "", HasFilterMenu ? "" : ""); - - CopyStringToBuffer(&PlayerBuffers.Menus, - "\n" - "

%sCredits Menu%s

\n" - " Enter Open URL (in new tab)
\n", - - HasCreditsMenu ? "" : "", HasCreditsMenu ? "" : "", - HasCreditsMenu ? "" : " unavailable", HasCreditsMenu ? "" : " unavailable"); - - CopyStringToBuffer(&PlayerBuffers.Menus, - "
\n" - "
\n" - - "
"); - - CopyStringToBuffer(&PlayerBuffers.Main, - "
\n"); - - if(N) - { - N->WorkingThis.LinkOffsets.NextStart = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location - (N->WorkingThis.LinkOffsets.PrevStart + N->WorkingThis.LinkOffsets.PrevEnd)); - if((N->Prev && N->Prev->Size) || (N->Next && N->Next->Size)) - { - if(N->Next && N->Next->Size > 0) - { - // TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here - buffer NextPlayerURL; - ClaimBuffer(&NextPlayerURL, BID_NEXT_PLAYER_URL, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); - ConstructPlayerURL(&NextPlayerURL, N->Project, Wrap0i(N->Next->OutputLocation, sizeof(N->Next->OutputLocation))); - CopyStringToBuffer(&PlayerBuffers.Main, - "
Next: '%s'
\n", - NextPlayerURL.Location, - N->Next->Title); - - DeclaimBuffer(&NextPlayerURL); - } - else - { - CopyStringToBuffer(&PlayerBuffers.Main, - "
You have arrived at the (current) end of %.*s
\n", (int)ProjectTitle.Length, ProjectTitle.Base); - } - } - N->WorkingThis.LinkOffsets.NextEnd = (PlayerBuffers.Main.Ptr - PlayerBuffers.Main.Location - (N->WorkingThis.LinkOffsets.PrevStart + N->WorkingThis.LinkOffsets.PrevEnd + N->WorkingThis.LinkOffsets.NextStart)); - } - - CopyStringToBuffer(&PlayerBuffers.Main, - " \n" - " "); - - buffer URLSearch; - ClaimBuffer(&URLSearch, BID_URL_SEARCH, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1); - ConstructSearchURL(&URLSearch, CurrentProject); - CopyString(CollationBuffers->URLSearch, sizeof(CollationBuffers->URLSearch), "%s", URLSearch.Location); - DeclaimBuffer(&URLSearch); - - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\n" - " \n", - CINERA_APP_VERSION.Major, - CINERA_APP_VERSION.Minor, - CINERA_APP_VERSION.Patch); - - if(Topics.Count || Media.Count) - { - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - " 0) - { - for(int i = 0; i < Topics.Count; ++i) - { - if(StringsDiffer0(Topics.Category[i].Marker, "nullTopic")) + else { - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, "%s, ", Topics.Category[i].Marker); + Result = RC_ARENA_FULL; } + HMMLCleanup(); } } - - if(Media.Count > 0) + else { - for(int i = 0; i < Media.Count; ++i) - { - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, "%s, ", Media.Category[i].WrittenText); - } + N->WorkingThis.LinkOffsets.PrevStart = 0; + N->WorkingThis.LinkOffsets.PrevEnd = 0; + Result = RC_PRIVATE_VIDEO; } - CollationBuffers->IncludesPlayer.Ptr -= 2; - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, "\">\n"); - } - - buffer URL = {}; - URL.ID = BID_URL; - - asset *CSSCinera = GetAsset(Wrap0(BuiltinAssets[ASSET_CSS_CINERA].Filename), ASSET_CSS); - ConstructResolvedAssetURL(&URL, CSSCinera, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\n" - " IncludesPlayer, CSSCinera, PAGE_PLAYER, 0); - - string ThemeFilename = MakeString("sls", "cinera__", &CurrentProject->Theme, ".css"); - asset *CSSTheme = GetAsset(ThemeFilename, ASSET_CSS); - FreeString(&ThemeFilename); - ConstructResolvedAssetURL(&URL, CSSTheme, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\">\n" - " IncludesPlayer, CSSTheme, PAGE_PLAYER, 0); - - asset *CSSTopics = GetAsset(Wrap0(BuiltinAssets[ASSET_CSS_TOPICS].Filename), ASSET_CSS); - ConstructResolvedAssetURL(&URL, CSSTopics, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\">\n" - " IncludesPlayer, CSSTopics, PAGE_PLAYER, 0); - - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\">"); - - if(BespokeTemplate->Metadata.Tags.ItemCount > 0) - { - if(BespokeTemplate->Metadata.RequiresCineraJS) + if(Result != RC_SUCCESS) { - RequiresCineraJS = TRUE; + PrintEdit(EDIT_SKIP, CurrentProject->Lineage, BaseFilename, HMML.metadata.title ? &Title : 0, FALSE, TRUE); } } else { - if(CurrentProject->PlayerTemplate.Metadata.RequiresCineraJS) - { - RequiresCineraJS = TRUE; - } + LogError(LOG_ERROR, "%s:%d: %s", Filepath, HMML.error.line, HMML.error.message); + IndexingError(&FilepathL, HMML.error.line, S_ERROR, HMML.error.message, 0); + PrintEdit(EDIT_SKIP, CurrentProject->Lineage, BaseFilename, 0, FALSE, TRUE); + Result = RC_ERROR_HMML; } - - asset *JSCineraPre = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_CINERA_PRE].Filename), ASSET_JS); - ConstructResolvedAssetURL(&URL, JSCineraPre, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\n "); - - if(RequiresCineraJS) - { - asset *JSCineraPost = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_CINERA_POST].Filename), ASSET_JS); - ConstructResolvedAssetURL(&URL, JSCineraPost, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\n "); - } - - asset *JSPlayerPre = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_PLAYER_PRE].Filename), ASSET_JS); - ConstructResolvedAssetURL(&URL, JSPlayerPre, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->IncludesPlayer, - "\n "); - - asset *JSPlayerPost = GetAsset(Wrap0(BuiltinAssets[ASSET_JS_PLAYER_POST].Filename), ASSET_JS); - ConstructResolvedAssetURL(&URL, JSPlayerPost, PAGE_PLAYER); - CopyStringToBuffer(&PlayerBuffers.Script, - ""); - - CopyStringToBuffer(&CollationBuffers->Player, "
\n" - " "); - CopyLandmarkedBuffer(&CollationBuffers->Player, &PlayerBuffers.Menus, 0, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->Player, "\n" - " "); - CopyLandmarkedBuffer(&CollationBuffers->Player, &PlayerBuffers.Main, &N->WorkingThis.LinkOffsets.PrevStart, PAGE_PLAYER); - CopyStringToBuffer(&CollationBuffers->Player, "\n" - " "); - CopyStringToBuffer(&CollationBuffers->Player, "
\n" - " "); - CopyLandmarkedBuffer(&CollationBuffers->Player, &PlayerBuffers.Script, 0, PAGE_PLAYER); - - // NOTE(matt): Tree structure of "global" buffer dependencies - // MenuBuffers.Credits - // MenuBuffers.FilterMedia - // MenuBuffers.FilterTopics - // MenuBuffers.Filter - // MenuBuffers.Reference - // MenuBuffers.Quote - DeclaimMenuBuffers(&MenuBuffers); - DeclaimPlayerBuffers(&PlayerBuffers); + hmml_free(&HMML); } else { - LogError(LOG_ERROR, "%s:%d: %s", Filename, HMML.error.line, HMML.error.message); - fprintf(stderr, "%sSkipping%s %s:%d: %s\n", ColourStrings[CS_ERROR], ColourStrings[CS_END], Filename, HMML.error.line, HMML.error.message); - hmml_free(&HMML); - return RC_ERROR_HMML; + // TODO(matt): SystemError() + LogError(LOG_ERROR, "Unable to open %s: %s", Filepath, strerror(errno)); + fprintf(stderr, "Unable to open %s: %s\n", Filepath, strerror(errno)); + Result = RC_ERROR_FILE; } - hmml_free(&HMML); - return RC_SUCCESS; + + MEM_TEST_END("HMMLToBuffers"); + return Result; } typedef struct @@ -10420,12 +10731,12 @@ SortLandmarks(memory_book *A) for(int AssetIndex = 0; AssetIndex < A->ItemCount; ++AssetIndex) { asset *This = GetPlaceInBook(A, AssetIndex); - for(int SearchLandmarkIndex = 0; SearchLandmarkIndex < This->SearchLandmarkCount; ++SearchLandmarkIndex) + for(int SearchLandmarkIndex = 0; SearchLandmarkIndex < This->Search.ItemCount; ++SearchLandmarkIndex) { - landmark *A = This->Search + SearchLandmarkIndex; - for(int TestIndex = SearchLandmarkIndex + 1; TestIndex < This->SearchLandmarkCount; ++TestIndex) + landmark *A = GetPlaceInBook(&This->Search, SearchLandmarkIndex); + for(int TestIndex = SearchLandmarkIndex + 1; TestIndex < This->Search.ItemCount; ++TestIndex) { - landmark *B = This->Search + TestIndex; + landmark *B = GetPlaceInBook(&This->Search, TestIndex); if(A->Offset > B->Offset) { landmark Temp = *A; @@ -10435,12 +10746,12 @@ SortLandmarks(memory_book *A) } } - for(int PlayerLandmarkIndex = 0; PlayerLandmarkIndex < This->PlayerLandmarkCount; ++PlayerLandmarkIndex) + for(int PlayerLandmarkIndex = 0; PlayerLandmarkIndex < This->Player.ItemCount; ++PlayerLandmarkIndex) { - landmark *A = This->Player + PlayerLandmarkIndex; - for(int TestIndex = PlayerLandmarkIndex + 1; TestIndex < This->PlayerLandmarkCount; ++TestIndex) + landmark *A = GetPlaceInBook(&This->Player, PlayerLandmarkIndex); + for(int TestIndex = PlayerLandmarkIndex + 1; TestIndex < This->Player.ItemCount; ++TestIndex) { - landmark *B = This->Player + TestIndex; + landmark *B = GetPlaceInBook(&This->Player, TestIndex); if(A->Offset > B->Offset) { landmark Temp = *A; @@ -10456,7 +10767,7 @@ rc BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template *Template, char *OutputPath, page_type PageType, unsigned int *PlayerOffset) { rc Result = RC_SUCCESS; - MEM_TEST_INITIAL(); + MEM_TEST_TOP("BuffersToHTML()"); #if DEBUG printf("\n\n --- Buffer Collation ---\n" " %s\n\n\n", OutputPath ? OutputPath : Project->OutLocation); @@ -10486,14 +10797,13 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * #if AFD if((Template->Metadata.Validity & PageType))// || Project->Mode & MODE_FORCEINTEGRATION) { - buffer Master; + buffer Master = {}; Master.Size = Template->File.Buffer.Size + (Kilobytes(512)); Master.ID = BID_MASTER; if(!(Master.Location = malloc(Master.Size))) { LogError(LOG_ERROR, "BuffersToHTML(): %s", strerror(errno)); - MEM_TEST_AFTER("BuffersToHTML"); Result = RC_ERROR_MEMORY; goto End; } @@ -10544,7 +10854,9 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * case TAG_VOD_PLATFORM: CopyStringToBufferNoFormat(&Master, Wrap0i(CollationBuffers->VODPlatform, sizeof(CollationBuffers->VODPlatform))); break; case TAG_SEARCH: { - CopyLandmarkedBuffer(&Master, &CollationBuffers->Search, 0, PageType); + /* */ MEM_TEST_MID("BuffersToHTML()"); + /* +MEM */ CopyLandmarkedBuffer(&Master, &CollationBuffers->Search, 0, PageType); + /* */ MEM_TEST_MID("BuffersToHTML()"); } break; case TAG_INCLUDES: if(PageType == PAGE_PLAYER) @@ -10557,8 +10869,11 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * } break; case TAG_PLAYER: - CopyLandmarkedBuffer(&Master, &CollationBuffers->Player, PlayerOffset, PageType); - break; + { + /* */ MEM_TEST_MID("BuffersToHTML()"); + /* +MEM */ CopyLandmarkedBuffer(&Master, &CollationBuffers->Player, PlayerOffset, PageType); + /* */ MEM_TEST_MID("BuffersToHTML()"); + } break; case TAG_ASSET: { buffer URL; @@ -10627,7 +10942,6 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * case TAG_CUSTOM15: CopyStringToBufferNoFormat(&Master, Wrap0i(CollationBuffers->Custom15, sizeof(CollationBuffers->Custom15))); break; case TEMPLATE_TAG_COUNT: break; } - DepartComment(&Template->File.Buffer); } while(Template->File.Buffer.Ptr - Template->File.Buffer.Location < Template->File.Buffer.Size) @@ -10648,14 +10962,15 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * printf(" Freed Master\n"); #endif - MEM_TEST_AFTER("BuffersToHTML"); Result = RC_ERROR_FILE; goto End; } fwrite(Master.Location, Master.Ptr - Master.Location, 1, OutFile); fclose(OutFile); - FreeBuffer(&Master); + /* */ MEM_TEST_MID("BuffersToHTML()"); + /* +MEM */ FreeBuffer(&Master); + /* */ MEM_TEST_MID("BuffersToHTML()"); #if DEBUG_MEM MemLog = fopen("/home/matt/cinera_mem", "a+"); @@ -10664,32 +10979,25 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * printf(" Freed Master\n"); #endif - MEM_TEST_AFTER("BuffersToHTML"); } else { - MEM_TEST_AFTER("BuffersToHTML"); Result = RC_INVALID_TEMPLATE; } #endif // AFE - MEM_TEST_AFTER("BuffersToHTML"); } else { - MEM_TEST_MID("BuffersToHTML1"); buffer Master = {}; Master.Size = Kilobytes(512); Master.ID = BID_MASTER; - MEM_TEST_MID("BuffersToHTML2"); if(!(Master.Location = malloc(Master.Size))) { LogError(LOG_ERROR, "BuffersToHTML(): %s", strerror(errno)); - MEM_TEST_AFTER("BuffersToHTML"); Result = RC_ERROR_MEMORY; goto End; } - MEM_TEST_MID("BuffersToHTML3"); Master.Ptr = Master.Location; CopyStringToBuffer(&Master, @@ -10697,9 +11005,7 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * " \n" " "); - MEM_TEST_MID("BuffersToHTML4"); CopyLandmarkedBuffer(&Master, PageType == PAGE_PLAYER ? &CollationBuffers->IncludesPlayer : &CollationBuffers->IncludesSearch, 0, PageType); - MEM_TEST_MID("BuffersToHTML5"); CopyStringToBuffer(&Master, "\n"); CopyStringToBuffer(&Master, @@ -10708,15 +11014,16 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * " "); if(PageType == PAGE_PLAYER) { - CopyLandmarkedBuffer(&Master, &CollationBuffers->Player, PlayerOffset, PageType); - MEM_TEST_MID("BuffersToHTML9"); + /* */ MEM_TEST_MID("BuffersToHTML()"); + /* +MEM */ CopyLandmarkedBuffer(&Master, &CollationBuffers->Player, PlayerOffset, PageType); + /* */ MEM_TEST_MID("BuffersToHTML()"); CopyStringToBuffer(&Master, "\n"); } else { - MEM_TEST_MID("BuffersToHTML10"); - CopyLandmarkedBuffer(&Master, &CollationBuffers->Search, 0, PageType); - MEM_TEST_MID("BuffersToHTML11"); + /* */ MEM_TEST_MID("BuffersToHTML()"); + /* +MEM */ CopyLandmarkedBuffer(&Master, &CollationBuffers->Search, 0, PageType); + /* */ MEM_TEST_MID("BuffersToHTML()"); } CopyStringToBuffer(&Master, @@ -10724,27 +11031,24 @@ BuffersToHTML(config *C, project *Project, buffers *CollationBuffers, template * "\n"); FILE *OutFile; - MEM_TEST_MID("BuffersToHTML12"); if(!(OutFile = fopen(OutputPath, "w"))) { LogError(LOG_ERROR, "Unable to open output file %s: %s", OutputPath, strerror(errno)); DeclaimBuffer(&Master); - MEM_TEST_AFTER("BuffersToHTML"); Result = RC_ERROR_FILE; goto End; } - MEM_TEST_MID("BuffersToHTML13"); fwrite(Master.Location, Master.Ptr - Master.Location, 1, OutFile); - MEM_TEST_MID("BuffersToHTML13"); fclose(OutFile); - MEM_TEST_MID("BuffersToHTML14"); OutFile = 0; - FreeBuffer(&Master); - MEM_TEST_AFTER("BuffersToHTML"); + /* */ MEM_TEST_MID("BuffersToHTML()"); + /* +MEM */ FreeBuffer(&Master); + /* */ MEM_TEST_MID("BuffersToHTML()"); Result = RC_SUCCESS; } End: SortLandmarks(&Assets); + MEM_TEST_END("BuffersToHTML()"); return Result; } @@ -11149,8 +11453,8 @@ GetNeighbourhood(neighbourhood *N, edit_type_id EditType) db_entry * InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, bool RecheckingPrivacy, bool *Reinserting) { - MEM_TEST_INITIAL(); - //MEM_TEST_MID("InsertIntoDB1"); + MEM_TEST_TOP("InsertIntoDB()"); + db_entry *Result = 0; ResetNeighbourhood(N); edit_type_id EditType = EDIT_APPEND; int EntryInsertionStart = StringLength("---\n"); @@ -11192,111 +11496,100 @@ InsertIntoDB(neighbourhood *N, buffers *CollationBuffers, template *BespokeTempl bool VideoIsPrivate = FALSE; - //MEM_TEST_MID("InsertIntoDB2"); - switch(HMMLToBuffers(CollationBuffers, BespokeTemplate, BaseFilename, N)) + /* */ MEM_TEST_MID("InsertIntoDB()"); + /* +MEM */ rc HMMLToBuffersReturn = HMMLToBuffers(CollationBuffers, BespokeTemplate, BaseFilename, N); + /* */ MEM_TEST_MID("InsertIntoDB()"); + if(HMMLToBuffersReturn == RC_SUCCESS || HMMLToBuffersReturn == RC_PRIVATE_VIDEO) { - // TODO(matt): Actually sort out the fatality of these cases - case RC_ERROR_FILE: - case RC_ERROR_FATAL: - case RC_ERROR_HMML: - case RC_ERROR_MAX_REFS: - case RC_ERROR_QUOTE: - case RC_INVALID_REFERENCE: - return 0; - case RC_PRIVATE_VIDEO: - VideoIsPrivate = TRUE; - case RC_SUCCESS: - break; - } - //MEM_TEST_MID("InsertIntoDB3"); - - ClearCopyStringNoFormat(N->WorkingThis.HMMLBaseFilename, sizeof(N->WorkingThis.HMMLBaseFilename), BaseFilename); - - if(!VideoIsPrivate) { ClearCopyStringNoFormat(N->WorkingThis.Title, sizeof(N->WorkingThis.Title), Wrap0i(CollationBuffers->Title, sizeof(CollationBuffers->Title))); } - - if(!DB.File.Buffer.Location) - { - InitIndexFile(CurrentProject); - } - - if(EditType == EDIT_REINSERTION) - { - // NOTE(matt): To save opening the DB.Metadata file, we defer sniping N->This in until InsertNeighbourLink() - if(!VideoIsPrivate) + if(HMMLToBuffersReturn == RC_PRIVATE_VIDEO) { - *N->This = N->WorkingThis; + VideoIsPrivate = TRUE; + } + + ClearCopyStringNoFormat(N->WorkingThis.HMMLBaseFilename, sizeof(N->WorkingThis.HMMLBaseFilename), BaseFilename); + + if(!VideoIsPrivate) { ClearCopyStringNoFormat(N->WorkingThis.Title, sizeof(N->WorkingThis.Title), Wrap0i(CollationBuffers->Title, sizeof(CollationBuffers->Title))); } + + if(!DB.File.Buffer.Location) + { + /* */ MEM_TEST_MID("InsertIntoDB()"); + /* +MEM */ InitIndexFile(CurrentProject); + /* */ MEM_TEST_MID("InsertIntoDB()"); + } + + // TODO(matt): CollationBuffers->SearchEntry may, I believe, now contain data. We must not write it to file by mistake + if(EditType == EDIT_REINSERTION) + { + // NOTE(matt): To save opening the DB.Metadata file, we defer sniping N->This in until InsertNeighbourLink() + if(!VideoIsPrivate) + { + *N->This = N->WorkingThis; + + if(!(DB.File.Handle = fopen(DB.File.Path, "w"))) { return 0; } + fwrite(DB.File.Buffer.Location, EntryInsertionStart, 1, DB.File.Handle); + fwrite(CollationBuffers->SearchEntry.Location, N->This->Size, 1, DB.File.Handle); + fwrite(DB.File.Buffer.Location + EntryInsertionEnd, DB.File.Buffer.Size - EntryInsertionEnd, 1, DB.File.Handle); + fclose(DB.File.Handle); + + if(N->This->Size == EntryInsertionEnd - EntryInsertionStart) + { + DB.File.Buffer.Ptr = DB.File.Buffer.Location + EntryInsertionStart; + CopyBufferSized(&DB.File.Buffer, &CollationBuffers->SearchEntry, N->This->Size); + } + else + { + FreeBuffer(&DB.File.Buffer); + ReadFileIntoBuffer(&DB.File); + } + } + } + else + { + ++N->Project->EntryCount; + + char *Ptr = (char*)N->Project; + Ptr += sizeof(*N->Project) + sizeof(db_entry) * N->ThisIndex; + uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location; + + if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { return 0; } + fwrite(DB.Metadata.File.Buffer.Location, BytesIntoFile, 1, DB.Metadata.File.Handle); + SetFileEditPosition(&DB.Metadata); + + fwrite(&N->WorkingThis, sizeof(N->WorkingThis), 1, DB.Metadata.File.Handle); + AccumulateFileEditSize(&DB.Metadata, sizeof(N->WorkingThis)); + + fwrite(DB.Metadata.File.Buffer.Location + BytesIntoFile, DB.Metadata.File.Buffer.Size - BytesIntoFile, 1, DB.Metadata.File.Handle); + CycleSignpostedFile(&DB.Metadata); + UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); + + char *EntryInDB = DB.Metadata.File.Buffer.Location + BytesIntoFile; + N->This = (db_entry *)EntryInDB; if(!(DB.File.Handle = fopen(DB.File.Path, "w"))) { return 0; } fwrite(DB.File.Buffer.Location, EntryInsertionStart, 1, DB.File.Handle); fwrite(CollationBuffers->SearchEntry.Location, N->This->Size, 1, DB.File.Handle); - fwrite(DB.File.Buffer.Location + EntryInsertionEnd, DB.File.Buffer.Size - EntryInsertionEnd, 1, DB.File.Handle); - fclose(DB.File.Handle); + fwrite(DB.File.Buffer.Location + EntryInsertionStart, DB.File.Buffer.Size - EntryInsertionStart, 1, DB.File.Handle); - if(N->This->Size == EntryInsertionEnd - EntryInsertionStart) - { - DB.File.Buffer.Ptr = DB.File.Buffer.Location + EntryInsertionStart; - CopyBufferSized(&DB.File.Buffer, &CollationBuffers->SearchEntry, N->This->Size); - } - else - { - FreeBuffer(&DB.File.Buffer); - ReadFileIntoBuffer(&DB.File); - } + CycleFile(&DB.File); + } + + string EntryTitle = Wrap0(CollationBuffers->Title); + + if(!VideoIsPrivate || !RecheckingPrivacy) + { + LogEdit(EditType, CurrentProject->Lineage, BaseFilename, &EntryTitle, VideoIsPrivate); + PrintEdit(EditType, CurrentProject->Lineage, BaseFilename, &EntryTitle, VideoIsPrivate, TRUE); + } + + // TODO(matt): Remove VideoIsPrivate in favour of generating a player page in a random location + + if(HMMLToBuffersReturn == RC_SUCCESS) + { + Result = N->This; } } - else - { - ++N->Project->EntryCount; - - char *Ptr = (char*)N->Project; - Ptr += sizeof(*N->Project) + sizeof(db_entry) * N->ThisIndex; - uint64_t BytesIntoFile = Ptr - DB.Metadata.File.Buffer.Location; - - if(!(DB.Metadata.File.Handle = fopen(DB.Metadata.File.Path, "w"))) { return 0; } - fwrite(DB.Metadata.File.Buffer.Location, BytesIntoFile, 1, DB.Metadata.File.Handle); - SetFileEditPosition(&DB.Metadata); - - fwrite(&N->WorkingThis, sizeof(N->WorkingThis), 1, DB.Metadata.File.Handle); - AccumulateFileEditSize(&DB.Metadata, sizeof(N->WorkingThis)); - - fwrite(DB.Metadata.File.Buffer.Location + BytesIntoFile, DB.Metadata.File.Buffer.Size - BytesIntoFile, 1, DB.Metadata.File.Handle); - CycleSignpostedFile(&DB.Metadata); - UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); - - char *EntryInDB = DB.Metadata.File.Buffer.Location + BytesIntoFile; - N->This = (db_entry *)EntryInDB; - - if(!(DB.File.Handle = fopen(DB.File.Path, "w"))) { return 0; } - fwrite(DB.File.Buffer.Location, EntryInsertionStart, 1, DB.File.Handle); - fwrite(CollationBuffers->SearchEntry.Location, N->This->Size, 1, DB.File.Handle); - fwrite(DB.File.Buffer.Location + EntryInsertionStart, DB.File.Buffer.Size - EntryInsertionStart, 1, DB.File.Handle); - - CycleFile(&DB.File); - } - - if(!VideoIsPrivate) - { -#if 0 - LogError(LOG_NOTICE, "%s %.*s/%.*s - %s", EditTypes[EditType].Name, (int)CurrentProject->Lineage.Length, CurrentProject->Lineage.Base, (int)BaseFilename.Length, BaseFilename.Base, CollationBuffers->Title); - fprintf(stderr, "%s%s%s %.*s/%.*s - %s\n", - ColourStrings[EditTypes[EditType].Colour], EditTypes[EditType].Name, ColourStrings[CS_END], (int)CurrentProject->Lineage.Length, CurrentProject->Lineage.Base, (int)BaseFilename.Length, BaseFilename.Base, CollationBuffers->Title); -#else - LogError(LOG_NOTICE, "%s %.*s/%.*s - %s", EditTypes[EditType].Name, (int)CurrentProject->Lineage.Length, CurrentProject->Lineage.Base, (int)BaseFilename.Length, BaseFilename.Base, CollationBuffers->Title); - - fprintf(stderr, "%s%s%s ", ColourStrings[EditTypes[EditType].Colour], EditTypes[EditType].Name, ColourStrings[CS_END]); - PrintLineageAndEntry(CurrentProject->Lineage, BaseFilename, Wrap0(CollationBuffers->Title), TRUE); -#endif - } - else if(!RecheckingPrivacy) - { - LogError(LOG_NOTICE, "Privately %s %.*s/%.*s", EditTypes[EditType].Name, (int)CurrentProject->Lineage.Length, CurrentProject->Lineage.Base, (int)BaseFilename.Length, BaseFilename.Base); - fprintf(stderr, "%sPrivately %s%s ", ColourStrings[CS_PRIVATE], EditTypes[EditType].Name, ColourStrings[CS_END]); - PrintLineageAndEntry(CurrentProject->Lineage, BaseFilename, Wrap0(CollationBuffers->Title), TRUE); - } - - // TODO(matt): Remove VideoIsPrivate in favour of generating a player page in a random location - MEM_TEST_AFTER("InsertIntoDB()"); - return VideoIsPrivate ? 0 : N->This; + MEM_TEST_END("InsertIntoDB()"); + return Result; } void @@ -11448,6 +11741,7 @@ OffsetAssociatedIndices(asset_index_and_location *AssetDeletionRecords, int Asse void DeleteStaleAssets(void) { + MEM_TEST_TOP("DeleteStaleAssets"); int AssetDeletionCount = 0; DB.Metadata.Signposts.AssetsBlock.Ptr = LocateBlock(B_ASET); db_block_assets *AssetsBlock = DB.Metadata.Signposts.AssetsBlock.Ptr; @@ -11489,6 +11783,7 @@ DeleteStaleAssets(void) WriteFromByteToEnd(&DB.Metadata.File, BytesIntoFile); CycleSignpostedFile(&DB.Metadata); } + MEM_TEST_END("DeleteStaleAssets"); } void @@ -11620,7 +11915,7 @@ AddLandmarks(neighbourhood *N, project *P, edit_type_id EditType) */ if(!AssetInMemory->OffsetLandmarks)// && Assets.Asset[i].PlayerLandmarkCount > 0) { - Asset->LandmarkCount += AssetInMemory->PlayerLandmarkCount; + Asset->LandmarkCount += AssetInMemory->Player.ItemCount; landmark_range ThisTarget = ProjectRange; if(ProjectRange.Length > 0) { @@ -11636,12 +11931,13 @@ AddLandmarks(neighbourhood *N, project *P, edit_type_id EditType) ProcessPrevLandmarks(N, Asset, ProjectRange, ThisTarget, &RunningIndex); } - for(int j = 0; j < AssetInMemory->PlayerLandmarkCount; ++j) + for(int j = 0; j < AssetInMemory->Player.ItemCount; ++j) { + landmark *ThisLandmark = GetPlaceInBook(&AssetInMemory->Player, j); db_landmark Landmark = {}; Landmark.Project = P->Index; Landmark.EntryIndex = N->ThisIndex; - Landmark.Position = AssetInMemory->Player[j].Offset; + Landmark.Position = ThisLandmark->Offset; fwrite(&Landmark, sizeof(Landmark), 1, DB.Metadata.File.Handle); } @@ -11673,7 +11969,7 @@ AddLandmarks(neighbourhood *N, project *P, edit_type_id EditType) for(int i = 0; i < Assets.ItemCount; ++i) { asset *This = GetPlaceInBook(&Assets, i); - if(!This->Known && This->PlayerLandmarkCount > 0) + if(!This->Known && This->Player.ItemCount > 0) { UpdateAssetInDB(This); UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); @@ -11744,7 +12040,7 @@ DeleteLandmarksForSearch(db_project_index ProjectIndex) void VerifyLandmarks_(neighbourhood *N, int LineNumber) { - fprintf(stderr, "%sVerifyLandmarks(%u)%s\n", ColourStrings[CS_MAGENTA], LineNumber, ColourStrings[CS_END]); + PrintLinedFunctionName(LineNumber, "VerifyLandmarks()"); db_block_assets *Block = LocateBlock(B_ASET); db_asset *Asset = LocateFirstAsset(Block); @@ -11932,7 +12228,7 @@ UpdateLandmarksForSearch(neighbourhood *N, db_project_index ProjectIndex) landmark_range Target = BinarySearchForMetadataLandmark(Asset, ProjectRange, SP_SEARCH); db_asset NewAsset = *Asset; - NewAsset.LandmarkCount += This->SearchLandmarkCount - Target.Length; + NewAsset.LandmarkCount += This->Search.ItemCount - Target.Length; fwrite(&NewAsset, sizeof(db_asset), 1, DB.Metadata.File.Handle); //AccumulateFileEditSize(&DB.Metadata, sizeof(db_asset)); @@ -11941,18 +12237,19 @@ UpdateLandmarksForSearch(neighbourhood *N, db_project_index ProjectIndex) //AccumulateFileEditSize(&DB.Metadata, sizeof(db_landmark) * Target.First); uint64_t ToWrite = ExistingLandmarkCount - Target.First; - for(int j = 0; j < This->SearchLandmarkCount; ++j) + for(int j = 0; j < This->Search.ItemCount; ++j) { + landmark *ThisLandmark = GetPlaceInBook(&This->Search, j); db_landmark Landmark = {}; Landmark.Project = ProjectIndex; Landmark.EntryIndex = SP_SEARCH; - Landmark.Position = This->Search[j].Offset; + Landmark.Position = ThisLandmark->Offset; fwrite(&Landmark, sizeof(Landmark), 1, DB.Metadata.File.Handle); //AccumulateFileEditSize(&DB.Metadata, sizeof(db_landmark)); } db_landmark *TrailingLandmark = FirstLandmark + Target.First + Target.Length; - AccumulateFileEditSize(&DB.Metadata, sizeof(db_landmark) * (This->SearchLandmarkCount - Target.Length)); + AccumulateFileEditSize(&DB.Metadata, sizeof(db_landmark) * (This->Search.ItemCount - Target.Length)); ToWrite -= Target.Length; fwrite(TrailingLandmark, sizeof(db_landmark), ToWrite, DB.Metadata.File.Handle); break; @@ -11968,7 +12265,7 @@ UpdateLandmarksForSearch(neighbourhood *N, db_project_index ProjectIndex) for(int InternalAssetIndex = 0; InternalAssetIndex < Assets.ItemCount; ++InternalAssetIndex) { asset *This = GetPlaceInBook(&Assets, InternalAssetIndex); - if(!This->Known && This->SearchLandmarkCount > 0) + if(!This->Known && This->Search.ItemCount > 0) { NewAsset = TRUE; UpdateAssetInDB(This); @@ -11991,131 +12288,129 @@ enum LINK_BACKWARDS } link_directions; -int +rc InsertNeighbourLink(db_header_project *P, db_entry *From, db_entry *To, enum8(link_directions) LinkDirection, bool FromHasOneNeighbour) { - MEM_TEST_INITIAL(); - file HTML = {}; - ReadPlayerPageIntoBuffer(&HTML, Wrap0i(P->BaseDir, sizeof(P->BaseDir)), Wrap0i(P->PlayerLocation, sizeof(P->PlayerLocation)), Wrap0i(From->OutputLocation, sizeof(From->OutputLocation))); - - MEM_TEST_MID("InsertNeighbourLink1"); + rc Result = RC_SUCCESS; + MEM_TEST_TOP("InsertNeighbourLink()"); file HTML = {}; ReadPlayerPageIntoBuffer(&HTML, Wrap0i(P->BaseDir, sizeof(P->BaseDir)), Wrap0i(P->PlayerLocation, sizeof(P->PlayerLocation)), Wrap0i(From->OutputLocation, sizeof(From->OutputLocation))); if(HTML.Buffer.Location) { - if(!(HTML.Handle = fopen(HTML.Path, "w"))) { FreeFile(&HTML); return RC_ERROR_FILE; }; + if(!(HTML.Handle = fopen(HTML.Path, "w"))) { Result = RC_ERROR_FILE; }; - buffer Link = {}; - ClaimBuffer(&Link, BID_LINK, Kilobytes(4)); - - buffer ToPlayerURL = {}; - if(To) + if(Result == RC_SUCCESS) { - ClaimBuffer(&ToPlayerURL, BID_TO_PLAYER_URL, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); - ConstructPlayerURL(&ToPlayerURL, P, Wrap0i(To->OutputLocation, sizeof(To->OutputLocation))); - } + buffer Link = {}; + ClaimBuffer(&Link, BID_LINK, Kilobytes(4)); - int NewPrevEnd = 0; - int NewNextEnd = 0; - switch(LinkDirection) - { - case LINK_BACKWARDS: - { - fwrite(HTML.Buffer.Location, From->LinkOffsets.PrevStart, 1, HTML.Handle); - if(To) + buffer ToPlayerURL = {}; + if(To) + { + ClaimBuffer(&ToPlayerURL, BID_TO_PLAYER_URL, MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_BASE_FILENAME_LENGTH); + ConstructPlayerURL(&ToPlayerURL, P, Wrap0i(To->OutputLocation, sizeof(To->OutputLocation))); + } + + int NewPrevEnd = 0; + int NewNextEnd = 0; + switch(LinkDirection) + { + case LINK_BACKWARDS: { - CopyStringToBuffer(&Link, - "
Previous: '%s'
\n", - ToPlayerURL.Location, - To->Title); - } - else + fwrite(HTML.Buffer.Location, From->LinkOffsets.PrevStart, 1, HTML.Handle); + if(To) + { + CopyStringToBuffer(&Link, + "
Previous: '%s'
\n", + ToPlayerURL.Location, + To->Title); + } + else + { + // TODO(matt): Be careful of this! Is this CurrentProject->Title definitely set to the right thing? + CopyStringToBuffer(&Link, + "
Welcome to %.*s
\n", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); + } + NewPrevEnd = Link.Ptr - Link.Location; + fwrite(Link.Location, (Link.Ptr - Link.Location), 1, HTML.Handle); + if(FromHasOneNeighbour) + { + fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, From->LinkOffsets.NextStart, 1, HTML.Handle); + RewindBuffer(&Link); + CopyStringToBuffer(&Link, + "
You have arrived at the (current) end of %.*s
\n", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); + NewNextEnd = Link.Ptr - Link.Location; + fwrite(Link.Location, NewNextEnd, 1, HTML.Handle); + fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd, + HTML.Buffer.Size - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd), + 1, + HTML.Handle); + } + else + { + fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, + HTML.Buffer.Size - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd), + 1, + HTML.Handle); + } + + From->LinkOffsets.PrevEnd = NewPrevEnd; + if(FromHasOneNeighbour) { From->LinkOffsets.NextEnd = NewNextEnd; } + } break; + case LINK_FORWARDS: { - // TODO(matt): Be careful of this! Is this CurrentProject->Title definitely set to the right thing? - CopyStringToBuffer(&Link, - "
Welcome to %.*s
\n", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); - } - NewPrevEnd = Link.Ptr - Link.Location; - fwrite(Link.Location, (Link.Ptr - Link.Location), 1, HTML.Handle); - if(FromHasOneNeighbour) - { - fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, From->LinkOffsets.NextStart, 1, HTML.Handle); - RewindBuffer(&Link); - CopyStringToBuffer(&Link, - "
You have arrived at the (current) end of %.*s
\n", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); + if(FromHasOneNeighbour) + { + fwrite(HTML.Buffer.Location, From->LinkOffsets.PrevStart, 1, HTML.Handle); + CopyStringToBuffer(&Link, + "
Welcome to %.*s
\n", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); + NewPrevEnd = Link.Ptr - Link.Location; + fwrite(Link.Location, NewPrevEnd, 1, HTML.Handle); + RewindBuffer(&Link); + fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, + From->LinkOffsets.NextStart, 1, HTML.Handle); + } + else + { + fwrite(HTML.Buffer.Location, From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart, 1, HTML.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", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); + } NewNextEnd = Link.Ptr - Link.Location; - fwrite(Link.Location, NewNextEnd, 1, HTML.Handle); + fwrite(Link.Location, (Link.Ptr - Link.Location), 1, HTML.Handle); + fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd, HTML.Buffer.Size - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd), 1, HTML.Handle); - } - else - { - fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, - HTML.Buffer.Size - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd), - 1, - HTML.Handle); - } - From->LinkOffsets.PrevEnd = NewPrevEnd; - if(FromHasOneNeighbour) { From->LinkOffsets.NextEnd = NewNextEnd; } - } break; - case LINK_FORWARDS: - { - if(FromHasOneNeighbour) - { - fwrite(HTML.Buffer.Location, From->LinkOffsets.PrevStart, 1, HTML.Handle); - CopyStringToBuffer(&Link, - "
Welcome to %.*s
\n", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); - NewPrevEnd = Link.Ptr - Link.Location; - fwrite(Link.Location, NewPrevEnd, 1, HTML.Handle); - RewindBuffer(&Link); - fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd, - From->LinkOffsets.NextStart, 1, HTML.Handle); - } - else - { - fwrite(HTML.Buffer.Location, From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart, 1, HTML.Handle); - } + if(FromHasOneNeighbour) { From->LinkOffsets.PrevEnd = NewPrevEnd; } + From->LinkOffsets.NextEnd = NewNextEnd; + } break; + } - if(To) - { - CopyStringToBuffer(&Link, - "
Next: '%s'
\n", - ToPlayerURL.Location, - To->Title); - } - else - { - CopyStringToBuffer(&Link, - "
You have arrived at the (current) end of %.*s
\n", (int)CurrentProject->Title.Length, CurrentProject->Title.Base); - } - NewNextEnd = Link.Ptr - Link.Location; - fwrite(Link.Location, (Link.Ptr - Link.Location), 1, HTML.Handle); - - fwrite(HTML.Buffer.Location + From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd, - HTML.Buffer.Size - (From->LinkOffsets.PrevStart + From->LinkOffsets.PrevEnd + From->LinkOffsets.NextStart + From->LinkOffsets.NextEnd), - 1, - HTML.Handle); - - if(FromHasOneNeighbour) { From->LinkOffsets.PrevEnd = NewPrevEnd; } - From->LinkOffsets.NextEnd = NewNextEnd; - } break; + if(To) { DeclaimBuffer(&ToPlayerURL); } + DeclaimBuffer(&Link); + fclose(HTML.Handle); + HTML.Handle = 0; } - - if(To) { DeclaimBuffer(&ToPlayerURL); } - DeclaimBuffer(&Link); - fclose(HTML.Handle); - HTML.Handle = 0; - FreeFile(&HTML); - MEM_TEST_AFTER("InsertNeighbourLink()"); - return RC_SUCCESS; } else { - FreeFile(&HTML); - MEM_TEST_AFTER("InsertNeighbourLink()"); - return RC_ERROR_FILE; + Result = RC_ERROR_FILE; } + FreeFile(&HTML); + MEM_TEST_END("InsertNeighbourLink()"); + return Result; } int @@ -12177,7 +12472,7 @@ DeleteNeighbourLinks(neighbourhood *N) void LinkToNewEntry(neighbourhood *N) { - MEM_TEST_INITIAL(); + MEM_TEST_TOP("LinkToNewEntry()"); N->PreLinkThisOffsetTotal = N->This->LinkOffsets.PrevEnd + N->This->LinkOffsets.NextStart + N->This->LinkOffsets.NextEnd; @@ -12188,9 +12483,7 @@ LinkToNewEntry(neighbourhood *N) + N->Prev->LinkOffsets.NextStart + N->Prev->LinkOffsets.NextEnd; - MEM_TEST_MID("LinkToNewEntry1"); InsertNeighbourLink(N->Project, N->Prev, N->This, LINK_FORWARDS, N->FormerIsFirst); - MEM_TEST_MID("LinkToNewEntry2"); N->PrevOffsetModifier = N->Prev->LinkOffsets.PrevEnd + N->Prev->LinkOffsets.NextStart @@ -12204,16 +12497,14 @@ LinkToNewEntry(neighbourhood *N) + N->Next->LinkOffsets.NextStart + N->Next->LinkOffsets.NextEnd; - MEM_TEST_MID("LinkToNewEntry3"); InsertNeighbourLink(N->Project, N->Next, N->This, LINK_BACKWARDS, N->LatterIsFinal); - MEM_TEST_MID("LinkToNewEntry4"); N->NextOffsetModifier = N->Next->LinkOffsets.PrevEnd + N->Next->LinkOffsets.NextStart + N->Next->LinkOffsets.NextEnd - N->PreLinkNextOffsetTotal; } - MEM_TEST_AFTER("LinkToNewEntry()"); + MEM_TEST_END("LinkToNewEntry()"); } void @@ -12476,8 +12767,7 @@ DeleteFromDB(neighbourhood *N, string BaseFilename) } LogError(LOG_INFORMATIONAL, "Deleted %.*s/%.*s", (int)CurrentProject->Lineage.Length, CurrentProject->Lineage.Base, (int)BaseFilename.Length, BaseFilename.Base); - fprintf(stderr, "%s%s%s ", ColourStrings[EditTypes[EDIT_DELETION].Colour], EditTypes[EDIT_DELETION].Name, ColourStrings[CS_END]); - PrintLineageAndEntryID(CurrentProject->Lineage, BaseFilename, TRUE); + PrintEdit(EDIT_DELETION, CurrentProject->Lineage, BaseFilename, 0, FALSE, TRUE); } return Entry ? RC_SUCCESS : RC_NOOP; @@ -12725,8 +13015,7 @@ PushUniqueString(memory_book *UniqueThemes, string *GlobalTheme) void GenerateThemeLinks(buffer *IncludesSearch, project *P) { - memory_book UniqueThemes = {}; - InitBookOfPointers(&UniqueThemes, 4, MBT_STRING_PTR); + memory_book UniqueThemes = InitBookOfPointers(MBT_STRING_PTR, 4); if(P) { PushUniqueStringsRecursively(&UniqueThemes, P); @@ -13026,12 +13315,10 @@ SearchToBuffer(buffers *CollationBuffers, db_header_project *StoredP, project *P int GeneratePlayerPage(neighbourhood *N, buffers *CollationBuffers, template *PlayerTemplate, string OutputLocation, bool Reinserting) { - MEM_TEST_INITIAL(); - MEM_TEST_MID("GeneratePlayerPage1"); + MEM_TEST_TOP("GeneratePlayerPage()"); string BaseDir = Wrap0i(N->Project->BaseDir, sizeof(N->Project->BaseDir)); string PlayerLocation = Wrap0i(N->Project->PlayerLocation, sizeof(N->Project->PlayerLocation)); char *PlayerPath = ConstructDirectoryPath(&BaseDir, &PlayerLocation, &OutputLocation); - MEM_TEST_MID("GeneratePlayerPage2"); DIR *OutputDirectoryHandle; if(!(OutputDirectoryHandle = opendir(PlayerPath))) // TODO(matt): open() @@ -13046,7 +13333,6 @@ GeneratePlayerPage(neighbourhood *N, buffers *CollationBuffers, template *Player closedir(OutputDirectoryHandle); ExtendString0(&PlayerPath, Wrap0("/index.html")); - MEM_TEST_MID("GeneratePlayerPage3"); bool SearchInTemplate = FALSE; for(int TagIndex = 0; TagIndex < PlayerTemplate->Metadata.Tags.ItemCount; ++TagIndex) @@ -13061,30 +13347,27 @@ GeneratePlayerPage(neighbourhood *N, buffers *CollationBuffers, template *Player break; } } - MEM_TEST_MID("GeneratePlayerPage4"); - - BuffersToHTML(0, CurrentProject, CollationBuffers, PlayerTemplate, PlayerPath, PAGE_PLAYER, &N->This->LinkOffsets.PrevStart); - MEM_TEST_MID("GeneratePlayerPage5"); + /* */ MEM_TEST_MID("GeneratePlayerPage()"); + /* +MEM */ BuffersToHTML(0, CurrentProject, CollationBuffers, PlayerTemplate, PlayerPath, PAGE_PLAYER, &N->This->LinkOffsets.PrevStart); + /* */ MEM_TEST_MID("GeneratePlayerPage()"); Free(PlayerPath); // NOTE(matt): A previous InsertNeighbourLink() call will have done SnipeEntryIntoMetadataBuffer(), but we must do it here now // that PrevStart has been adjusted by BuffersToHTML() UpdateLandmarksForNeighbourhood(N, Reinserting ? EDIT_REINSERTION : EDIT_ADDITION); - MEM_TEST_MID("GeneratePlayerPage6"); - MEM_TEST_MID("GeneratePlayerPage7"); if(SearchInTemplate) { FreeBuffer(&CollationBuffers->Search); } - MEM_TEST_MID("GeneratePlayerPage8"); ResetAssetLandmarks(); - MEM_TEST_AFTER("GeneratePlayerPage()"); + MEM_TEST_END("GeneratePlayerPage()"); return RC_SUCCESS; } rc GenerateSearchPage(neighbourhood *N, buffers *CollationBuffers, db_header_project *StoredP, project *P) { + MEM_TEST_TOP("GenerateSearchPage()"); string BaseDir = Wrap0i(N->Project->BaseDir, sizeof(N->Project->BaseDir)); string SearchLocation = Wrap0i(N->Project->SearchLocation, sizeof(N->Project->SearchLocation)); char *SearchPath = ConstructDirectoryPath(&BaseDir, &SearchLocation, 0); @@ -13123,12 +13406,14 @@ GenerateSearchPage(neighbourhood *N, buffers *CollationBuffers, db_header_projec Free(SearchPath); FreeBuffer(&CollationBuffers->Search); ResetAssetLandmarks(); + MEM_TEST_END("GenerateSearchPage()"); return RC_SUCCESS; } rc GenerateGlobalSearchPage(neighbourhood *N, buffers *CollationBuffers) { + MEM_TEST_TOP("GenerateGlobalSearchPage()"); db_block_projects *ProjectsBlock = DB.Metadata.Signposts.ProjectsBlock.Ptr; string SearchLocationL = Wrap0i(ProjectsBlock->GlobalSearchDir, sizeof(ProjectsBlock->GlobalSearchDir)); char *SearchPath = MakeString0("l", &SearchLocationL); @@ -13150,7 +13435,9 @@ GenerateGlobalSearchPage(neighbourhood *N, buffers *CollationBuffers) { case RC_SUCCESS: { - BuffersToHTML(Config, 0, CollationBuffers, &Config->SearchTemplate, SearchPath, PAGE_SEARCH, 0); + /* */ MEM_TEST_MID("GenerateGlobalSearchPage()"); + /* +MEM */ BuffersToHTML(Config, 0, CollationBuffers, &Config->SearchTemplate, SearchPath, PAGE_SEARCH, 0); + /* */ MEM_TEST_MID("GenerateGlobalSearchPage()"); UpdateLandmarksForSearch(N, GLOBAL_SEARCH_PAGE_INDEX); break; } @@ -13168,12 +13455,14 @@ GenerateGlobalSearchPage(neighbourhood *N, buffers *CollationBuffers) Free(SearchPath); FreeBuffer(&CollationBuffers->Search); ResetAssetLandmarks(); + MEM_TEST_END("GenerateGlobalSearchPage()"); return RC_SUCCESS; } int GenerateSearchPages(neighbourhood *N, buffers *CollationBuffers) { + MEM_TEST_TOP("GenerateSearchPages()"); // TODO(matt): Ascend through all the ancestry, generating search pages PrintFunctionName("GenerateSearchPage()"); project *This = CurrentProject; @@ -13194,10 +13483,13 @@ GenerateSearchPages(neighbourhood *N, buffers *CollationBuffers) { // TODO(matt): Fully determine whether UpdateNeighbourhoodPointers() must happen here UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); - GenerateGlobalSearchPage(N, CollationBuffers); + /* */ MEM_TEST_MID("GenerateSearchPages()"); + /* +MEM */ GenerateGlobalSearchPage(N, CollationBuffers); + /* */ MEM_TEST_MID("GenerateSearchPages()"); } //sleep(1); + MEM_TEST_END("GenerateSearchPages()"); return RC_SUCCESS; } @@ -13223,38 +13515,38 @@ DeleteEntry(neighbourhood *N, string BaseFilename) return RC_NOOP; } -int +rc InsertEntry(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, string BaseFilename, bool RecheckingPrivacy) { - MEM_TEST_INITIAL(); + rc Result = RC_SUCCESS; + MEM_TEST_TOP("InsertEntry()"); bool Reinserting = FALSE; - MEM_TEST_MID("InsertEntry0"); - db_entry *Entry = InsertIntoDB(N, CollationBuffers, BespokeTemplate, BaseFilename, RecheckingPrivacy, &Reinserting); - MEM_TEST_MID("InsertEntry1"); + /* */ MEM_TEST_MID("InsertEntry()"); + /* +MEM */ db_entry *Entry = InsertIntoDB(N, CollationBuffers, BespokeTemplate, BaseFilename, RecheckingPrivacy, &Reinserting); + /* */ MEM_TEST_MID("InsertEntry()"); if(Entry) { - MEM_TEST_MID("InsertEntry2"); LinkNeighbours(N, LINK_INCLUDE); - MEM_TEST_MID("InsertEntry3"); if(BespokeTemplate->File.Buffer.Location) { - MEM_TEST_MID("InsertEntry4"); - GeneratePlayerPage(N, CollationBuffers, BespokeTemplate, Wrap0i(Entry->OutputLocation, sizeof(Entry->OutputLocation)), Reinserting); - MEM_TEST_MID("InsertEntry5"); + /* */ MEM_TEST_MID("InsertEntry()"); + /* +MEM (inferred) */ GeneratePlayerPage(N, CollationBuffers, BespokeTemplate, Wrap0i(Entry->OutputLocation, sizeof(Entry->OutputLocation)), Reinserting); + /* */ MEM_TEST_MID("InsertEntry()"); FreeTemplate(BespokeTemplate); - MEM_TEST_MID("InsertEntry6"); } else { - MEM_TEST_MID("InsertEntry7"); - GeneratePlayerPage(N, CollationBuffers, &CurrentProject->PlayerTemplate, Wrap0i(Entry->OutputLocation, sizeof(Entry->OutputLocation)), Reinserting); - MEM_TEST_MID("InsertEntry8"); + /* */ MEM_TEST_MID("InsertEntry()"); + /* +MEM */ GeneratePlayerPage(N, CollationBuffers, &CurrentProject->PlayerTemplate, Wrap0i(Entry->OutputLocation, sizeof(Entry->OutputLocation)), Reinserting); + /* */ MEM_TEST_MID("InsertEntry()"); } - MEM_TEST_AFTER("InsertEntry()"); - return RC_SUCCESS; } - MEM_TEST_AFTER("InsertEntry()"); - return RC_NOOP; + else + { + Result = RC_NOOP; + } + MEM_TEST_END("InsertEntry()"); + return Result; } void @@ -13345,8 +13637,6 @@ UpdateDeferredAssetChecksums(void) rc RemoveDirectory(string D) { - // TODO(matt): Change this to operate on a string - // AFD int Result; char *Path = 0; ExtendString0(&Path, D); @@ -13631,10 +13921,11 @@ DeleteDeadDBEntries(neighbourhood *N, bool *Modified) return Deleted ? RC_SUCCESS : RC_NOOP; } -int +rc SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate) { - MEM_TEST_INITIAL(); + rc Result = RC_SUCCESS; + MEM_TEST_TOP("SyncDBWithInput()"); if(DB.Metadata.File.Buffer.Size > 0 && Config->QueryString.Length == 0) { DeleteAllLandmarksAndAssets(); @@ -13679,55 +13970,55 @@ SyncDBWithInput(neighbourhood *N, buffers *CollationBuffers, template *BespokeTe char HMMLDir0[CurrentProject->HMMLDir.Length + 1]; CopyStringNoFormat(HMMLDir0, sizeof(HMMLDir0), CurrentProject->HMMLDir); DIR *HMMLDirHandle = opendir(HMMLDir0); - if(!HMMLDirHandle) + if(HMMLDirHandle) { - LogError(LOG_ERROR, "Unable to scan project directory %.*s: %s", (int)CurrentProject->HMMLDir.Length, CurrentProject->HMMLDir.Base, strerror(errno)); - fprintf(stderr, "Unable to scan project directory %.*s: %s\n", (int)CurrentProject->HMMLDir.Length, CurrentProject->HMMLDir.Base, strerror(errno)); - return RC_ERROR_DIRECTORY; - } - - MEM_TEST_MID("SyncDBWithInput1"); - struct dirent *ProjectFiles; - bool Inserted = FALSE; - while((ProjectFiles = readdir(HMMLDirHandle))) - { - string Filename = Wrap0(ProjectFiles->d_name); - if(ExtensionMatches(Filename, EXT_HMML)) + struct dirent *ProjectFiles; + bool Inserted = FALSE; + while((ProjectFiles = readdir(HMMLDirHandle))) { - ResetNeighbourhood(N); - MEM_TEST_MID("SyncDBWithInput1a"); + string Filename = Wrap0(ProjectFiles->d_name); + if(ExtensionMatches(Filename, EXT_HMML)) + { + ResetNeighbourhood(N); + /* */ MEM_TEST_MID("SyncDBWithInput()"); + /* +MEM */ Inserted |= (InsertEntry(N, CollationBuffers, BespokeTemplate, GetBaseFilename(Filename, EXT_HMML), 0) == RC_SUCCESS); + /* */ MEM_TEST_MID("SyncDBWithInput()"); + VerifyLandmarks(N); + } + } + closedir(HMMLDirHandle); - Inserted |= (InsertEntry(N, CollationBuffers, BespokeTemplate, GetBaseFilename(Filename, EXT_HMML), 0) == RC_SUCCESS); - MEM_TEST_MID("SyncDBWithInput1b"); + UpdateDeferredAssetChecksums(); + + UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); + + if(Deleted || Inserted || Modified) + { + // TODO(matt): The DB may not be set up correctly at this point. Figure out why! + // + // To reproduce: + // 1. Generate it with a blank global_search_dir and global_search_url + // 2. Generate it with a filled global_search_dir and global_search_url + // + // Of note: We seem to be producing the asset landmark for Generation -1 just fine. The problem is that + // we're not getting the index.html file + + /* */ MEM_TEST_MID("SyncDBWithInput()"); + /* +MEM */ GenerateSearchPages(N, CollationBuffers); + /* */ MEM_TEST_MID("SyncDBWithInput()"); + DeleteStaleAssets(); + UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); VerifyLandmarks(N); } } - closedir(HMMLDirHandle); - MEM_TEST_MID("SyncDBWithInput2"); - - - UpdateDeferredAssetChecksums(); - - UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); - - if(Deleted || Inserted || Modified) + else { - // TODO(matt): The DB may not be set up correctly at this point. Figure out why! - // - // To reproduce: - // 1. Generate it with a blank global_search_dir and global_search_url - // 2. Generate it with a filled global_search_dir and global_search_url - // - // Of note: We seem to be producing the asset landmark for Generation -1 just fine. The problem is that - // we're not getting the index.html file - GenerateSearchPages(N, CollationBuffers); - DeleteStaleAssets(); - UpdateNeighbourhoodPointers(N, &DB.Metadata.Signposts); - VerifyLandmarks(N); + LogError(LOG_ERROR, "Unable to scan project directory %.*s: %s", (int)CurrentProject->HMMLDir.Length, CurrentProject->HMMLDir.Base, strerror(errno)); + fprintf(stderr, "Unable to scan project directory %.*s: %s\n", (int)CurrentProject->HMMLDir.Length, CurrentProject->HMMLDir.Base, strerror(errno)); + Result = RC_ERROR_DIRECTORY; } - - MEM_TEST_AFTER("SyncDBWithInput()"); - return RC_SUCCESS; + MEM_TEST_END("SyncDBWithInput()"); + return Result; } void @@ -13762,10 +14053,12 @@ project_generations CopyAccumulator(project_generations *G) { project_generations Result = {}; - for(int i = 0; i < G->Count; ++i) + Result.EntriesInGeneration = InitBook(MBT_UINT32, 4); + for(int i = 0; i < G->EntriesInGeneration.ItemCount; ++i) { - PushGeneration(&Result); - Result.EntriesInGeneration[i] = G->EntriesInGeneration[i]; + uint32_t *Src = GetPlaceInBook(&G->EntriesInGeneration, i); + uint32_t *Dest = MakeSpaceInBook(&Result.EntriesInGeneration); + *Dest = *Src; } return Result; } @@ -13774,9 +14067,10 @@ project_generations InitAccumulator(project_generations *G) { project_generations Result = {}; + Result.EntriesInGeneration = InitBook(MBT_UINT32, 4); for(int i = 0; i < G->CurrentGeneration; ++i) { - PushGeneration(&Result); + MakeSpaceInBook(&Result.EntriesInGeneration); ++Result.CurrentGeneration; } return Result; @@ -13863,7 +14157,7 @@ InitDB(void) } else { - fprintf(stderr, "%sMalformed metadata file%s: %s\n", + fprintf(stderr, "%sMalformed database%s: %s\n", ColourStrings[CS_ERROR], ColourStrings[CS_END], DB.Metadata.File.Path); } } @@ -14058,13 +14352,18 @@ OffsetAssetLandmarks(db_asset *A, uint64_t *BytesThroughFile, project_generation for(; i < A->LandmarkCount; ++i, ++Landmark) { db_landmark NewLandmark = *Landmark; - uint32_t OldEntriesInGeneration = OldAcc->EntriesInGeneration ? OldAcc->EntriesInGeneration[NewLandmark.Project.Generation] : 0; + uint32_t OldEntriesInGeneration = 0 ; + if(OldAcc->EntriesInGeneration.ItemCount > 0) + { + OldEntriesInGeneration = *(uint32_t *)GetPlaceInBook(&OldAcc->EntriesInGeneration, NewLandmark.Project.Generation); + } + if(NewLandmark.Project.Index >= OldEntriesInGeneration) { NewLandmark.Project.Index += - (NewLandmark.Project.Generation < NewAcc->Count ? NewAcc->EntriesInGeneration[NewLandmark.Project.Generation] : 0) + (NewLandmark.Project.Generation < NewAcc->EntriesInGeneration.ItemCount ? *(uint32_t *)GetPlaceInBook(&NewAcc->EntriesInGeneration, NewLandmark.Project.Generation) : 0) - - (NewLandmark.Project.Generation < OldAcc->Count ? OldEntriesInGeneration : 0); + (NewLandmark.Project.Generation < OldAcc->EntriesInGeneration.ItemCount ? OldEntriesInGeneration : 0); } fwrite(&NewLandmark, sizeof(db_landmark), 1, DB.Metadata.File.Handle); *BytesThroughFile += sizeof(db_landmark); @@ -14113,6 +14412,7 @@ InsertProjectIntoDB_TopLevel(project_generations *G, db_block_projects **Block, uint64_t BlockPos = (char *)*Block - DB.Metadata.File.Buffer.Location; *Child = InitProjectInDBPostamble(&OldG, G); *Block = (db_block_projects *)(DB.Metadata.File.Buffer.Location + BlockPos); + FreeBook(&OldG.EntriesInGeneration); } void @@ -14136,6 +14436,7 @@ InsertProjectIntoDB(project_generations *G, db_header_project **Parent, db_heade uint64_t PPos = (char *)*Parent - DB.Metadata.File.Buffer.Location; *Child = InitProjectInDBPostamble(&OldG, G); *Parent = (db_header_project *)(DB.Metadata.File.Buffer.Location + PPos); + FreeBook(&OldG.EntriesInGeneration); } bool @@ -14152,16 +14453,17 @@ DeleteLandmarksOfProjectAndChildren(db_asset *A, uint64_t *BytesThroughBuffer, p //LandmarkDeletionCount += LandmarkRange.Length; uint64_t LandmarkDeletionCount = 0; - for(int i = LocalAcc->CurrentGeneration; i < LocalAcc->Count; ++i) + for(int i = LocalAcc->CurrentGeneration; i < LocalAcc->EntriesInGeneration.ItemCount; ++i) { - for(int j = 0; j < LocalAcc->EntriesInGeneration[i]; ++j) + uint32_t *This = GetPlaceInBook(&LocalAcc->EntriesInGeneration, i); + for(int j = 0; j < *This; ++j) { db_project_index Acc = {}; Acc.Generation = i; Acc.Index = j; - if(i < MainAcc->Count) + if(i < MainAcc->EntriesInGeneration.ItemCount) { - Acc.Index += MainAcc->EntriesInGeneration[i]; + Acc.Index += *(uint32_t *)GetPlaceInBook(&MainAcc->EntriesInGeneration, i); } landmark_range LandmarkRange = DetermineProjectLandmarksRange(A, Acc); LandmarkDeletionCount += LandmarkRange.Length; @@ -14185,19 +14487,20 @@ DeleteLandmarksOfProjectAndChildren(db_asset *A, uint64_t *BytesThroughBuffer, p db_landmark *FirstLandmark = LocateFirstLandmark(A); uint64_t LandmarkIndex = 0; - for(int GenIndex = LocalAcc->CurrentGeneration; GenIndex < LocalAcc->Count; ++GenIndex) + for(int GenIndex = LocalAcc->CurrentGeneration; GenIndex < LocalAcc->EntriesInGeneration.ItemCount; ++GenIndex) { uint64_t GenLandmarkDeletionCount = 0; db_landmark *GenLandmark = 0; //uint64_t LandmarkIndex = 0; - for(int j = 0; j < LocalAcc->EntriesInGeneration[GenIndex]; ++j) + uint32_t *This = GetPlaceInBook(&LocalAcc->EntriesInGeneration, GenIndex); + for(int j = 0; j < *This; ++j) { db_project_index Acc = {}; Acc.Generation = GenIndex; Acc.Index = j; - if(GenIndex < MainAcc->Count) + if(GenIndex < MainAcc->EntriesInGeneration.ItemCount) { - Acc.Index += MainAcc->EntriesInGeneration[GenIndex]; + Acc.Index += *(uint32_t *)GetPlaceInBook(&MainAcc->EntriesInGeneration, GenIndex); } landmark_range LandmarkRange = DetermineProjectLandmarksRange(A, Acc); GenLandmarkDeletionCount += LandmarkRange.Length; @@ -14226,9 +14529,11 @@ DeleteLandmarksOfProjectAndChildren(db_asset *A, uint64_t *BytesThroughBuffer, p // NOTE(matt): These are all unsigned values. If the subtraction would yield a negative value, it actually ends up // being positive because the high bit is set. Casting them to a larger type removes the possibility // for under / overflow, and treating them as signed permits signed comparison - if((int64_t)NewLandmark.Project.Index - (int64_t)LocalAcc->EntriesInGeneration[GenIndex] >= (int64_t)MainAcc->EntriesInGeneration[GenIndex]) + uint32_t LocalEntries = *(uint32_t *)GetPlaceInBook(&LocalAcc->EntriesInGeneration, GenIndex); + uint32_t MainEntries = *(uint32_t *)GetPlaceInBook(&MainAcc->EntriesInGeneration, GenIndex); + if((int64_t)NewLandmark.Project.Index - (int64_t)LocalEntries >= (int64_t)MainEntries) { - NewLandmark.Project.Index -= LocalAcc->EntriesInGeneration[GenIndex]; + NewLandmark.Project.Index -= *(uint32_t *)GetPlaceInBook(&LocalAcc->EntriesInGeneration, GenIndex); } fwrite(&NewLandmark, sizeof(db_landmark), 1, DB.Metadata.File.Handle); *BytesThroughBuffer += sizeof(db_landmark); @@ -14302,6 +14607,7 @@ DeleteProjectInterior(db_header_project **Child, project_generations *G, uint64_ *BytesThroughBuffer += WriteFromByteToEnd(&DB.Metadata.File, *BytesThroughBuffer); CycleSignpostedFile(&DB.Metadata); + FreeBook(&ChildAccumulator.EntriesInGeneration); } void @@ -14372,13 +14678,13 @@ DetermineFirstLandmarkAndRangeOfProjectHierarchy(db_asset *A, uint32_t GenIndex, landmark_range *Dest) { db_landmark *Result = 0; - for(int j = 0; j < ThisAcc->EntriesInGeneration[GenIndex]; ++j) + for(int j = 0; j < *(uint32_t *)GetPlaceInBook(&ThisAcc->EntriesInGeneration, GenIndex); ++j) { db_project_index ThisIndex = {}; ThisIndex.Generation = GenIndex; ThisIndex.Index = j; - if(MainAcc && GenIndex < MainAcc->Count) { ThisIndex.Index += MainAcc->EntriesInGeneration[GenIndex]; } - if(PrevAcc && GenIndex < PrevAcc->Count) { ThisIndex.Index += PrevAcc->EntriesInGeneration[GenIndex]; } + if(MainAcc && GenIndex < MainAcc->EntriesInGeneration.ItemCount) { ThisIndex.Index += *(uint32_t *)GetPlaceInBook(&MainAcc->EntriesInGeneration, GenIndex); } + if(PrevAcc && GenIndex < PrevAcc->EntriesInGeneration.ItemCount) { ThisIndex.Index += *(uint32_t *)GetPlaceInBook(&PrevAcc->EntriesInGeneration, GenIndex); } landmark_range LandmarkRange = DetermineProjectLandmarksRange(A, ThisIndex); if(!Result && LandmarkRange.Length > 0) { @@ -14427,7 +14733,7 @@ ReorganiseProjectsInterior(project_generations *G, project *CChild, uint64_t Chi uint64_t RunningLandmarkIndex = 0; db_landmark *FirstLandmark = LocateFirstLandmark(Asset); WriteFromByteToPointer(&DB.Metadata.File, &Byte, FirstLandmark); - for(uint64_t GenIndex = 0; GenIndex < ThisAcc.Count; ++GenIndex) + for(uint64_t GenIndex = 0; GenIndex < ThisAcc.EntriesInGeneration.ItemCount; ++GenIndex) { landmark_range ThisRange = {}; db_landmark *ThisLandmark = DetermineFirstLandmarkAndRangeOfProjectHierarchy(Asset, G, &LocalAcc, &ThisAcc, GenIndex, &ThisRange); @@ -14444,9 +14750,9 @@ ReorganiseProjectsInterior(project_generations *G, project *CChild, uint64_t Chi ++ThisLandmarkIndex, ++RunningLandmarkIndex, ++ThisLandmark) { db_landmark NewLandmark = *ThisLandmark; - if(GenIndex < LocalAcc.Count) + if(GenIndex < LocalAcc.EntriesInGeneration.ItemCount) { - NewLandmark.Project.Index -= LocalAcc.EntriesInGeneration[GenIndex]; + NewLandmark.Project.Index -= *(uint32_t*)GetPlaceInBook(&LocalAcc.EntriesInGeneration, GenIndex); } fwrite(&NewLandmark, sizeof(db_landmark), 1, DB.Metadata.File.Handle); Byte += sizeof(db_landmark); @@ -14456,7 +14762,7 @@ ReorganiseProjectsInterior(project_generations *G, project *CChild, uint64_t Chi ++LocalAccLandmarkIndex, ++RunningLandmarkIndex, ++LocalAccLandmark) { db_landmark NewLandmark = *LocalAccLandmark; - NewLandmark.Project.Index += ThisAcc.EntriesInGeneration[GenIndex]; + NewLandmark.Project.Index += *(uint32_t *)GetPlaceInBook(&ThisAcc.EntriesInGeneration, GenIndex); fwrite(&NewLandmark, sizeof(db_landmark), 1, DB.Metadata.File.Handle); Byte += sizeof(db_landmark); } @@ -14483,6 +14789,7 @@ ReorganiseProjectsInterior(project_generations *G, project *CChild, uint64_t Chi } CycleSignpostedFile(&DB.Metadata); + FreeBook(&ThisAcc.EntriesInGeneration); Result = TRUE; break; } @@ -14492,6 +14799,9 @@ ReorganiseProjectsInterior(project_generations *G, project *CChild, uint64_t Chi } This = SkipProjectAndChildren(This); } + + FreeBook(&LocalAcc.EntriesInGeneration); + return Result; } @@ -14657,6 +14967,7 @@ SyncDB(config *C) // DB.Metadata.Signposts.ProjectsBlock.Ptr = LocateBlock(B_PROJ); project_generations Accumulator = {}; + Accumulator.EntriesInGeneration = InitBook(MBT_UINT32, 4); db_block_projects *SParent = DB.Metadata.Signposts.ProjectsBlock.Ptr; @@ -14699,13 +15010,14 @@ SyncDB(config *C) //PrintConfig(C); //PrintGenerations(&Accumulator, FALSE); - FreeGenerations(&Accumulator); + FreeBook(&Accumulator.EntriesInGeneration); //_exit(0); } void SyncGlobalPagesWithInput(neighbourhood *N, buffers *CollationBuffers) { + MEM_TEST_TOP("SyncGlobalPagesWithInput"); db_block_projects *ProjectsBlock = DB.Metadata.Signposts.ProjectsBlock.Ptr; string StoredGlobalSearchDir = Wrap0i(ProjectsBlock->GlobalSearchDir, sizeof(ProjectsBlock->GlobalSearchDir)); string StoredGlobalSearchURL = Wrap0i(ProjectsBlock->GlobalSearchURL, sizeof(ProjectsBlock->GlobalSearchURL)); @@ -14746,6 +15058,7 @@ SyncGlobalPagesWithInput(neighbourhood *N, buffers *CollationBuffers) } // TODO(matt): Come back to this! Free(StoredGlobalSearchDir0); + MEM_TEST_END("SyncGlobalPagesWithInput"); } void @@ -14769,7 +15082,7 @@ void InitMemoryArena(arena *Arena, int Size) { Arena->Size = Size; - if(!(Arena->Location = calloc(1, Arena->Size))) + if(!(Arena->Location = malloc(Arena->Size))) { _exit(RC_ERROR_MEMORY); } @@ -14811,6 +15124,7 @@ PackProjectTemplates(project *P, neighbourhood *N, bool *Titled) case RC_INVALID_TEMPLATE: // Invalid template case RC_SUCCESS: break; + default: break; } } @@ -14832,6 +15146,7 @@ PackProjectTemplates(project *P, neighbourhood *N, bool *Titled) case RC_INVALID_TEMPLATE: // Invalid template case RC_SUCCESS: break; + default: break; } } } @@ -14859,6 +15174,7 @@ PackTemplates(neighbourhood *N) case RC_INVALID_TEMPLATE: // Invalid template case RC_SUCCESS: break; + default: break; } } @@ -14871,9 +15187,12 @@ PackTemplates(neighbourhood *N) void SyncProject(project *P, neighbourhood *N, buffers *CollationBuffers, template *BespokeTemplate, bool *Titled) { + MEM_TEST_TOP("SyncProject()"); for(int i = 0; i < P->Child.ItemCount; ++i) { - SyncProject(GetPlaceInBook(&P->Child, i), N, CollationBuffers, BespokeTemplate, Titled); + /* */ MEM_TEST_MID("SyncProject()"); + /* +MEM */ SyncProject(GetPlaceInBook(&P->Child, i), N, CollationBuffers, BespokeTemplate, Titled); + /* */ MEM_TEST_MID("SyncProject()"); } SetCurrentProject(P, N); @@ -14893,9 +15212,12 @@ SyncProject(project *P, neighbourhood *N, buffers *CollationBuffers, template *B PrintLineage(P->Lineage, FALSE); fprintf(stderr, " ───╼\n"); - SyncDBWithInput(N, CollationBuffers, BespokeTemplate); + /* */ MEM_TEST_MID("SyncProject()"); + /* +MEM */ SyncDBWithInput(N, CollationBuffers, BespokeTemplate); + /* */ MEM_TEST_MID("SyncProject()"); PushWatchHandle(P->HMMLDir, EXT_HMML, WT_HMML, P, 0); + MEM_TEST_END("SyncProject()"); } char *inotifyEventStrings[] = @@ -14974,8 +15296,11 @@ PrintEvent(struct inotify_event *Event, int EventIndex, int Indentation) void InitAll(neighbourhood *Neighbourhood, buffers *CollationBuffers, template *BespokeTemplate) { + MEM_TEST_TOP("InitAll()"); RewindCollationBuffers(CollationBuffers); - InitDB(); + /* */ MEM_TEST_MID("InitAll()"); + /* +MEM */ InitDB(); + /* */ MEM_TEST_MID("InitAll()"); // TODO(matt): Straight up remove these PrintAssetsBlock() calls //PrintAssetsBlock(0); @@ -14998,7 +15323,9 @@ InitAll(neighbourhood *Neighbourhood, buffers *CollationBuffers, template *Bespo bool TitledSync = FALSE; for(int i = 0; i < Config->Project.ItemCount; ++i) { - SyncProject(GetPlaceInBook(&Config->Project, i), Neighbourhood, CollationBuffers, BespokeTemplate, &TitledSync); + /* */ MEM_TEST_MID("InitAll()"); + /* +MEM */ SyncProject(GetPlaceInBook(&Config->Project, i), Neighbourhood, CollationBuffers, BespokeTemplate, &TitledSync); + /* */ MEM_TEST_MID("InitAll()"); } SyncGlobalPagesWithInput(Neighbourhood, CollationBuffers); @@ -15010,7 +15337,11 @@ InitAll(neighbourhood *Neighbourhood, buffers *CollationBuffers, template *Bespo DeleteStaleAssets(); //PrintAssetsBlock(0); - printf("\n╾─ Monitoring file system for %snew%s, %sedited%s and %sdeleted%s .hmml and asset files ─╼\n", + MEM_TEST_END("InitAll()"); + + fprintf(stderr, + "\n" + "╾─ Monitoring file system for %snew%s, %sedited%s and %sdeleted%s .hmml and asset files ─╼\n", ColourStrings[EditTypes[EDIT_ADDITION].Colour], ColourStrings[CS_END], ColourStrings[EditTypes[EDIT_REINSERTION].Colour], ColourStrings[CS_END], ColourStrings[EditTypes[EDIT_DELETION].Colour], ColourStrings[CS_END]); @@ -15019,13 +15350,13 @@ InitAll(neighbourhood *Neighbourhood, buffers *CollationBuffers, template *Bespo void RemoveAndFreeWatchHandles(watch_handles *W) { - for(int i = 0; i < W->Count; ++i) + for(int i = 0; i < W->Handles.ItemCount; ++i) { - watch_handle *This = &W->Handles[i]; + watch_handle *This = GetPlaceInBook(&W->Handles, i); inotify_rm_watch(inotifyInstance, This->Descriptor); - FreeAndResetCount(This->Files, This->FileCount); + FreeBook(&This->Files); } - FreeAndResetCountAndCapacity(W->Handles, W->Count, W->Capacity); + FreeAndReinitialiseBook(&W->Handles); FreeAndReinitialiseBook(&W->Paths); } @@ -15046,9 +15377,9 @@ GetWatchFileForEvent(struct inotify_event *Event) { watch_file *Result = 0; bool Update = FALSE; - for(int HandleIndex = 0; HandleIndex < WatchHandles.Count; ++HandleIndex) + for(int HandleIndex = 0; HandleIndex < WatchHandles.Handles.ItemCount; ++HandleIndex) { - watch_handle *ThisWatch = WatchHandles.Handles + HandleIndex; + watch_handle *ThisWatch = GetPlaceInBook(&WatchHandles.Handles, HandleIndex); if(Event->wd == ThisWatch->Descriptor) { if(Event->mask & IN_DELETE_SELF || Event->mask == (IN_CREATE | IN_ISDIR)) @@ -15058,9 +15389,9 @@ GetWatchFileForEvent(struct inotify_event *Event) if(StringsMatch(ThisWatch->TargetPath, ThisWatch->WatchedPath)) { - for(int FileIndex = 0; FileIndex < ThisWatch->FileCount; ++FileIndex) + for(int FileIndex = 0; FileIndex < ThisWatch->Files.ItemCount; ++FileIndex) { - watch_file *ThisFile = ThisWatch->Files + FileIndex; + watch_file *ThisFile = GetPlaceInBook(&ThisWatch->Files, FileIndex); if(ThisFile->Extension != EXT_NULL) { if(ExtensionMatches(Wrap0(Event->name), ThisFile->Extension)) @@ -15206,8 +15537,7 @@ MonitorFilesystem(neighbourhood *N, buffers *CollationBuffers, template *Bespoke if(WouldHaveDeleted && (Events.Ptr + sizeof(struct inotify_event) + Event->len) - Events.Location == BytesRead) { - fprintf(stderr, "Sleeping before polling for more filesystem events"); - sleep(1); + sleep(1); // NOTE(matt): Give any remaining events time to occur struct inotify_event EventN = *Event; char EventNName[Event->len + 1]; @@ -15302,14 +15632,64 @@ void InitWatchHandles(uint32_t DefaultEventsMask, uint64_t DesiredPageSize) { WatchHandles.DefaultEventsMask = DefaultEventsMask; - InitBook(&WatchHandles.Paths, 1, DesiredPageSize, MBT_STRING); + WatchHandles.Handles = InitBook(MBT_WATCH_HANDLE, 16); + WatchHandles.Paths = InitBookOfStrings(DesiredPageSize); +} + +void +InitMemoryBookTypeWidths(void) +{ + for(memory_book_type i = 0; i < MBT_COUNT; ++i) + { + switch(i) + { + case MBT_ASSET: MemoryBookTypeWidths[i] = sizeof(asset); break; + case MBT_CATEGORY_INFO: MemoryBookTypeWidths[i] = sizeof(category_info); break; + case MBT_CONFIG_IDENTIFIER_ID: MemoryBookTypeWidths[i] = sizeof(config_identifier_id); break; + case MBT_CONFIG_BOOL_PAIR: MemoryBookTypeWidths[i] = sizeof(config_bool_pair); break; + case MBT_CONFIG_INT_PAIR: MemoryBookTypeWidths[i] = sizeof(config_int_pair); break; + case MBT_CONFIG_PAIR: MemoryBookTypeWidths[i] = sizeof(config_pair); break; + case MBT_CONFIG_STRING_ASSOCIATION: MemoryBookTypeWidths[i] = sizeof(config_string_association); break; + case MBT_CONFIG_TYPE_FIELD: MemoryBookTypeWidths[i] = sizeof(config_type_field); break; + case MBT_CONFIG_TYPE_SPEC: MemoryBookTypeWidths[i] = sizeof(config_type_spec); break; + case MBT_IDENTIFIER: MemoryBookTypeWidths[i] = sizeof(identifier); break; + case MBT_LANDMARK: MemoryBookTypeWidths[i] = sizeof(landmark); break; + case MBT_MEDIUM: MemoryBookTypeWidths[i] = sizeof(medium); break; + case MBT_NAVIGATION_BUFFER: MemoryBookTypeWidths[i] = sizeof(navigation_buffer); break; + case MBT_PERSON: MemoryBookTypeWidths[i] = sizeof(person); break; + case MBT_PROJECT: MemoryBookTypeWidths[i] = sizeof(project); break; + case MBT_REF_INFO: MemoryBookTypeWidths[i] = sizeof(ref_info); break; + case MBT_RESOLUTION_ERROR: MemoryBookTypeWidths[i] = sizeof(resolution_error); break; + case MBT_SCOPE_TREE: MemoryBookTypeWidths[i] = sizeof(scope_tree); break; + case MBT_SPEAKER: MemoryBookTypeWidths[i] = sizeof(speaker); break; + case MBT_SUPPORT: MemoryBookTypeWidths[i] = sizeof(support); break; + case MBT_TAG_OFFSET: MemoryBookTypeWidths[i] = sizeof(tag_offset); break; + case MBT_TOKEN: MemoryBookTypeWidths[i] = sizeof(token); break; + case MBT_TOKENS: MemoryBookTypeWidths[i] = sizeof(tokens); break; + case MBT_UINT32: MemoryBookTypeWidths[i] = sizeof(uint32_t); break; + case MBT_VARIANT: MemoryBookTypeWidths[i] = sizeof(variant); break; + case MBT_VARIANT_STRING: MemoryBookTypeWidths[i] = sizeof(variant_string); break; + case MBT_WATCH_FILE: MemoryBookTypeWidths[i] = sizeof(watch_file); break; + case MBT_WATCH_HANDLE: MemoryBookTypeWidths[i] = sizeof(watch_handle); break; + + case MBT_NONE: + case MBT_PERSON_PTR: + case MBT_PROJECT_PTR: + case MBT_STRING: + case MBT_STRING_PTR: + case MBT_COUNT: break; + } + } } int main(int ArgC, char **Args) { + MEM_TEST_TOP("main()"); + Assert(ArrayCount(MemoryBookTypeWidths) == MBT_COUNT); Assert(ArrayCount(BufferIDStrings) == BID_COUNT); Assert(ArrayCount(TemplateTags) == TEMPLATE_TAG_COUNT); + InitMemoryBookTypeWidths(); char CommandLineArg; char *ConfigPath = "$XDG_CONFIG_HOME/cinera/cinera.conf"; while((CommandLineArg = getopt(ArgC, Args, "0c:ehv")) != -1) @@ -15334,7 +15714,9 @@ main(int ArgC, char **Args) } // NOTE(matt): Init MemoryArenas (they are global) + MEM_TEST_MID("main()"); InitMemoryArena(&MemoryArena, Megabytes(4)); + MEM_TEST_MID("main()"); ConfigPath = ExpandPath(Wrap0(ConfigPath), 0); @@ -15369,8 +15751,7 @@ main(int ArgC, char **Args) CollationBuffers.Search.ID = BID_COLLATION_BUFFERS_SEARCH; // NOTE(matt): Allocated by SearchToBuffer() - memory_book TokensList = {}; - InitBook(&TokensList, sizeof(tokens), 8, MBT_TOKENS); + memory_book TokensList = InitBook(MBT_TOKENS, 8); template BespokeTemplate = {}; neighbourhood Neighbourhood = {}; @@ -15398,23 +15779,20 @@ main(int ArgC, char **Args) } else { - InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + /* */ MEM_TEST_MID("main()"); + /* +MEM */ InitAll(&Neighbourhood, &CollationBuffers, &BespokeTemplate); + /* */ MEM_TEST_MID("main()"); } } - else + else if(SeekConfirmation("Print config help?", TRUE)) { - fprintf(stderr, "\n" - "Print config help? (Y/n)\n"); - if(getchar() != 'n') + if(ArgC < 2) { - if(ArgC < 2) - { - PrintHelp(Args[0], ConfigPath); - } - else - { - PrintHelpConfig(); - } + PrintHelp(Args[0], ConfigPath); + } + else + { + PrintHelpConfig(); } } @@ -15447,5 +15825,6 @@ main(int ArgC, char **Args) DiscardAllAndFreeConfig(); RemoveAndFreeWatchHandles(&WatchHandles); + MEM_TEST_END("main()"); Exit(); } diff --git a/cinera/cinera_config.c b/cinera/cinera_config.c index e7bed48..3b34127 100644 --- a/cinera/cinera_config.c +++ b/cinera/cinera_config.c @@ -343,7 +343,7 @@ tokens * PushTokens(memory_book *TokensList) { tokens *This = MakeSpaceInBook(TokensList); - InitBook(&This->Token, sizeof(token), 16, MBT_TOKEN); + This->Token = InitBook(MBT_TOKEN, 16); This->CurrentLine = 1; return This; } @@ -562,6 +562,7 @@ typedef struct string QueryString; bool RespectingPrivacy; + bool SuppressingPrompts; time_t PrivacyCheckInterval; uint8_t LogLevel; @@ -648,7 +649,8 @@ Tokenise(memory_book *TokensList, string Path) Advancement = 2; T.Type = TOKEN_NULL; string Char = { .Base = B->Ptr, .Length = 2 }; - ConfigError(Result->File.Path, Result->CurrentLine, S_WARNING, "Mismatched closing multiline comment marker: ", &Char); + string Filepath = Wrap0(Result->File.Path); + ConfigError(&Filepath, Result->CurrentLine, S_WARNING, "Mismatched closing multiline comment marker: ", &Char); } else if(!StringsDifferS(TokenStrings[TOKEN_DOUBLEQUOTE], B)) { @@ -730,13 +732,14 @@ Tokenise(memory_book *TokensList, string Path) T.Type = TOKEN_NULL; string Char = GetUTF8Character(B->Ptr, B->Size - (B->Ptr - B->Location)); Advancement = Char.Length; + string Filepath = Wrap0(Result->File.Path); if(Char.Base) { - ConfigError(Result->File.Path, Result->CurrentLine, S_WARNING, "Unhandled character (ignored): ", &Char); + ConfigError(&Filepath, Result->CurrentLine, S_WARNING, "Unhandled character (ignored): ", &Char); } else { - ConfigErrorInt(Result->File.Path, Result->CurrentLine, S_WARNING, "Malformed UTF-8 bytes encountered (skipped): ", Char.Length); + ConfigErrorInt(&Filepath, Result->CurrentLine, S_WARNING, "Malformed UTF-8 bytes encountered (skipped): ", Char.Length); } } @@ -838,7 +841,7 @@ PushTypeSpec(memory_book *TypeSpecs, config_identifier_id ID, bool IsPermeable) //PrintFunctionName("PushTypeSpec()"); config_type_spec *Result = MakeSpaceInBook(TypeSpecs); Result->ID = ID; - InitBook(&Result->Field, sizeof(config_type_field), 4, MBT_CONFIG_TYPE_FIELD); + Result->Field = InitBook(MBT_CONFIG_TYPE_FIELD, 4); Result->Permeable = IsPermeable; return Result; } @@ -858,8 +861,7 @@ _memory_book(config_type_specs) InitTypeSpecs(void) { //PrintFunctionName("InitTypeSpecs()"); - memory_book Result = {}; - InitBook(&Result, sizeof(config_type_spec), 16, MBT_CONFIG_TYPE_SPEC); + memory_book Result = InitBook(MBT_CONFIG_TYPE_SPEC, 16); config_type_spec *Root = PushTypeSpec(&Result, IDENT_NULL, FALSE); PushTypeSpecField(Root, FT_STRING, IDENT_ASSETS_ROOT_DIR, TRUE); @@ -909,6 +911,7 @@ InitTypeSpecs(void) PushTypeSpecField(Root, FT_BOOLEAN, IDENT_DENY_BESPOKE_TEMPLATES, TRUE); PushTypeSpecField(Root, FT_BOOLEAN, IDENT_IGNORE_PRIVACY, TRUE); PushTypeSpecField(Root, FT_BOOLEAN, IDENT_SINGLE_BROWSER_TAB, TRUE); + PushTypeSpecField(Root, FT_BOOLEAN, IDENT_SUPPRESS_PROMPTS, TRUE); PushTypeSpecField(Root, FT_NUMBER, IDENT_PRIVACY_CHECK_INTERVAL, TRUE); PushTypeSpecField(Root, FT_SCOPE, IDENT_INCLUDE, FALSE); PushTypeSpecField(Root, FT_SCOPE, IDENT_MEDIUM, FALSE); @@ -1170,7 +1173,7 @@ SetTypeSpec(scope_tree *Type, memory_book *TypeSpecs) //PrintTypeSpec(Spec); Type->TypeSpec.ID = Spec->ID; Type->TypeSpec.Permeable = Spec->Permeable; - InitBook(&Type->TypeSpec.Field, sizeof(config_type_field), 4, MBT_CONFIG_TYPE_FIELD); + Type->TypeSpec.Field = InitBook(MBT_CONFIG_TYPE_FIELD, 4); for(int i = 0; i < Spec->Field.ItemCount; ++i) { config_type_field *Src = GetPlaceInBook(&Spec->Field, i); @@ -1537,10 +1540,10 @@ PushBoolPair(scope_tree *Parent, config_bool_pair *B) void InitScopeBooks(scope_tree *Scope) { - InitBook(&Scope->Trees, sizeof(scope_tree), 4, MBT_CONFIG_PAIR); - InitBook(&Scope->Pairs, sizeof(config_pair), 4, MBT_CONFIG_PAIR); - InitBook(&Scope->IntPairs, sizeof(config_int_pair), 4, MBT_CONFIG_INT_PAIR); - InitBook(&Scope->BoolPairs, sizeof(config_bool_pair), 4, MBT_CONFIG_BOOL_PAIR); + Scope->Trees = InitBook(MBT_SCOPE_TREE, 4); + Scope->Pairs = InitBook(MBT_CONFIG_PAIR, 4); + Scope->IntPairs = InitBook(MBT_CONFIG_INT_PAIR, 4); + Scope->BoolPairs = InitBook(MBT_CONFIG_BOOL_PAIR, 4); } scope_tree * @@ -1816,7 +1819,7 @@ PushRule(config_include_rules *R, config_identifier_id ID) } void -ParseRuleString(char *Filename, memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, config_include_rules *Rules, token *RuleString) +ParseRuleString(string *Filename, memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, config_include_rules *Rules, token *RuleString) { int i = SkipWhitespaceS(&RuleString->Content, 0); config_type_field *Field = 0; @@ -1908,6 +1911,7 @@ ParseIncludeRules(memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, toke ++T->CurrentIndex; for(; T->CurrentIndex < T->Token.ItemCount;) { + string Filepath = Wrap0(T->File.Path); if(TokenIs(T, TOKEN_IDENTIFIER)) { if(TokenEquals(T, "allow")) @@ -1916,7 +1920,7 @@ ParseIncludeRules(memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, toke { FreeBook(&Rules->ID); token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0); + ConfigError(&Filepath, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0); return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_SCHEME_MIXTURE : RC_SYNTAX_ERROR; } Rules->Scheme = CRT_ALLOW; @@ -1927,7 +1931,7 @@ ParseIncludeRules(memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, toke { FreeBook(&Rules->ID); token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0); + ConfigError(&Filepath, This->LineNumber, S_WARNING, "Mixture of allow and deny identifiers in \"include\" scope", 0); return DepartIncludeAssignment(T, IncludeIdentifierTokenIndex) ? RC_SCHEME_MIXTURE : RC_SYNTAX_ERROR; } Rules->Scheme = CRT_DENY; @@ -1935,7 +1939,7 @@ ParseIncludeRules(memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, toke else { token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Invalid identifier in \"include\" scope: ", &This->Content); + ConfigError(&Filepath, This->LineNumber, S_WARNING, "Invalid identifier in \"include\" scope: ", &This->Content); fprintf(stderr, " Valid identifiers:\n" " allow\n" @@ -1947,7 +1951,7 @@ ParseIncludeRules(memory_book *TypeSpecs, config_type_spec *ParentTypeSpec, toke if(!ExpectToken(T, TOKEN_ASSIGN, 0)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; } token RuleString = {}; if(!ExpectToken(T, TOKEN_STRING, &RuleString)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; } - ParseRuleString(T->File.Path, TypeSpecs, ParentTypeSpec, Rules, &RuleString); + ParseRuleString(&Filepath, TypeSpecs, ParentTypeSpec, Rules, &RuleString); if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeBook(&Rules->ID); return RC_SYNTAX_ERROR; } } else if(TokenIs(T, TOKEN_CLOSE_BRACE)) @@ -2051,6 +2055,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T scope_tree *Parent = Tree; for(T->CurrentIndex = 0; T->CurrentIndex < T->Token.ItemCount;) { + string Filepath = Wrap0(T->File.Path); if(TokenIs(T, TOKEN_IDENTIFIER)) { config_type_field *Field = CurrentFieldIsInSpec(&Parent->TypeSpec, T); @@ -2061,7 +2066,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T if(Field->IsSetLocally && Field->Singleton) { token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Field already set: ", &This->Content); + ConfigError(&Filepath, This->LineNumber, S_WARNING, "Field already set: ", &This->Content); } // NOTE(matt): If we get rid of FT_BARE, then the ++T->CurrentIndex that all cases perform could happen // right here @@ -2098,7 +2103,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T BoolPair.Position.LineNumber = Value.LineNumber; if(!ExpectToken(T, TOKEN_SEMICOLON, 0)) { FreeScopeTree(Tree); return 0; } - bool Bool = GetBoolFromString(T->File.Path, &Value); + bool Bool = GetBoolFromString(&Filepath, &Value); if(Bool != -1) { BoolPair.Value = Bool; @@ -2128,7 +2133,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T if(Field->ID == IDENT_NUMBERING_SCHEME) { - numbering_scheme NumberingScheme = GetNumberingSchemeFromString(T->File.Path, &Value); + numbering_scheme NumberingScheme = GetNumberingSchemeFromString(&Filepath, &Value); if(NumberingScheme != NS_COUNT) { IntPair.Value = NumberingScheme; @@ -2146,7 +2151,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T } else if(Field->ID == IDENT_LOG_LEVEL) { - log_level LogLevel = GetLogLevelFromString(T->File.Path, &Value); + log_level LogLevel = GetLogLevelFromString(&Filepath, &Value); if(LogLevel != LOG_COUNT) { IntPair.Value = LogLevel; @@ -2164,7 +2169,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T } else if(Field->ID == IDENT_GENRE) { - genre Genre = GetGenreFromString(T->File.Path, &Value); + genre Genre = GetGenreFromString(&Filepath, &Value); if(Genre != GENRE_COUNT) { IntPair.Value = Genre; @@ -2182,7 +2187,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T } else if(Field->ID == IDENT_ICON_TYPE) { - icon_type IconType = GetIconTypeFromString(T->File.Path, &Value); + icon_type IconType = GetIconTypeFromString(&Filepath, &Value); if(IconType != IT_COUNT) { IntPair.Value = IconType; @@ -2200,7 +2205,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T } else if(Field->ID == IDENT_ART_VARIANTS || Field->ID == IDENT_ICON_VARIANTS) { - int64_t ArtVariants = ParseArtVariantsString(T->File.Path, &Value); + int64_t ArtVariants = ParseArtVariantsString(&Filepath, &Value); if(ArtVariants != -1) { IntPair.Value = ArtVariants; @@ -2268,7 +2273,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T { int IncludePathTokenIndex = T->CurrentIndex - 1; config_include_rules Rules = {}; - InitBook(&Rules.ID, sizeof(config_identifier_id), 4, MBT_CONFIG_IDENTIFIER_ID); + Rules.ID = InitBook(MBT_CONFIG_IDENTIFIER_ID, 4); switch(ParseIncludeRules(TypeSpecs, &Parent->TypeSpec, T, &Rules)) { case RC_SUCCESS: @@ -2304,7 +2309,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T else { token *This = GetPlaceInBook(&T->Token, IncludePathTokenIndex); - ConfigFileIncludeError(T->File.Path, This->LineNumber, Wrap0(IncludePath)); + ConfigFileIncludeError(&Filepath, This->LineNumber, Wrap0(IncludePath)); } Free(IncludePath); } break; @@ -2392,14 +2397,14 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T else { token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Field not allowed: ", &This->Content); + ConfigError(&Filepath, This->LineNumber, S_WARNING, "Field not allowed: ", &This->Content); DepartAssignment(T); } } else { token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigError(T->File.Path, This->LineNumber, S_WARNING, "Invalid field: ", &This->Content); + ConfigError(&Filepath, This->LineNumber, S_WARNING, "Invalid field: ", &This->Content); if(!DepartAssignment(T)) { FreeScopeTree(Tree); @@ -2412,7 +2417,7 @@ ScopeTokens(scope_tree *Tree, memory_book *TokensList, tokens *T, memory_book *T if(!Parent->Parent) { token *This = GetPlaceInBook(&T->Token, T->CurrentIndex); - ConfigError(T->File.Path, This->LineNumber, S_ERROR, "Syntax error: Unpaired closing brace", 0); + ConfigError(&Filepath, This->LineNumber, S_ERROR, "Syntax error: Unpaired closing brace", 0); FreeScopeTree(Tree); return 0; } @@ -2552,7 +2557,8 @@ GetPerson(config *C, resolution_errors *E, config_pair *PersonID) if(!GetError(E, S_WARNING, &PersonID->Position, IDENT_PERSON)) { - ConfigError(PersonID->Position.Filename, PersonID->Position.LineNumber, S_WARNING, "Could not find person: ", &PersonID->Value); + string Filepath = Wrap0(PersonID->Position.Filename); + ConfigError(&Filepath, PersonID->Position.LineNumber, S_WARNING, "Could not find person: ", &PersonID->Value); PushError(E, S_WARNING, &PersonID->Position, IDENT_PERSON); } return 0; @@ -2562,8 +2568,7 @@ string DeriveLineageOfProject(config *C, scope_tree *Project) { string Result = {}; - memory_book StringList = {}; - InitBookOfPointers(&StringList, 4, MBT_STRING_PTR); + memory_book StringList = InitBookOfPointers(MBT_STRING_PTR, 4); if(Project) { string **Writer = MakeSpaceInBook(&StringList); @@ -2642,6 +2647,7 @@ string ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_identifier_id Variable, token_position *Position) { string Result = {}; + string Filepath = Wrap0(Position->Filename); switch(Variable) { case IDENT_LINEAGE: @@ -2655,7 +2661,7 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_ string This = Wrap0(ConfigIdentifiers[Variable].String); if(!GetError(E, S_WARNING, Position, Variable)) { - ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); + ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); PushError(E, S_WARNING, Position, Variable); } } @@ -2671,7 +2677,7 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_ string This = Wrap0(ConfigIdentifiers[Variable].String); if(!GetError(E, S_WARNING, Position, Variable)) { - ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); + ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); PushError(E, S_WARNING, Position, Variable); } } @@ -2691,7 +2697,7 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_ string This = Wrap0(ConfigIdentifiers[Variable].String); if(!GetError(E, S_WARNING, Position, Variable)) { - ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); + ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); PushError(E, S_WARNING, Position, Variable); } } @@ -2714,7 +2720,7 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_ { if(!GetError(E, S_WARNING, Position, Variable)) { - ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Owner set, but the person does not exist: ", &Pair->Value); + ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Owner set, but the person does not exist: ", &Pair->Value); PushError(E, S_WARNING, Position, Variable); } Processed = TRUE; @@ -2725,7 +2731,7 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_ { if(!GetError(E, S_WARNING, Position, Variable)) { - ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "No owner set", 0); + ConfigError(&Filepath, Position->LineNumber, S_WARNING, "No owner set", 0); PushError(E, S_WARNING, Position, Variable); } } @@ -2749,7 +2755,7 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_ string This = Wrap0(ConfigIdentifiers[Variable].String); if(!GetError(E, S_WARNING, Position, Variable)) { - ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); + ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Variable could not be resolved: ", &This); PushError(E, S_WARNING, Position, Variable); } } @@ -2759,7 +2765,7 @@ ResolveLocalVariable(config *C, resolution_errors *E, scope_tree *Scope, config_ if(!GetError(E, S_WARNING, Position, Variable)) { string This = Wrap0(ConfigIdentifiers[Variable].String); - ConfigError(Position->Filename, Position->LineNumber, S_WARNING, "Unhandled local variable: ", &This); + ConfigError(&Filepath, Position->LineNumber, S_WARNING, "Unhandled local variable: ", &This); PushError(E, S_WARNING, Position, Variable); } } break; @@ -2916,7 +2922,7 @@ PushVariantUniquely(resolution_errors *E, memory_book *VariantStrings, string Pa variant_string *NewVariantString = MakeSpaceInBook(VariantStrings); NewVariantString->Path = Path; - InitBook(&NewVariantString->Variants, sizeof(variant), 4, MBT_VARIANT); + NewVariantString->Variants = InitBook(MBT_VARIANT, 4); variant *NewVariants = MakeSpaceInBook(&NewVariantString->Variants); *NewVariants = Variants; } @@ -3112,7 +3118,7 @@ PushPersonOntoConfig(config *C, resolution_errors *E, config_verifiers *V, scope } } - InitBook(&This->Support, sizeof(support), 2, MBT_SUPPORT); + This->Support = InitBook(MBT_SUPPORT, 2); for(int i = 0; i < PersonTree->Trees.ItemCount; ++i) { scope_tree *ThisSupport = GetPlaceInBook(&PersonTree->Trees, i); @@ -3161,7 +3167,7 @@ void PushAssociation(config_string_associations *HMMLDirs, string *S0, string *S1, project *P) { config_string_association *This = MakeSpaceInBook(&HMMLDirs->Associations); - InitBookOfPointers(&This->Projects, 4, MBT_PROJECT_PTR); + This->Projects = InitBookOfPointers(MBT_PROJECT_PTR, 4); if(S0) { This->String0 = *S0; } if(S1) { This->String1 = *S1; } @@ -3240,17 +3246,18 @@ SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HM void PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, scope_tree *ProjectTree) { - InitBook(&P->Medium, sizeof(medium), 8, MBT_MEDIUM); - InitBook(&P->Child, sizeof(project), 8, MBT_PROJECT); - InitBookOfPointers(&P->Indexer, 4, MBT_PERSON_PTR); - InitBookOfPointers(&P->CoHost, 4, MBT_PERSON_PTR); - InitBookOfPointers(&P->Guest, 4, MBT_PERSON_PTR); + P->Medium = InitBook(MBT_MEDIUM, 8); + P->Child = InitBook(MBT_PROJECT, 8); + P->Indexer = InitBookOfPointers(MBT_PERSON_PTR, 4); + P->CoHost = InitBookOfPointers(MBT_PERSON_PTR, 4); + P->Guest = InitBookOfPointers(MBT_PERSON_PTR, 4); config_string_associations *HMMLDirs = &V->HMMLDirs; P->ID = ResolveString(C, E, ProjectTree, &ProjectTree->ID, FALSE); if(P->ID.Length > MAX_PROJECT_ID_LENGTH) { - ConfigErrorSizing(ProjectTree->ID.Position.Filename, ProjectTree->ID.Position.LineNumber, IDENT_PROJECT, &P->ID, MAX_PROJECT_ID_LENGTH); + string Filepath = Wrap0(ProjectTree->ID.Position.Filename); + ConfigErrorSizing(&Filepath, ProjectTree->ID.Position.LineNumber, IDENT_PROJECT, &P->ID, MAX_PROJECT_ID_LENGTH); PushError(E, S_ERROR, 0, IDENT_PROJECT); } @@ -3269,6 +3276,7 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc for(int i = 0; i < ProjectTree->Pairs.ItemCount; ++i) { config_pair *This = GetPlaceInBook(&ProjectTree->Pairs, i); + string Filepath = Wrap0(This->Position.Filename); switch(This->Key) { case IDENT_DEFAULT_MEDIUM: @@ -3290,7 +3298,7 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc { P->Theme = ResolveString(C, E, ProjectTree, This, FALSE); if(P->Theme.Length > MAX_THEME_LENGTH) { - ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key, + ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key, &P->Theme, MAX_THEME_LENGTH); PushError(E, S_ERROR, 0, This->Key); } @@ -3300,7 +3308,7 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc P->Title = ResolveString(C, E, ProjectTree, This, FALSE); if(P->Title.Length > MAX_PROJECT_NAME_LENGTH) { - ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key, + ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key, &P->Title, MAX_PROJECT_NAME_LENGTH); PushError(E, S_ERROR, 0, This->Key); } @@ -3317,7 +3325,7 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc P->BaseDir = StripSlashes(ResolveString(C, E, ProjectTree, This, TRUE), P_ABS); if(P->BaseDir.Length > MAX_BASE_DIR_LENGTH) { - ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key, + ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key, &P->BaseDir, MAX_BASE_DIR_LENGTH); PushError(E, S_ERROR, 0, This->Key); } @@ -3327,7 +3335,7 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc P->BaseURL = ResolveString(C, E, ProjectTree, This, FALSE); if(P->BaseURL.Length > MAX_BASE_URL_LENGTH) { - ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key, + ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key, &P->BaseURL, MAX_BASE_URL_LENGTH); PushError(E, S_ERROR, 0, This->Key); } @@ -3337,7 +3345,7 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc P->PlayerLocation = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); if(P->PlayerLocation.Length > MAX_RELATIVE_PAGE_LOCATION_LENGTH) { - ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key, + ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key, &P->PlayerLocation, MAX_RELATIVE_PAGE_LOCATION_LENGTH); PushError(E, S_ERROR, 0, This->Key); } @@ -3347,7 +3355,7 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc P->SearchLocation = StripSlashes(ResolveString(C, E, ProjectTree, This, FALSE), P_REL); if(P->SearchLocation.Length > MAX_RELATIVE_PAGE_LOCATION_LENGTH) { - ConfigErrorSizing(This->Position.Filename, This->Position.LineNumber, This->Key, + ConfigErrorSizing(&Filepath, This->Position.LineNumber, This->Key, &P->SearchLocation, MAX_RELATIVE_PAGE_LOCATION_LENGTH); PushError(E, S_ERROR, 0, This->Key); } @@ -4374,18 +4382,18 @@ config_verifiers InitVerifiers(void) { config_verifiers Result = {}; - InitBook(&Result.VariantStrings, sizeof(variant_string), 8, MBT_VARIANT_STRING); + Result.VariantStrings = InitBook(MBT_VARIANT_STRING, 8); Result.HMMLDirs.ID0 = IDENT_HMML_DIR; - InitBook(&Result.HMMLDirs.Associations, sizeof(config_string_association), 8, MBT_CONFIG_STRING_ASSOCIATION); + Result.HMMLDirs.Associations = InitBook(MBT_CONFIG_STRING_ASSOCIATION, 8); Result.BaseDirAndSearchLocation.ID0 = IDENT_BASE_DIR; Result.BaseDirAndSearchLocation.ID1 = IDENT_SEARCH_LOCATION; - InitBook(&Result.BaseDirAndSearchLocation.Associations, sizeof(config_string_association), 8, MBT_CONFIG_STRING_ASSOCIATION); + Result.BaseDirAndSearchLocation.Associations = InitBook(MBT_CONFIG_STRING_ASSOCIATION, 8); Result.BaseDirAndPlayerLocation.ID0 = IDENT_BASE_DIR; Result.BaseDirAndPlayerLocation.ID1 = IDENT_PLAYER_LOCATION; - InitBook(&Result.BaseDirAndPlayerLocation.Associations, sizeof(config_string_association), 8, MBT_CONFIG_STRING_ASSOCIATION); + Result.BaseDirAndPlayerLocation.Associations = InitBook(MBT_CONFIG_STRING_ASSOCIATION, 8); return Result; } @@ -4394,19 +4402,20 @@ ResolveVariables(scope_tree *S) { Assert(LOG_COUNT == ArrayCount(LogLevelStrings)); config *Result = calloc(1, sizeof(config)); - InitBook(&Result->ResolvedVariables, 1, Kilobytes(1), MBT_STRING); - InitBook(&Result->Person, sizeof(person), 8, MBT_PERSON); - InitBook(&Result->Project, sizeof(project), 8, MBT_PROJECT); + Result->ResolvedVariables = InitBookOfStrings(Kilobytes(1)); + Result->Person = InitBook(MBT_PERSON, 8); + Result->Project = InitBook(MBT_PROJECT, 8); resolution_errors Errors = {}; - InitBook(&Errors.Warnings, sizeof(resolution_error), 16, MBT_RESOLUTION_ERROR); - InitBook(&Errors.Errors, sizeof(resolution_error), 16, MBT_RESOLUTION_ERROR); + Errors.Warnings = InitBook(MBT_RESOLUTION_ERROR, 16); + Errors.Errors = InitBook(MBT_RESOLUTION_ERROR, 16); config_verifiers Verifiers = InitVerifiers(); for(int i = 0; i < S->Pairs.ItemCount; ++i) { config_pair *Pair = GetPlaceInBook(&S->Pairs, i); + string Filepath = Wrap0(Pair->Position.Filename); switch(Pair->Key) { case IDENT_DB_LOCATION: @@ -4416,7 +4425,7 @@ ResolveVariables(scope_tree *S) Result->GlobalSearchDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); if(Result->GlobalSearchDir.Length > MAX_BASE_DIR_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->GlobalSearchDir, MAX_BASE_DIR_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4426,7 +4435,7 @@ ResolveVariables(scope_tree *S) Result->GlobalSearchURL = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); if(Result->GlobalSearchURL.Length > MAX_BASE_URL_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->GlobalSearchURL, MAX_BASE_URL_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4440,7 +4449,7 @@ ResolveVariables(scope_tree *S) Result->GlobalTheme = ResolveString(Result, &Errors, S, Pair, FALSE); if(Result->GlobalTheme.Length > MAX_THEME_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->GlobalTheme, MAX_THEME_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4450,7 +4459,7 @@ ResolveVariables(scope_tree *S) Result->AssetsRootDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); if(Result->AssetsRootDir.Length > MAX_ROOT_DIR_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->AssetsRootDir, MAX_ROOT_DIR_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4460,7 +4469,7 @@ ResolveVariables(scope_tree *S) Result->AssetsRootURL = StripSlashes(ResolveString(Result, &Errors, S, Pair, TRUE), P_ABS); if(Result->AssetsRootURL.Length > MAX_ROOT_URL_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->AssetsRootURL, MAX_ROOT_URL_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4470,7 +4479,7 @@ ResolveVariables(scope_tree *S) Result->CSSDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL); if(Result->CSSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->CSSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4480,7 +4489,7 @@ ResolveVariables(scope_tree *S) Result->ImagesDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL); if(Result->ImagesDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->ImagesDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4490,7 +4499,7 @@ ResolveVariables(scope_tree *S) Result->JSDir = StripSlashes(ResolveString(Result, &Errors, S, Pair, FALSE), P_REL); if(Result->JSDir.Length > MAX_RELATIVE_ASSET_LOCATION_LENGTH) { - ConfigErrorSizing(Pair->Position.Filename, Pair->Position.LineNumber, Pair->Key, + ConfigErrorSizing(&Filepath, Pair->Position.LineNumber, Pair->Key, &Result->JSDir, MAX_RELATIVE_ASSET_LOCATION_LENGTH); PushError(&Errors, S_ERROR, 0, Pair->Key); } @@ -4517,6 +4526,17 @@ ResolveVariables(scope_tree *S) } } + for(int i = 0; i < S->BoolPairs.ItemCount; ++i) + { + config_bool_pair *BoolPair = GetPlaceInBook(&S->BoolPairs, i); + switch(BoolPair->Key) + { + case IDENT_SUPPRESS_PROMPTS: + { Result->SuppressingPrompts = BoolPair->Value; } break; + default: break; + } + } + for(int i = 0; i < S->Trees.ItemCount; ++i) { scope_tree *Tree = GetPlaceInBook(&S->Trees, i);