cinera.c 269 KB
Newer Older
1 2
#if 0
ctime -begin ${0%.*}.ctm
3 4
#gcc -g -fsanitize=address -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl
gcc -O2 -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl
5 6
#clang -fsanitize=address -g -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl
#clang -O2 -Wall -std=c99 -pipe $0 -o ${0%.*} hmml.a -lcurl
7 8 9 10
ctime -end ${0%.*}.ctm
exit
#endif

Matt Mascarenhas's avatar
Matt Mascarenhas committed
11 12 13 14 15 16 17 18
typedef struct
{
    unsigned int Major, Minor, Patch;
} version;

version CINERA_APP_VERSION = {
    .Major = 0,
    .Minor = 5,
19
    .Patch = 60
Matt Mascarenhas's avatar
Matt Mascarenhas committed
20 21
};

22
// TODO(matt): Copy in the DB 3 stuff from cinera_working.c
23
#define CINERA_DB_VERSION 3
24

25 26 27
#include <stdarg.h> // NOTE(matt): varargs
#include <stdio.h>  // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror
#include <stdlib.h> // NOTE(matt): calloc, malloc, free
28
#include "hmmlib.h"
29
#include <getopt.h> // NOTE(matt): getopts
30
//#include "config.h" // TODO(matt): Implement config.h
31
#include <curl/curl.h>
32 33
#include <time.h>
#include <sys/stat.h>
34 35
#include <sys/types.h>
#include <dirent.h>
36 37
#include <string.h> // NOTE(matt): strerror
#include <errno.h> //NOTE(matt): errno
Matt Mascarenhas's avatar
Matt Mascarenhas committed
38 39
#include <sys/inotify.h> // NOTE(matt): inotify
#include <unistd.h> // NOTE(matt): sleep()
40

41 42 43 44 45 46 47 48
typedef unsigned int bool;
#define TRUE 1
#define FALSE 0

#define DEBUG 0
#define DEBUG_MEM 0

bool PROFILING = 0;
49 50 51 52
clock_t TIMING_START;
#define START_TIMING_BLOCK(...) if(PROFILING) { printf(__VA_ARGS__); TIMING_START = clock(); }
#define END_TIMING_BLOCK() if(PROFILING) { printf("\e[1;34m%ld\e[0m\n", clock() - TIMING_START);}

53 54 55
#define Kilobytes(Bytes) Bytes << 10
#define Megabytes(Bytes) Bytes << 20

56 57 58 59 60 61 62 63 64
#define MAX_PROJECT_ID_LENGTH 31
#define MAX_PROJECT_NAME_LENGTH 63
#define MAX_BASE_URL_LENGTH 127
#define MAX_RELATIVE_PAGE_LOCATION_LENGTH 31
#define MAX_PLAYER_URL_PREFIX_LENGTH 15

#define MAX_BASE_FILENAME_LENGTH 31
#define MAX_TITLE_LENGTH 128 - (MAX_BASE_FILENAME_LENGTH + 1) - (int)sizeof(link_insertion_offsets) - (int)sizeof(unsigned short int) - 1 // NOTE(matt): We size this such that index_metadata is 128 bytes total

65 66 67
#define MAX_CUSTOM_SNIPPET_SHORT_LENGTH 255
#define MAX_CUSTOM_SNIPPET_LONG_LENGTH 1023

68 69
enum
{
70 71 72
    EDITION_SINGLE,
    EDITION_PROJECT,
    EDITION_NETWORK
73 74
} editions;

75 76 77 78 79 80 81 82 83 84 85 86 87
enum
{
    // NOTE(matt): https://tools.ietf.org/html/rfc5424#section-6.2.1
    LOG_EMERGENCY,
    LOG_ALERT,
    LOG_CRITICAL,
    LOG_ERROR,
    LOG_WARNING,
    LOG_NOTICE,
    LOG_INFORMATIONAL,
    LOG_DEBUG
} log_levels;

88 89
enum
{
90 91 92 93 94
    MODE_FORCEINTEGRATION = 1 << 0,
    MODE_ONESHOT          = 1 << 1,
    MODE_EXAMINE          = 1 << 2,
    MODE_NOCACHE          = 1 << 3,
    MODE_NOPRIVACY        = 1 << 4,
95 96
} modes;

97 98 99 100 101 102 103 104 105
enum
{
    RC_ARENA_FULL,
    RC_ERROR_DIRECTORY,
    RC_ERROR_FATAL,
    RC_ERROR_FILE,
    RC_ERROR_HMML,
    RC_ERROR_MAX_REFS,
    RC_ERROR_MEMORY,
Matt Mascarenhas's avatar
Matt Mascarenhas committed
106
    RC_ERROR_PROJECT,
107
    RC_ERROR_QUOTE,
Matt Mascarenhas's avatar
Matt Mascarenhas committed
108
    RC_ERROR_SEEK,
109 110 111
    RC_FOUND,
    RC_UNFOUND,
    RC_INVALID_REFERENCE,
Matt Mascarenhas's avatar
Matt Mascarenhas committed
112
    RC_INVALID_TEMPLATE,
113
    RC_PRIVATE_VIDEO,
114 115 116 117 118 119 120 121 122 123 124 125 126
    RC_NOOP,
    RC_RIP,
    RC_SUCCESS
} returns;

typedef struct
{
    void *Location;
    void *Ptr;
    char *ID;
    int  Size;
} arena;

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
typedef struct
{
    // Universal
    char  CacheDir[256];
    int   Edition;
    int   LogLevel;
    int   Mode;
    int   UpdateInterval;

    // Advisedly universal, although could be per-project
    char *RootDir; // Absolute
    char *RootURL;
    char *CSSDir; // Relative to Root{Dir,URL}
    char *ImagesDir; // Relative to Root{Dir,URL}
    char *JSDir; // Relative to Root{Dir,URL}

    // Per Project
    char *ProjectID;
    char *Theme;
    char *DefaultMedium;

    // Per Project - Input
    char *ProjectDir; // Absolute
    char *TemplatesDir; // Absolute
    char *TemplateIndexLocation; // Relative to TemplatesDir ???
    char *TemplatePlayerLocation; // Relative to TemplatesDir ???

    // Per Project - Output
    char *BaseDir; // Absolute
    char *BaseURL;
    char *IndexLocation; // Relative to Base{Dir,URL}
    char *PlayerLocation; // Relative to Base{Dir,URL}
    char *PlayerURLPrefix; /* NOTE(matt): This will become a full blown customisable output URL.
                                    For now it simply replaces the ProjectID */
    // Single Edition - Input
    char SingleHMMLFilePath[256];

    // Single Edition - Output
    char *OutLocation;
    char *OutIntegratedLocation;
} config;

// NOTE(matt): Globals
config Config = {};
arena MemoryArena;
172
time_t LastPrivacyCheck;
173 174 175
time_t LastQuoteFetch;
//

176 177 178 179
typedef struct
{
    char *Location;
    char *Ptr;
180
    char *ID;
181 182 183
    int  Size;
} buffer;

184 185 186 187
typedef struct
{
    buffer Buffer;
    FILE  *Handle;
188
    char   Path[256]; // NOTE(matt): Could this just be a char *?
189 190 191
    int    FileSize;
} file_buffer;

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
// TODO(matt): Increment CINERA_DB_VERSION!
typedef struct
{
    // NOTE(matt): Consider augmenting this to contain such stuff as: "hex signature"
    unsigned int       CurrentDBVersion; // NOTE(matt): Put this first to aid reliability
    version            CurrentAppVersion;
    version            CurrentHMMLVersion;

    unsigned int       InitialDBVersion;
    version            InitialAppVersion;
    version            InitialHMMLVersion;

    unsigned short int EntryCount;
    char               ProjectID[MAX_PROJECT_ID_LENGTH + 1];
    char               ProjectName[MAX_PROJECT_NAME_LENGTH + 1];
    char               BaseURL[MAX_BASE_URL_LENGTH + 1];
    char               IndexLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
    char               PlayerLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
210
    char               PlayerURLPrefix[MAX_PLAYER_URL_PREFIX_LENGTH + 1]; // TODO(matt): Replace this with the OutputPath, when we add that
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
} index_header;

typedef struct
{
    unsigned int       PrevStart, NextStart;
    unsigned short int PrevEnd, NextEnd;
} link_insertion_offsets; // NOTE(matt): Relative

typedef struct
{
    link_insertion_offsets  LinkOffsets;
    unsigned short int      Size;
    char                    BaseFilename[MAX_BASE_FILENAME_LENGTH + 1];
    char                    Title[MAX_TITLE_LENGTH + 1];
} index_metadata;

typedef struct
{
    file_buffer File;
    file_buffer Metadata;
    index_header Header;
    index_metadata Entry;
} index;
// TODO(matt): Increment CINERA_DB_VERSION!

236 237 238 239 240 241 242 243 244
typedef struct
{
    buffer IncludesIndex;
    buffer Search;
    buffer Index; // NOTE(matt): This buffer is malloc'd separately, rather than claimed from the memory_arena
    buffer IncludesPlayer;
    buffer Menus;
    buffer Player;
    buffer ScriptPlayer;
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262

    char Custom0[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom1[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom2[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom3[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom4[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom5[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom6[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom7[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom8[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom9[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom10[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom11[MAX_CUSTOM_SNIPPET_SHORT_LENGTH + 1];
    char Custom12[MAX_CUSTOM_SNIPPET_LONG_LENGTH + 1];
    char Custom13[MAX_CUSTOM_SNIPPET_LONG_LENGTH + 1];
    char Custom14[MAX_CUSTOM_SNIPPET_LONG_LENGTH + 1];
    char Custom15[MAX_CUSTOM_SNIPPET_LONG_LENGTH + 1];

263
    char ProjectID[MAX_PROJECT_ID_LENGTH + 1];
264
    char ProjectName[MAX_PROJECT_NAME_LENGTH + 1];
265
    char Theme[MAX_PROJECT_NAME_LENGTH + 1];
266 267 268
    char Title[MAX_TITLE_LENGTH + 1];
    char URLIndex[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
    char URLPlayer[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1];
269 270 271
    char VideoID[16];
} buffers;

272 273
enum
{
274 275 276 277
    // Contents and Player Pages Mandatory
    TAG_INCLUDES,

    // Contents Page Mandatory
278 279
    TAG_INDEX,

280
    // Player Page Mandatory
281 282 283 284
    TAG_MENUS,
    TAG_PLAYER,
    TAG_SCRIPT,

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
    // Player Page Optional
    TAG_CUSTOM0,
    TAG_CUSTOM1,
    TAG_CUSTOM2,
    TAG_CUSTOM3,
    TAG_CUSTOM4,
    TAG_CUSTOM5,
    TAG_CUSTOM6,
    TAG_CUSTOM7,
    TAG_CUSTOM8,
    TAG_CUSTOM9,
    TAG_CUSTOM10,
    TAG_CUSTOM11,

    TAG_CUSTOM12,
    TAG_CUSTOM13,
    TAG_CUSTOM14,
    TAG_CUSTOM15,

304 305
    TAG_TITLE,
    TAG_VIDEO_ID,
306 307 308

    // Anywhere Optional
    TAG_PROJECT,
309 310
    TAG_PROJECT_ID,
    TAG_THEME,
311
    TAG_URL,
312 313 314 315 316 317 318 319 320 321
} template_tags;

typedef struct
{
    int Code; // template_tags
    char *Tag;
} tag;

tag Tags[] = {
    { TAG_INCLUDES, "__CINERA_INCLUDES__" },
322 323 324

    { TAG_INDEX,    "__CINERA_INDEX__" },

325 326 327
    { TAG_MENUS,    "__CINERA_MENUS__" },
    { TAG_PLAYER,   "__CINERA_PLAYER__" },
    { TAG_SCRIPT,   "__CINERA_SCRIPT__" },
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346

    { TAG_CUSTOM0,  "__CINERA_CUSTOM0__" },
    { TAG_CUSTOM1,  "__CINERA_CUSTOM1__" },
    { TAG_CUSTOM2,  "__CINERA_CUSTOM2__" },
    { TAG_CUSTOM3,  "__CINERA_CUSTOM3__" },
    { TAG_CUSTOM4,  "__CINERA_CUSTOM4__" },
    { TAG_CUSTOM5,  "__CINERA_CUSTOM5__" },
    { TAG_CUSTOM6,  "__CINERA_CUSTOM6__" },
    { TAG_CUSTOM7,  "__CINERA_CUSTOM7__" },
    { TAG_CUSTOM8,  "__CINERA_CUSTOM8__" },
    { TAG_CUSTOM9,  "__CINERA_CUSTOM9__" },
    { TAG_CUSTOM10,  "__CINERA_CUSTOM10__" },
    { TAG_CUSTOM11,  "__CINERA_CUSTOM11__" },

    { TAG_CUSTOM12,  "__CINERA_CUSTOM12__" },
    { TAG_CUSTOM13,  "__CINERA_CUSTOM13__" },
    { TAG_CUSTOM14,  "__CINERA_CUSTOM14__" },
    { TAG_CUSTOM15,  "__CINERA_CUSTOM15__" },

347
    { TAG_TITLE,    "__CINERA_TITLE__" },
348
    { TAG_VIDEO_ID, "__CINERA_VIDEO_ID__" },
349

350 351 352 353
    { TAG_PROJECT,     "__CINERA_PROJECT__" },
    { TAG_PROJECT_ID,  "__CINERA_PROJECT_ID__" },
    { TAG_THEME,       "__CINERA_THEME__" },
    { TAG_URL,         "__CINERA_URL__" },
354 355 356 357 358 359 360 361 362 363
};

typedef struct
{
    int Offset;
    int TagCode;
} tag_offset;

typedef struct
{
Matt Mascarenhas's avatar
Matt Mascarenhas committed
364
    char Filename[256];
365 366 367
    tag_offset Tag[16];
    int Validity; // NOTE(matt): Bitmask describing which page the template is valid for, i.e. contents and / or player page
    int TagCount;
368 369 370 371 372 373
} template_metadata;

typedef struct
{
    template_metadata Metadata;
    buffer Buffer;
374 375
} template;

376 377 378 379 380 381 382 383
// 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];
} quote_info;

384 385
typedef struct
{
386 387 388 389
    char Timecode[8];
    int Identifier;
} identifier;

390
#define REF_MAX_IDENTIFIER 64
391

392 393
typedef struct
{
394 395 396 397
    char RefTitle[620];
    char ID[512];
    char URL[512];
    char Source[256];
398
    identifier Identifier[REF_MAX_IDENTIFIER];
399
    int IdentifierCount;
400 401
} ref_info;

402 403
typedef struct
{
404 405
    char Marker[32];
    char WrittenText[32];
406
} category_info;
407

408 409 410 411 412 413
typedef struct
{
    category_info Category[64];
    int Count;
} categories;

414
// TODO(matt): Parse this stuff out of a config file
415 416 417 418 419 420 421 422 423 424
typedef struct
{
    char *Username;
    char *CreditedName;
    char *HomepageURL;
    char *SupportIcon;
    char *SupportURL;
} credential_info;

credential_info Credentials[] =
425
{
426 427
    { "a_waterman", "Andrew Waterman", "https://www.linkedin.com/in/andrew-waterman-76805788", "", ""},
    { "y_lee", "Yunsup Lee", "https://www.linkedin.com/in/yunsup-lee-385b692b/", "", ""},
428
    { "AndrewJDR", "Andrew Johnson", "", "", ""},
429
    { "AsafGartner", "Asaf Gartner", "", "", ""},
430 431 432
    { "BretHudson", "Bret Hudson", "http://www.brethudson.com/", "cinera_sprite_patreon.png", "https://www.patreon.com/indieFunction"},
    { "ChronalDragon", "Andrew Chronister", "http://chronal.net/", "", ""},
    { "Kelimion", "Jeroen van Rijn", "https://handmade.network/home", "", ""},
433
    { "Mannilie", "Emmanuel Vaccaro", "http://emmanuelvaccaro.com/", "", ""},
434
    { "Miblo", "Matt Mascarenhas", "https://miblodelcarpio.co.uk/", "cinera_sprite_sendowl.png", "https://miblodelcarpio.co.uk/cinera#pledge"},
435
    { "Mr4thDimention", "Allen Webster", "http://www.4coder.net/", "", ""},
436
    { "Pseudonym73", "Andrew Bromage", "https://twitter.com/deguerre", "", ""},
437
    { "Quel_Solaar", "Eskil Steenberg", "http://quelsolaar.com/", "", ""},
438
    { "ZedZull", "Jay Waggle", "", "", ""},
439
    { "abnercoimbre", "Abner Coimbre", "https://handmade.network/m/abnercoimbre", "", ""},
440
    { "brianwill", "Brian Will", "http://brianwill.net/blog/", "", ""},
441
    { "cbloom", "Charles Bloom", "http://cbloomrants.blogspot.co.uk/", "", ""},
Matt Mascarenhas's avatar
Matt Mascarenhas committed
442
    { "cmuratori", "Casey Muratori", "https://handmadehero.org", "cinera_sprite_sendowl.png", "https://handmadehero.org/patreon.html"},
443
    { "csnover", "Colin Snover", "https://zetafleet.com/", "", ""},
444
    { "debiatan", "Miguel Lechón", "http://blog.debiatan.net/", "", ""},
445 446 447
    { "dspecht", "Dustin Specht", "", "", ""},
    { "effect0r", "Cory Henderlite", "", "", ""},
    { "ffsjs", "ffsjs", "", "", ""},
448 449
    { "fierydrake", "Mike Tunnicliffe", "", "", ""},
    { "garlandobloom", "Matthew VanDevander", "https://lowtideproductions.com/", "cinera_sprite_patreon.png", "https://www.patreon.com/mv"},
450 451 452
    { "ikerms", "Iker Murga", "", "", ""},
    { "insofaras", "Alex Baines", "https://abaines.me.uk/", "", ""},
    { "jacebennett", "Jace Bennett", "", "", ""},
453
    { "jon", "Jonathan Blow", "http://the-witness.net/news/", "", ""},
454
    { "jpike", "Jacob Pike", "", "", ""},
455
    { "martincohen", "Martin Cohen", "http://blog.coh.io/", "", ""},
456 457
    { "miotatsu", "Mio Iwakura", "http://riscy.tv/", "cinera_sprite_patreon.png", "https://patreon.com/miotatsu"},
    { "nothings", "Sean Barrett", "https://nothings.org/", "", ""},
458
    { "pervognsen", "Per Vognsen", "https://github.com/pervognsen/bitwise/", "", ""},
459
    { "philipbuuck", "Philip Buuck", "http://philipbuuck.com/", "", ""},
460
    { "powerc9000", "Clay Murray", "http://claymurray.website/", "", ""},
461
    { "rygorous", "Fabian Giesen", "https://fgiesen.wordpress.com/", "", ""},
462
    { "schme", "Kasper Sauramo", "", "", ""},
463 464
    { "sssmcgrath", "Shawn McGrath", "http://www.dyadgame.com/", "", ""},
    { "thehappiecat", "Anne", "https://www.youtube.com/c/TheHappieCat", "cinera_sprite_patreon.png", "https://www.patreon.com/thehappiecat"},
465
    { "theinternetftw", "Ben Craddock", "", "", ""},
466
    { "wheatdog", "Tim Liou", "http://stringbulbs.com/", "", ""},
467 468
    { "williamchyr", "William Chyr", "http://williamchyr.com/", "", ""},
    { "wonchun", "Won Chun", "https://twitter.com/won3d", "", ""},
469 470 471 472 473 474 475 476 477 478 479 480
};

typedef struct
{
    char *Medium;
    char *Icon;
    char *WrittenName;
} category_medium;

category_medium CategoryMedium[] =
{
    // medium        icon         written name
481
    { "admin",       "&#128505;", "Administrivia"},
482
    { "afk",         "&#8230;"  , "Away from Keyboard"},
483 484
    { "authored",    "&#128490;", "Chat Comment"}, // TODO(matt): Conditionally handle Chat vs Guest Comments
    { "blackboard",  "&#128396;", "Blackboard"},
485
    { "drawing",     "&#127912;", "Drawing"},
486
    { "experience",  "&#127863;", "Experience"},
487 488
    { "hat",         "&#127913;", "Hat"},
    { "multimedia",  "&#127916;", "Media Clip"},
489 490 491 492 493
    { "owl",         "&#129417;", "Owl of Shame"},
    { "programming", "&#128430;", "Programming"}, // TODO(matt): Potentially make this configurable per project
    { "rant",        "&#128162;", "Rant"},
    { "research",    "&#128214;", "Research"},
    { "run",         "&#127939;", "In-Game"}, // TODO(matt): Potentially make this configurable per project
494
    { "speech",      "&#128489;", "Speech"},
495
    { "trivia",      "&#127922;", "Trivia"},
496 497
};

Matt Mascarenhas's avatar
Matt Mascarenhas committed
498 499 500 501 502 503 504 505 506 507 508 509
enum
{
    NS_CALENDRICAL,
    NS_LINEAR,
    NS_SEASONAL,
} numbering_schemes;

typedef struct
{
    char *ProjectID;
    char *FullName;
    char *Unit; // e.g. Day, Episode, Session
510 511 512
    int NumberingScheme; // numbering_schemes
    char *Medium;
    char *AltURLPrefix; // NOTE(matt): This currently just straight up replaces the ProjectID in the player pages' output directories
Matt Mascarenhas's avatar
Matt Mascarenhas committed
513 514 515 516
} project_info;

project_info ProjectInfo[] =
{
517 518
    { "bitwise", "Bitwise", "Day", NS_LINEAR, "programming", "" },

519 520 521 522 523
    { "book",   "Book Club",                        "Day", NS_LINEAR,      "research",    "" },
    { "coad",   "Computer Organization and Design", "",    NS_LINEAR,      "research",    "" },
    { "reader", "RISC-V Reader",                    "",    NS_LINEAR,      "research",    "" },
    { "riscy",  "RISCY BUSINESS",                   "Day", NS_LINEAR,      "programming", "" },
    { "risc",   "RISCellaneous",                    "",    NS_CALENDRICAL, "speech",      "" },
524

525 526 527 528 529
    { "chat",       "Handmade Chat",         "Day", NS_LINEAR, "speech",      "" },
    { "code",       "Handmade Hero",         "Day", NS_LINEAR, "programming", "day" },
    { "intro-to-c", "Intro to C on Windows", "Day", NS_LINEAR, "programming", "day" },
    { "misc",       "Handmade Miscellany",   "",    NS_LINEAR, "admin",       "" },
    { "ray",        "Handmade Ray",          "Day", NS_LINEAR, "programming", "" },
530

531 532 533 534
    { "hmdshow", "HandmadeDev Show",   "", NS_SEASONAL, "speech",      "ep" },
    { "lecture", "Abner Talks",        "", NS_SEASONAL, "speech",      "" },
    { "stream",  "Abner Programs",     "", NS_SEASONAL, "programming", "" },
    { "special", "Abner Show Special", "", NS_SEASONAL, "programming", "" },
535

536
    { "obbg", "Open Block Building Game", "Episode", NS_LINEAR, "programming", "" },
537

538
    { "sysadmin", "SysAdmin", "Session", NS_LINEAR, "admin", "" },
Matt Mascarenhas's avatar
Matt Mascarenhas committed
539 540
};

541 542
#define ArrayCount(A) sizeof(A)/sizeof(*(A))

543 544
void
Clear(char *String, int Size)
545
{
546 547 548 549
    for(int i = 0; i < Size; ++i)
    {
        String[i] = 0;
    }
550 551
}

552
int
553
StringLength(char *String)
554
{
555 556
    int i = 0;
    while(String[i])
557
    {
558
        ++i;
559
    }
560
    return i;
561 562
}

563 564 565 566
#define CopyString(Dest, DestSize, Format, ...) CopyString_(__LINE__, (Dest), (DestSize), (Format), ##__VA_ARGS__)
__attribute__ ((format (printf, 4, 5)))
int
CopyString_(int LineNumber, char Dest[], int DestSize, char *Format, ...)
567
{
568 569 570 571 572
    int Length = 0;
    va_list Args;
    va_start(Args, Format);
    Length = vsnprintf(Dest, DestSize, Format, Args);
    if(Length >= DestSize)
573
    {
574 575
        printf("CopyString() call on line %d has been passed a buffer too small (%d bytes) to contain null-terminated %d(+1)-character string\n", LineNumber, DestSize, Length);
        __asm__("int3");
576
    }
577 578
    va_end(Args);
    return Length;
579 580
}

581
#define CopyStringNoFormat(Dest, DestSize, String) CopyStringNoFormat_(__LINE__, Dest, DestSize, String)
582
int
583
CopyStringNoFormat_(int LineNumber, char *Dest, int DestSize, char *String)
584
{
585
    int Length = 0;
586
    char *Start = String;
587 588 589
    while(*String)
    {
        *Dest++ = *String++;
590
        ++Length;
591
    }
592 593 594 595 596 597
    if(Length >= DestSize)
    {
        printf("CopyStringNoFormat() call on line %d has been passed a buffer too small (%d bytes) to contain null-terminated %d(+1)-character string:\n"
                "%s\n", LineNumber, DestSize, Length, Start);
        __asm__("int3");
    }
598
    *Dest = '\0';
599
    return Length;
600 601
}

602
#define ClearCopyStringNoFormat(Dest, DestSize, String) ClearCopyStringNoFormat_(__LINE__, Dest, DestSize, String)
603
int
604
ClearCopyStringNoFormat_(int LineNumber, char *Dest, int DestSize, char *String)
605 606
{
    Clear(Dest, DestSize);
607 608 609 610 611 612 613 614 615 616 617 618 619 620 621
    int Length = 0;
    char *Start = String;
    while(*String)
    {
        *Dest++ = *String++;
        ++Length;
    }
    if(Length >= DestSize)
    {
        printf("ClearCopyStringNoFormat() call on line %d has been passed a buffer too small (%d bytes) to contain null-terminated %d(+1)-character string:\n"
                "%s\n", LineNumber, DestSize, Length, Start);
        __asm__("int3");
    }
    *Dest = '\0';
    return Length;
622 623
}

624
// TODO(matt): Maybe do a version of this that takes a string as a Terminator
625
#define CopyStringNoFormatT(Dest, DestSize, String, Terminator) CopyStringNoFormatT_(__LINE__, Dest, DestSize, String, Terminator)
626
int
627
CopyStringNoFormatT_(int LineNumber, char *Dest, int DestSize, char *String, char Terminator)
628 629
{
    int Length = 0;
630
    char *Start = String;
631 632 633 634 635
    while(*String != Terminator)
    {
        *Dest++ = *String++;
        ++Length;
    }
636 637 638 639 640 641
    if(Length >= DestSize)
    {
        printf("CopyStringNoFormatT() call on line %d has been passed a buffer too small (%d bytes) to contain %c-terminated %d(+1)-character string:\n"
                "%.*s\n", LineNumber, DestSize, Terminator == 0 ? '0' : Terminator, Length, Length, Start);
        __asm__("int3");
    }
642 643 644 645
    *Dest = '\0';
    return Length;
}

646 647
#define CopyStringToBuffer(Dest, Format, ...) CopyStringToBuffer_(__LINE__, Dest, Format, ##__VA_ARGS__)
__attribute__ ((format (printf, 3, 4)))
648
void
649
CopyStringToBuffer_(int LineNumber, buffer *Dest, char *Format, ...)
650
{
651 652
    va_list Args;
    va_start(Args, Format);
653
    int Length = vsnprintf(Dest->Ptr, Dest->Size - (Dest->Ptr - Dest->Location), Format, Args);
654
    va_end(Args);
655
    if(Length + (Dest->Ptr - Dest->Location) >= Dest->Size)
656
    {
657 658 659
        fprintf(stderr, "CopyStringToBuffer(%s) call on line %d cannot accommodate null-terminated %d(+1)-character string:\n"
                "%s\n", Dest->ID, LineNumber, Length, Format);
        __asm__("int3");
660
    }
661
    Dest->Ptr += Length;
662 663
}

664
#define CopyStringToBufferNoFormat(Dest, String) CopyStringToBufferNoFormat_(__LINE__, Dest, String)
665
void
666
CopyStringToBufferNoFormat_(int LineNumber, buffer *Dest, char *String)
667
{
668
    char *Start = String;
669 670 671 672
    while(*String)
    {
        *Dest->Ptr++ = *String++;
    }
673 674 675 676 677 678
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
        fprintf(stderr, "CopyStringToBufferNoFormat(%s) call on line %d cannot accommodate %d-character string:\n"
                "%s\n", Dest->ID, LineNumber, StringLength(Start), Start);
        __asm__("int3");
    }
679 680 681
    *Dest->Ptr = '\0';
}

682
#define CopyStringToBufferHTMLSafe(Dest, String) CopyStringToBufferHTMLSafe_(__LINE__, Dest, String)
683
void
684
CopyStringToBufferHTMLSafe_(int LineNumber, buffer *Dest, char *String)
685
{
686 687
    char *Start = String;
    int Length = StringLength(String);
688 689 690 691
    while(*String)
    {
        switch(*String)
        {
692 693 694 695 696
            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;
            case '&': *Dest->Ptr++ = '&'; *Dest->Ptr++ = 'a'; *Dest->Ptr++ = 'm'; *Dest->Ptr++ = 'p'; *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++ = '3'; *Dest->Ptr++ = '9'; *Dest->Ptr++ = ';'; Length += 4; break;
697 698 699 700
            default: *Dest->Ptr++ = *String; break;
        }
        ++String;
    }
701 702 703 704 705 706 707
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
        fprintf(stderr, "CopyStringToBufferHTMLSafe(%s) call on line %d cannot accommodate %d(+1)-character HTML-sanitised string:\n"
                "%s\n", Dest->ID, LineNumber, Length, Start);
        __asm__("int3");
    }
    *Dest->Ptr = '\0';
708 709
}

710
#define CopyStringToBufferHTMLSafeBreakingOnSlash(Dest, String) CopyStringToBufferHTMLSafeBreakingOnSlash_(__LINE__, Dest, String)
711
void
712
CopyStringToBufferHTMLSafeBreakingOnSlash_(int LineNumber, buffer *Dest, char *String)
713
{
714 715
    char *Start = String;
    int Length = StringLength(String);
716 717 718 719
    while(*String)
    {
        switch(*String)
        {
720 721 722 723 724 725
            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;
            case '&': *Dest->Ptr++ = '&'; *Dest->Ptr++ = 'a'; *Dest->Ptr++ = 'm'; *Dest->Ptr++ = 'p'; *Dest->Ptr++ = ';'; Length += 4; break;
            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;
726 727 728
            default: *Dest->Ptr++ = *String; break;
        }
        ++String;
729
    }
730 731 732 733 734 735
    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", Dest->ID, LineNumber, Length, Start);
        __asm__("int3");
    }
736 737
}

738
#define CopyBuffer(Dest, Src) CopyBuffer_(__LINE__, Dest, Src)
Matt Mascarenhas's avatar
Matt Mascarenhas committed
739
void
740
CopyBuffer_(int LineNumber, buffer *Dest, buffer *Src)
Matt Mascarenhas's avatar
Matt Mascarenhas committed
741
{
742 743
    Src->Ptr = Src->Location;
    while(*Src->Ptr)
Matt Mascarenhas's avatar
Matt Mascarenhas committed
744
    {
745
        *Dest->Ptr++ = *Src->Ptr++;
Matt Mascarenhas's avatar
Matt Mascarenhas committed
746
    }
747 748
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
749
        fprintf(stderr, "CopyBuffer(%s) call on line %d cannot accommodate %d(+1)-character %s\n", Dest->ID, LineNumber, StringLength(Src->Location), Src->ID);
750 751
        __asm__("int3");
    }
752
    *Dest->Ptr = '\0';
753 754
}

755
#define CopyBufferSized(Dest, Src, Size) CopyBufferSized_(__LINE__, Dest, Src, Size)
756
void
757
CopyBufferSized_(int LineNumber, buffer *Dest, buffer *Src, int Size)
758 759
{
    Src->Ptr = Src->Location;
760
    while(Src->Ptr - Src->Location < Size)
761 762 763 764 765
    {
        *Dest->Ptr++ = *Src->Ptr++;
    }
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
766
        fprintf(stderr, "CopyBufferNoNull(%s) call on line %d cannot accommodate %d(+1)-character %s\n", Dest->ID, LineNumber, StringLength(Src->Location), Src->ID);
767 768
        __asm__("int3");
    }
Matt Mascarenhas's avatar
Matt Mascarenhas committed
769 770
}

771
int
772
StringsDiffer(char *A, char *B) // NOTE(matt): Two null-terminated strings
773
{
774
    while(*A && *B && *A == *B)
775 776 777 778 779 780
    {
        ++A, ++B;
    }
    return *A - *B;
}

781 782 783 784 785 786 787 788 789 790 791 792
int
StringsDifferCaseInsensitive(char *A, char *B) // NOTE(matt): Two null-terminated strings
{
    while(*A && *B &&
            ((*A >= 'A' && *A <= 'Z') ? *A + ('a' - 'A') : *A) ==
            ((*B >= 'A' && *B <= 'Z') ? *B + ('a' - 'A') : *B))
    {
        ++A, ++B;
    }
    return *A - *B;
}

793
bool
794 795
StringsDifferT(char *A, // NOTE(matt): Null-terminated string
               char *B, // NOTE(matt): Not null-terminated string (e.g. one mid-buffer)
796
               char Terminator // NOTE(matt): Caller definable terminator. Pass 0 to only match on the extent of A
797
               )
798
{
799
    // TODO(matt): Make sure this can't crash upon reaching the end of B's buffer
800
    int ALength = StringLength(A);
801
    int i = 0;
802
    while(i < ALength && A[i] && A[i] == B[i])
803 804 805
    {
        ++i;
    }
806
    if((!Terminator && !A[i] && ALength == i) ||
807
       (!A[i] && ALength == i && (B[i] == Terminator)))
808 809 810 811 812 813 814 815 816
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

817 818 819
int
MakeDir(char *Path)
{
820
    // TODO(matt): Correctly check for permissions
821 822 823 824
    int i = StringLength(Path);
    int Ancestors = 0;
    while(mkdir(Path, 00755) == -1)
    {
825 826 827 828
        if(errno == EACCES)
        {
            return RC_ERROR_DIRECTORY;
        }
829 830 831 832 833 834
        while(Path[i] != '/' && i > 0)
        {
            --i;
        }
        ++Ancestors;
        Path[i] = '\0';
835
        if(i == 0) { return RC_ERROR_DIRECTORY; }
836 837 838 839 840 841 842 843 844 845 846
    }
    while(Ancestors > 0)
    {
        while(Path[i] != '\0')
        {
            ++i;
        }
        Path[i] = '/';
        --Ancestors;
        if((mkdir(Path, 00755)) == -1)
        {
847
            return RC_ERROR_DIRECTORY;
848 849
        }
    }
850 851 852 853
    return RC_SUCCESS;
}

void
854
LogUsage(buffer *Buffer)
855
{
Matt Mascarenhas's avatar
Matt Mascarenhas committed
856
#if DEBUG
Matt Mascarenhas's avatar
Matt Mascarenhas committed
857
    char LogPath[256];
858
    CopyString(LogPath, "%s/%s", Config.CacheDir, "buffers.log");
859 860 861
    FILE *LogFile;
    if(!(LogFile = fopen(LogPath, "a+")))
    {
862
        MakeDir(Config.CacheDir);
863 864 865 866 867 868 869 870
        if(!(LogFile = fopen(LogPath, "a+")))
        {
            perror("LogUsage");
            return;
        }
    }

    fprintf(LogFile, "%s,%ld,%d\n",
871 872 873
            Buffer->ID,
            Buffer->Ptr - Buffer->Location,
            Buffer->Size);
874
    fclose(LogFile);
Matt Mascarenhas's avatar
Matt Mascarenhas committed
875
#endif
876 877
}

878
__attribute__ ((format (printf, 2, 3)))
879
void
880
LogError(int LogLevel, char *Format, ...)
881 882 883
{
    if(Config.LogLevel >= LogLevel)
    {
Matt Mascarenhas's avatar
Matt Mascarenhas committed
884
        char LogPath[256];
885
        CopyString(LogPath, sizeof(LogPath), "%s/%s", Config.CacheDir, "errors.log");
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
        FILE *LogFile;
        if(!(LogFile = fopen(LogPath, "a+")))
        {
            MakeDir(Config.CacheDir);
            if(!(LogFile = fopen(LogPath, "a+")))
            {
                perror("LogUsage");
                return;
            }
        }

        va_list Args;
        va_start(Args, Format);
        vfprintf(LogFile, Format, Args);
        va_end(Args);
        // TODO(matt): Include the LogLevel "string" and the current wall time
        fprintf(LogFile, "\n");
        fclose(LogFile);
    }
905 906 907 908 909 910
}

void
FreeBuffer(buffer *Buffer)
{
    free(Buffer->Location);
911
    Buffer->Location = 0;
912 913 914 915 916 917
#if DEBUG_MEM
    FILE *MemLog = fopen("/home/matt/cinera_mem", "a+");
    fprintf(MemLog, "     Freed %s\n", Buffer->ID);
    fclose(MemLog);
    printf("     Freed %s\n", Buffer->ID);
#endif
918 919
}

920 921
#if 0
#define ClaimBuffer(MemoryArena, Buffer, ID, Size) if(__ClaimBuffer(MemoryArena, Buffer, ID, Size))\
922 923 924 925 926 927
{\
    fprintf(stderr, "%s:%d: MemoryArena cannot contain %s of size %d\n", __FILE__, __LINE__, ID, Size);\
    hmml_free(&HMML);\
    FreeBuffer(MemoryArena);\
    return 1;\
};
928
#endif
929 930

int
931
ClaimBuffer(buffer *Buffer, char *ID, int Size)
932
{
933
    if(MemoryArena.Ptr - MemoryArena.Location + Size > MemoryArena.Size)
934
    {
935
        return RC_ARENA_FULL;
936
    }
937
    Buffer->Location = (char *)MemoryArena.Ptr;
938 939
    Buffer->Size = Size;
    Buffer->ID = ID;
940
    MemoryArena.Ptr += Buffer->Size;
941 942 943
    *Buffer->Location = '\0';
    Buffer->Ptr = Buffer->Location;
#if DEBUG
944
    float PercentageUsed = (float)(MemoryArena.Ptr - MemoryArena.Location) / MemoryArena.Size * 100;
945
    printf("  ClaimBuffer(%s): %d\n"
946
           "    Total ClaimedMemory: %ld (%.2f%%, leaving %ld free)\n\n", Buffer->ID, Buffer->Size, MemoryArena.Ptr - MemoryArena.Location, PercentageUsed, MemoryArena.Size - (MemoryArena.Ptr - MemoryArena.Location));
947
#endif
948
    return RC_SUCCESS;
949 950 951
}

void
952
DeclaimBuffer(buffer *Buffer)
953 954
{
    *Buffer->Location = '\0';
955
    MemoryArena.Ptr -= Buffer->Size;
956 957
    float PercentageUsed = (float)(Buffer->Ptr - Buffer->Location) / Buffer->Size * 100;
#if DEBUG
958
    printf("DeclaimBuffer(%s)\n"
959 960
           "    Used: %ld / %d (%.2f%%)\n"
           "\n"
961
           "    Total ClaimedMemory: %ld\n\n",
962 963 964 965
           Buffer->ID,
           Buffer->Ptr - Buffer->Location,
           Buffer->Size,
           PercentageUsed,
966
           MemoryArena.Ptr - MemoryArena.Location);
967
#endif
968
    LogUsage(Buffer);
969 970 971 972 973 974 975
    if(PercentageUsed >= 95.0f)
    {
        // TODO(matt): Implement either dynamically growing buffers, or phoning home to [email protected]
        LogError(LOG_ERROR, "%s used %.2f%% of its allotted memory\n", Buffer->ID, PercentageUsed);
        fprintf(stderr, "\e[1;31mWarning\e[0m: %s used %.2f%% of its allotted memory\n", Buffer->ID, PercentageUsed);
    }
    else if(PercentageUsed >= 80.0f)
976 977
    {
        // TODO(matt): Implement either dynamically growing buffers, or phoning home to [email protected]
978
        LogError(LOG_ERROR, "%s used %.2f%% of its allotted memory\n", Buffer->ID, PercentageUsed);
979
        fprintf(stderr, "\e[0;33mWarning\e[0m: %s used %.2f%% of its allotted memory\n", Buffer->ID, PercentageUsed);
980
    }
Matt Mascarenhas's avatar
Matt Mascarenhas committed
981
    Buffer->Size = 0;
982 983
}

984 985
void
RewindBuffer(buffer *Buffer)
986 987 988 989 990 991 992 993 994 995 996 997 998
{
#if DEBUG
    float PercentageUsed = (float)(Buffer->Ptr - Buffer->Location) / Buffer->Size * 100;
    printf("Rewinding %s\n"
                    "    Used: %ld / %d (%.2f%%)\n\n",
                    Buffer->ID,
                    Buffer->Ptr - Buffer->Location,
                    Buffer->Size,
                    PercentageUsed);
#endif
    Buffer->Ptr = Buffer->Location;
}

999 1000 1001 1002 1003 1004 1005
enum
{
    TEMPLATE_INDEX,
    TEMPLATE_PLAYER,
    TEMPLATE_BESPOKE
} templates;

1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040
char *
GetDirectoryPath(char *Filepath)
{
    char *Ptr = Filepath + StringLength(Filepath) - 1;
    while(Ptr > Filepath && *Ptr != '/')
    {
        --Ptr;
    }
    if(Ptr == Filepath)
    {
        *Ptr++ = '.';
    }
    *Ptr = '\0';
    return Filepath;
}

char *
GetBaseFilename(char *Filepath,
        char *Extension // Including the "."
                        // Pass 0 to retain the whole file path, only without its parent directories
        )
{
    char *BaseFilename = Filepath + StringLength(Filepath) - 1;
    while(BaseFilename > Filepath && *BaseFilename != '/')
    {
        --BaseFilename;
    }
    if(*BaseFilename == '/')
    {
        ++BaseFilename;
    }
    BaseFilename[StringLength(BaseFilename) - StringLength(Extension)] = '\0';
    return BaseFilename;
}

1041 1042 1043
void
ConstructTemplatePath(template *Template, int TemplateType)
{
1044 1045 1046 1047
    // NOTE(matt): Bespoke template paths are set relative to:
    //                  in Project Edition: ProjectDir
    //                  in Single  Edition: Parent directory of .hmml file

1048 1049 1050
    if(Template->Metadata.Filename[0] != '/')
    {
        char Temp[256];
1051
        CopyString(Temp, sizeof(Temp), "%s", Template->Metadata.Filename);
1052
        char *Ptr = Template->Metadata.Filename;
1053
        char *End = Template->Metadata.Filename + sizeof(Template->Metadata.Filename);
1054 1055
        if(TemplateType == TEMPLATE_BESPOKE)
        {
1056 1057
            if(Config.Edition == EDITION_SINGLE)
            {
1058
                Ptr += CopyString(Ptr, End - Ptr, "%s/", GetDirectoryPath(Config.SingleHMMLFilePath));
1059 1060 1061
            }
            else
            {
1062
                Ptr += CopyString(Ptr, End - Ptr, "%s/", Config.ProjectDir);
1063
            }
1064 1065 1066
        }
        else
        {
1067
            Ptr += CopyString(Ptr, End - Ptr, "%s/", Config.TemplatesDir);
1068
        }
1069
        CopyString(Ptr, End - Ptr, "%s", Temp);
1070 1071 1072
    }
}

1073
int
1074
InitTemplate(template **Template)
1075
{
1076 1077 1078 1079
    if(MemoryArena.Ptr - MemoryArena.Location + sizeof(template) > MemoryArena.Size)
    {
        return RC_ARENA_FULL;
    }
1080
    *Template = (template *)MemoryArena.Ptr;
1081 1082 1083 1084
    Clear((*Template)->Metadata.Filename, 256); // NOTE(matt): template_metadata specifies Filename[256]
    (*Template)->Metadata.Validity = 0;
    (*Template)->Metadata.TagCount = 0;
    for(int i = 0; i < 16; ++i) // NOTE(matt): template_metadata specifies Tag[16]
1085
    {
1086 1087
        (*Template)->Metadata.Tag[i].Offset = 0;
        (*Template)->Metadata.Tag[i].TagCode = 0;
1088
    }
1089 1090 1091 1092 1093 1094 1095
    MemoryArena.Ptr += sizeof(template);
    return RC_SUCCESS;
}

int
ClaimTemplate(template **Template, char *Location, int TemplateType)
{
1096
    CopyString((*Template)->Metadata.Filename, sizeof((*Template)->Metadata.Filename), "%s", Location);
1097 1098 1099
    ConstructTemplatePath((*Template), TemplateType);

    if(TemplateType == TEMPLATE_BESPOKE)
1100
    {
1101
        fprintf(stderr, "\e[0;35mPacking\e[0m template: %s\n", (*Template)->Metadata.Filename);
1102
    }
1103 1104 1105 1106 1107 1108

    FILE *File;
    if(!(File = fopen((*Template)->Metadata.Filename, "r")))
    {
        LogError(LOG_ERROR, "Unable to open template %s: %s", (*Template)->Metadata.Filename, strerror(errno));
        fprintf(stderr, "Unable to open template %s: %s\n", (*Template)->Metadata.Filename, strerror(errno));
1109
        Clear((*Template)->Metadata.Filename, 256); // NOTE(matt): template_metadata specifies Filename[256]
1110 1111 1112 1113
        return RC_ERROR_FILE;
    }
    fseek(File, 0, SEEK_END);
    (*Template)->Buffer.Size = ftell(File);
1114
    if(MemoryArena.Ptr - MemoryArena.Location + (*Template)->Buffer.Size > MemoryArena.Size)
1115
    {
1116
        Clear((*Template)->Metadata.Filename, 256); // NOTE(matt): template_metadata specifies Filename[256]
1117 1118
        return RC_ARENA_FULL;
    }
1119

1120 1121 1122
    (*Template)->Buffer.Location = MemoryArena.Ptr;
    (*Template)->Buffer.Ptr = (*Template)->Buffer.Location;
    (*Template)->Buffer.ID = (*Template)->Metadata.Filename;
1123 1124 1125 1126 1127

    fseek(File, 0, SEEK_SET);
    fread((*Template)->Buffer.Location, (*Template)->Buffer.Size, 1, File);
    fclose(File);

1128 1129
    MemoryArena.Ptr += (*Template)->Buffer.Size;

1130
#if DEBUG
1131 1132
    printf("  ClaimTemplate(%s): %d\n"
           "    Total ClaimedMemory: %ld\n\n", (*Template)->Metadata.Filename, (*Template)->Buffer.Size, MemoryArena.Ptr - MemoryArena.Location);
1133 1134 1135 1136 1137
#endif
    return RC_SUCCESS;
}

int
1138
DeclaimTemplate(template *Template)
1139
{
1140 1141 1142 1143 1144 1145 1146 1147 1148 1149
    Clear(Template->Metadata.Filename, 256);
    Template->Metadata.Validity = 0;
    for(int i = 0; i < Template->Metadata.TagCount; ++i)
    {
        Template->Metadata.Tag[i].Offset = 0;
        Template->Metadata.Tag[i].TagCode = 0;
    }
    Template->Metadata.TagCount = 0;
    MemoryArena.Ptr -= (*Template).Buffer.Size;

1150
#if DEBUG
1151
    printf("DeclaimTemplate(%s)\n"
1152
           "    Total ClaimedMemory: %ld\n\n",
1153 1154
           (*Template).Metadata.Filename,
           MemoryArena.Ptr - MemoryArena.Location);
1155 1156 1157 1158
#endif
    return RC_SUCCESS;
}

1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
int
TimecodeToSeconds(char *Timecode)
{
    int HMS[3] = { 0, 0, 0 }; // 0 == Seconds; 1 == Minutes; 2 == Hours
    int Colons = 0;
    while(*Timecode)
    {
        //if((*Timecode < '0' || *Timecode > '9') && *Timecode != ':') { return FALSE; }

        if(*Timecode == ':')
        {
            ++Colons;
            //if(Colons > 2) { return FALSE; }
            for(int i = 0; i < Colons; ++i)
            {
                HMS[Colons - i] = HMS[Colons - (i + 1)];
            }
            HMS[0] = 0;
        }
        else
        {
            HMS[0] = HMS[0] * 10 + *Timecode - '0';
        }

        ++Timecode;
    }

    //if(HMS[0] > 59 || HMS[1] > 59 || Timecode[-1] == ':') { return FALSE; }

    return HMS[2] * 60 * 60 + HMS[1] * 60 + HMS[0];
}

1191 1192
typedef struct
{
1193 1194 1195
    unsigned int Hue:16;
    unsigned int Saturation:8;
    unsigned int Lightness:8;
1196 1197 1198
} hsl_colour;

hsl_colour
1199 1200 </