cinera.c 356 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

11
#include <stdint.h>
Matt Mascarenhas's avatar
Matt Mascarenhas committed
12 13
typedef struct
{
14
    uint32_t Major, Minor, Patch;
Matt Mascarenhas's avatar
Matt Mascarenhas committed
15 16 17 18
} version;

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

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

#define __USE_XOPEN2K8 // NOTE(matt): O_NOFOLLOW
#include <fcntl.h> // NOTE(matt): open()
#define __USE_XOPEN2K // NOTE(matt): readlink()
Matt Mascarenhas's avatar
Matt Mascarenhas committed
42
#include <unistd.h> // NOTE(matt): sleep()
43

44 45 46 47
typedef unsigned int bool;
#define TRUE 1
#define FALSE 0

48 49 50 51
#define enum8(type) int8_t
#define enum16(type) int16_t
#define enum32(type) int32_t

52 53 54 55
#define DEBUG 0
#define DEBUG_MEM 0

bool PROFILING = 0;
56 57 58 59
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);}

60 61 62
#define Kilobytes(Bytes) Bytes << 10
#define Megabytes(Bytes) Bytes << 20

63 64
#define MAX_PROJECT_ID_LENGTH 31
#define MAX_PROJECT_NAME_LENGTH 63
65
#define MAX_BASE_DIR_LENGTH 127
66 67 68 69
#define MAX_BASE_URL_LENGTH 127
#define MAX_RELATIVE_PAGE_LOCATION_LENGTH 31
#define MAX_PLAYER_URL_PREFIX_LENGTH 15

70 71 72 73
#define MAX_ROOT_DIR_LENGTH 127
#define MAX_ROOT_URL_LENGTH 127
#define MAX_RELATIVE_ASSET_LOCATION_LENGTH 31

74
#define MAX_BASE_FILENAME_LENGTH 31
75
#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 db_entry is 128 bytes total
76

77 78 79
#define MAX_ASSET_FILENAME_LENGTH 63

// TODO(matt): Stop distinguishing between short / long and lift the size limit once we're on the LUT
80 81 82
#define MAX_CUSTOM_SNIPPET_SHORT_LENGTH 255
#define MAX_CUSTOM_SNIPPET_LONG_LENGTH 1023

83 84 85 86
#define ArrayCount(A) sizeof(A)/sizeof(*(A))
#define Assert(Expression) if(!(Expression)) { printf("l.%d: \e[1;31mAssertion failure\e[0m\n", __LINE__); __asm__("int3"); }
#define FOURCC(String) ((uint32_t)(String[0] << 0) | (uint32_t)(String[1] << 8) | (uint32_t)(String[2] << 16) | (uint32_t)(String[3] << 24))

87 88
enum
{
89 90 91
    EDITION_SINGLE,
    EDITION_PROJECT,
    EDITION_NETWORK
92 93
} editions;

94 95 96 97 98 99 100 101 102 103 104 105 106
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;

107 108
enum
{
109 110 111 112 113
    MODE_FORCEINTEGRATION = 1 << 0,
    MODE_ONESHOT          = 1 << 1,
    MODE_EXAMINE          = 1 << 2,
    MODE_NOCACHE          = 1 << 3,
    MODE_NOPRIVACY        = 1 << 4,
114 115
    MODE_SINGLETAB        = 1 << 5,
    MODE_NOREVVEDRESOURCE = 1 << 6
116 117
} modes;

118 119 120 121 122 123 124 125 126
enum
{
    RC_ARENA_FULL,
    RC_ERROR_DIRECTORY,
    RC_ERROR_FATAL,
    RC_ERROR_FILE,
    RC_ERROR_HMML,
    RC_ERROR_MAX_REFS,
    RC_ERROR_MEMORY,
127
    RC_ERROR_PARSING,
Matt Mascarenhas's avatar
Matt Mascarenhas committed
128
    RC_ERROR_PROJECT,
129
    RC_ERROR_QUOTE,
Matt Mascarenhas's avatar
Matt Mascarenhas committed
130
    RC_ERROR_SEEK,
131 132 133
    RC_FOUND,
    RC_UNFOUND,
    RC_INVALID_REFERENCE,
Matt Mascarenhas's avatar
Matt Mascarenhas committed
134
    RC_INVALID_TEMPLATE,
135
    RC_PRIVATE_VIDEO,
136 137 138 139 140 141 142 143 144 145 146 147 148
    RC_NOOP,
    RC_RIP,
    RC_SUCCESS
} returns;

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

149 150 151
typedef struct
{
    // Universal
152 153 154 155 156
    char              CacheDir[256];
    enum8(editions)   Edition;
    enum8(log_levels) LogLevel;
    enum8(modes)      Mode;
    int               UpdateInterval;
157 158 159 160 161 162 163

    // 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}
164
    char *QueryString;
165 166 167 168 169 170 171 172 173

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

    // Per Project - Input
    char *ProjectDir; // Absolute
    char *TemplatesDir; // Absolute
174
    char *TemplateSearchLocation; // Relative to TemplatesDir ???
175 176 177 178 179
    char *TemplatePlayerLocation; // Relative to TemplatesDir ???

    // Per Project - Output
    char *BaseDir; // Absolute
    char *BaseURL;
180
    char *SearchLocation; // Relative to Base{Dir,URL}
181 182 183 184 185 186 187 188 189 190 191
    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;

192 193 194 195
typedef struct
{
    char *Location;
    char *Ptr;
196
    char *ID;
197 198 199
    int  Size;
} buffer;

200 201 202 203
typedef struct
{
    buffer Buffer;
    FILE  *Handle;
204
    char   Path[256]; // NOTE(matt): Could this just be a char *?
205 206 207
    int    FileSize;
} file_buffer;

208 209 210 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 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
char *AssetTypeNames[] =
{
    "Generic",
    "CSS",
    "Image",
    "JavaScript"
};

enum
{
    ASSET_GENERIC,
    ASSET_CSS,
    ASSET_IMG,
    ASSET_JS,
    ASSET_TYPE_COUNT
} asset_types;

typedef struct asset
{
    int32_t            Hash;
    enum8(asset_types) Type;
    char               Filename[MAX_ASSET_FILENAME_LENGTH + 1];
    int32_t            FilenameAt:29;
    int32_t            Known:1;
	int32_t            OffsetLandmarks:1;
    int32_t            DeferredUpdate:1;
    uint32_t           SearchLandmarkCapacity;
    uint32_t           SearchLandmarkCount;
    uint32_t           *SearchLandmark;
    uint32_t           PlayerLandmarkCapacity;
    uint32_t           PlayerLandmarkCount;
    uint32_t           *PlayerLandmark;
} asset;

asset BuiltinAssets[] =
{
    { 0, ASSET_CSS, "cinera.css" },
    { 0, ASSET_CSS }, // NOTE(matt): .Filename set by InitBuiltinAssets()
    { 0, ASSET_CSS, "cinera_topics.css" },
    { 0, ASSET_IMG, "cinera_icon_filter.png" },
    { 0, ASSET_JS,  "cinera_search.js" },
    { 0, ASSET_JS,  "cinera_player_pre.js" },
    { 0, ASSET_JS,  "cinera_player_post.js" },
};

enum
{
    ASSET_CSS_CINERA,
    ASSET_CSS_THEME,
    ASSET_CSS_TOPICS,
    ASSET_IMG_FILTER,
    ASSET_JS_SEARCH,
    ASSET_JS_PLAYER_PRE,
    ASSET_JS_PLAYER_POST,
    BUILTIN_ASSETS_COUNT,
} builtin_assets;

typedef struct
{
    int Count;
    int Capacity;
    asset *Asset;
} assets;

enum
{
    WT_HMML,
    WT_ASSET
} watch_types;

typedef struct
{
    int                 Descriptor;
    enum8(watch_types)  Type;
    char                Path[MAX_ROOT_DIR_LENGTH + 1 + MAX_RELATIVE_ASSET_LOCATION_LENGTH];
} watch_handle;

typedef struct
{
    int Count;
    int Capacity;
    watch_handle *Handle;
} watch_handles;

// DBVersion 1
typedef struct { unsigned int DBVersion; version AppVersion; version HMMLVersion; unsigned int EntryCount; } db_header1;
typedef struct { int Size; char BaseFilename[32]; } db_entry1;
typedef struct { file_buffer File; file_buffer Metadata; db_header1 Header; db_entry1 Entry; } database1;
//

// DBVersion 2
typedef struct { unsigned int DBVersion; version AppVersion; version HMMLVersion; unsigned int EntryCount; char SearchLocation[32]; char PlayerLocation[32]; } db_header2;
typedef db_entry1 db_entry2;
typedef struct { file_buffer File; file_buffer Metadata; db_header2 Header; db_entry2 Entry; } database2;
//

304 305 306
// TODO(matt): Increment CINERA_DB_VERSION!
typedef struct
{
307
    unsigned int       CurrentDBVersion;
308 309 310 311 312 313 314 315 316 317 318
    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];
319
    char               SearchLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
320
    char               PlayerLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
321 322
    char               PlayerURLPrefix[MAX_PLAYER_URL_PREFIX_LENGTH + 1];
} db_header3;
323 324 325 326 327

typedef struct
{
    unsigned int       PrevStart, NextStart;
    unsigned short int PrevEnd, NextEnd;
328
} link_insertion_offsets; // NOTE(matt): PrevStart is Absolute (or relative to start of file), the others are Relative to PrevStart
329 330 331 332 333 334 335

typedef struct
{
    link_insertion_offsets  LinkOffsets;
    unsigned short int      Size;
    char                    BaseFilename[MAX_BASE_FILENAME_LENGTH + 1];
    char                    Title[MAX_TITLE_LENGTH + 1];
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
} db_entry3;

typedef struct
{
    file_buffer File;
    file_buffer Metadata;
    db_header3 Header;
    db_entry3 Entry;
} database3;

#pragma pack(push, 1)
typedef struct
{
    uint32_t HexSignature; // 'CNRA'
    uint32_t CurrentDBVersion;
    version  CurrentAppVersion;
    version  CurrentHMMLVersion;

    uint32_t InitialDBVersion;
    version  InitialAppVersion;
    version  InitialHMMLVersion;

    uint32_t BlockCount;
} db_header4;

typedef struct
{
    uint32_t BlockID; // 'NTRY'
    uint16_t Count;
    char     ProjectID[MAX_PROJECT_ID_LENGTH + 1];
    char     ProjectName[MAX_PROJECT_NAME_LENGTH + 1];
    char     BaseURL[MAX_BASE_URL_LENGTH + 1];
    char     SearchLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
    char     PlayerLocation[MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
    char     PlayerURLPrefix[MAX_PLAYER_URL_PREFIX_LENGTH + 1]; // TODO(matt): Replace this with the OutputPath, when we add that
} db_header_entries4;

typedef db_entry3 db_entry4;

typedef struct
{
    uint32_t BlockID; // 'ASET'
    uint16_t Count;
    char     RootDir[MAX_ROOT_DIR_LENGTH + 1];
    char     RootURL[MAX_ROOT_URL_LENGTH + 1];
    char     CSSDir[MAX_RELATIVE_ASSET_LOCATION_LENGTH + 1];
    char     ImagesDir[MAX_RELATIVE_ASSET_LOCATION_LENGTH + 1];
    char     JSDir[MAX_RELATIVE_ASSET_LOCATION_LENGTH + 1];
} db_header_assets4;

typedef struct
{
    int32_t            Hash;
    uint32_t           LandmarkCount;
    enum8(asset_types) Type;
    char               Filename[MAX_ASSET_FILENAME_LENGTH + 1];
} db_asset4;

typedef struct
{
    int32_t  EntryIndex;
    uint32_t Position;
} db_landmark4;
399 400 401 402 403

typedef struct
{
    file_buffer File;
    file_buffer Metadata;
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422

    db_header4 Header;
    db_header_entries4 EntriesHeader;
    db_entry4 Entry;
    db_header_assets4 AssetsHeader;
    db_asset4 Asset;
    db_landmark4 Landmark;
} database4;
#pragma pack(pop)

#define CINERA_DB_VERSION 4

#define db_header db_header4
#define db_header_entries db_header_entries4
#define db_entry db_entry4
#define db_header_assets db_header_assets4
#define db_asset db_asset4
#define db_landmark db_landmark4
#define database database4
423 424
// TODO(matt): Increment CINERA_DB_VERSION!

425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
// NOTE(matt): Globals
arena MemoryArena;
config Config;
assets Assets;
int inotifyInstance;
watch_handles WatchHandles;
database DB;
time_t LastPrivacyCheck;
time_t LastQuoteFetch;
//

enum
{
    PAGE_TYPE_SEARCH = -1,
} page_type_indices;

441 442
typedef struct
{
443 444 445
    buffer IncludesSearch;
    buffer SearchEntry;
    buffer Search; // NOTE(matt): This buffer is malloc'd separately, rather than claimed from the memory_arena
446 447 448 449
    buffer IncludesPlayer;
    buffer Menus;
    buffer Player;
    buffer ScriptPlayer;
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467

    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];

468
    char ProjectID[MAX_PROJECT_ID_LENGTH + 1];
469
    char ProjectName[MAX_PROJECT_NAME_LENGTH + 1];
470
    char Theme[MAX_PROJECT_NAME_LENGTH + 1];
471
    char Title[MAX_TITLE_LENGTH + 1];
472
    char URLSearch[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1];
473
    char URLPlayer[MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1];
474
    char VideoID[16];
475
    char VODPlatform[16];
476 477
} buffers;

478 479
enum
{
480 481 482 483
    // Contents and Player Pages Mandatory
    TAG_INCLUDES,

    // Contents Page Mandatory
484
    TAG_SEARCH,
485

486
    // Player Page Mandatory
487 488 489 490
    TAG_MENUS,
    TAG_PLAYER,
    TAG_SCRIPT,

491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
    // 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,

510 511
    TAG_TITLE,
    TAG_VIDEO_ID,
512
    TAG_VOD_PLATFORM,
513 514

    // Anywhere Optional
515 516 517 518
    TAG_ASSET,
    TAG_CSS,
    TAG_IMAGE,
    TAG_JS,
519
    TAG_PROJECT,
520
    TAG_PROJECT_ID,
521
    TAG_SEARCH_URL,
522
    TAG_THEME,
523
    TAG_URL,
524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
    TEMPLATE_TAG_COUNT,
} template_tag_codes;

char *TemplateTags[] = {
    "__CINERA_INCLUDES__",

    "__CINERA_SEARCH__",

    "__CINERA_MENUS__",
    "__CINERA_PLAYER__",
    "__CINERA_SCRIPT__",

    "__CINERA_CUSTOM0__",
    "__CINERA_CUSTOM1__",
    "__CINERA_CUSTOM2__",
    "__CINERA_CUSTOM3__",
    "__CINERA_CUSTOM4__",
    "__CINERA_CUSTOM5__",
    "__CINERA_CUSTOM6__",
    "__CINERA_CUSTOM7__",
    "__CINERA_CUSTOM8__",
    "__CINERA_CUSTOM9__",
    "__CINERA_CUSTOM10__",
    "__CINERA_CUSTOM11__",

    "__CINERA_CUSTOM12__",
    "__CINERA_CUSTOM13__",
    "__CINERA_CUSTOM14__",
    "__CINERA_CUSTOM15__",

    "__CINERA_TITLE__",
    "__CINERA_VIDEO_ID__",
556
    "__CINERA_VOD_PLATFORM__",
557 558 559 560 561 562 563

    "__CINERA_ASSET__",
    "__CINERA_CSS__",
    "__CINERA_IMAGE__",
    "__CINERA_JS__",
    "__CINERA_PROJECT__",
    "__CINERA_PROJECT_ID__",
564
    "__CINERA_SEARCH_URL__",
565 566
    "__CINERA_THEME__",
    "__CINERA_URL__",
567 568 569 570 571
};

typedef struct
{
    int Offset;
572
    uint32_t AssetIndex;
573
    enum8(template_tag_codes) TagCode;
574 575 576 577 578
} tag_offset;

typedef struct
{
    int Validity; // NOTE(matt): Bitmask describing which page the template is valid for, i.e. contents and / or player page
579
    int TagCapacity;
580
    int TagCount;
581
    tag_offset *Tags;
582 583 584 585
} template_metadata;

typedef struct
{
586
    file_buffer File;
587
    template_metadata Metadata;
588 589
} template;

590 591 592 593 594 595 596 597
// 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;

598 599
typedef struct
{
600 601 602 603
    char Timecode[8];
    int Identifier;
} identifier;

604
#define MAX_REF_IDENTIFIER_COUNT 64
605 606
typedef struct
{
607 608 609 610
    char RefTitle[620];
    char ID[512];
    char URL[512];
    char Source[256];
611
    identifier Identifier[MAX_REF_IDENTIFIER_COUNT];
612
    int IdentifierCount;
613 614
} ref_info;

615 616
typedef struct
{
617 618
    char Marker[32];
    char WrittenText[32];
619
} category_info;
620

621 622 623 624 625 626
typedef struct
{
    category_info Category[64];
    int Count;
} categories;

627 628 629 630 631 632 633 634 635 636 637 638 639
char *SupportIcons[] =
{
    "cinera_sprite_patreon.png",
    "cinera_sprite_sendowl.png",
};

typedef enum
{
    ICON_PATREON = BUILTIN_ASSETS_COUNT,
    ICON_SENDOWL,
    SUPPORT_ICON_COUNT,
} support_icons;

640
// TODO(matt): Parse this stuff out of a config file
641 642
typedef struct
{
643 644 645 646 647
    char                *Username;
    char                *CreditedName;
    char                *HomepageURL;
    enum8(support_icons) SupportIconIndex;
    char                *SupportURL;
648 649 650
} credential_info;

credential_info Credentials[] =
651
{
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694
    { "a_waterman", "Andrew Waterman", "https://www.linkedin.com/in/andrew-waterman-76805788" },
    { "y_lee", "Yunsup Lee", "https://www.linkedin.com/in/yunsup-lee-385b692b/" },
    { "AndrewJDR", "Andrew Johnson" },
    { "AsafGartner", "Asaf Gartner" },
    { "BretHudson", "Bret Hudson", "http://www.brethudson.com/", ICON_PATREON, "https://www.patreon.com/indieFunction"},
    { "ChronalDragon", "Andrew Chronister", "http://chronal.net/" },
    { "Kelimion", "Jeroen van Rijn", "https://handmade.network/home" },
    { "Mannilie", "Emmanuel Vaccaro", "http://emmanuelvaccaro.com/" },
    { "Miblo", "Matt Mascarenhas", "https://miblodelcarpio.co.uk/", ICON_SENDOWL, "https://miblodelcarpio.co.uk/cinera#pledge"},
    { "Mr4thDimention", "Allen Webster", "http://www.4coder.net/" },
    { "Pseudonym73", "Andrew Bromage", "https://twitter.com/deguerre" },
    { "Quel_Solaar", "Eskil Steenberg", "http://quelsolaar.com/" },
    { "ZedZull", "Jay Waggle" },
    { "abnercoimbre", "Abner Coimbre", "https://handmade.network/m/abnercoimbre" },
    { "brianwill", "Brian Will", "http://brianwill.net/blog/" },
    { "cbloom", "Charles Bloom", "http://cbloomrants.blogspot.co.uk/" },
    { "cmuratori", "Casey Muratori", "https://handmadehero.org", ICON_SENDOWL, "https://handmadehero.org/fund"},
    { "csnover", "Colin Snover", "https://zetafleet.com/" },
    { "debiatan", "Miguel Lechón", "http://blog.debiatan.net/" },
    { "dspecht", "Dustin Specht" },
    { "effect0r", "Cory Henderlite" },
    { "ffsjs", "ffsjs" },
    { "fierydrake", "Mike Tunnicliffe" },
    { "garlandobloom", "Matthew VanDevander", "https://lowtideproductions.com/", ICON_PATREON, "https://www.patreon.com/mv"},
    { "ikerms", "Iker Murga" },
    { "insofaras", "Alex Baines", "https://abaines.me.uk/" },
    { "jacebennett", "Jace Bennett" },
    { "jon", "Jonathan Blow", "http://the-witness.net/news/" },
    { "jpike", "Jacob Pike" },
    { "martincohen", "Martin Cohen", "http://blog.coh.io/" },
    { "miotatsu", "Mio Iwakura", "http://riscy.tv/", ICON_PATREON, "https://patreon.com/miotatsu"},
    { "nothings", "Sean Barrett", "https://nothings.org/" },
    { "pervognsen", "Per Vognsen", "https://github.com/pervognsen/bitwise/" },
    { "philipbuuck", "Philip Buuck", "http://philipbuuck.com/" },
    { "powerc9000", "Clay Murray", "http://claymurray.website/" },
    { "rygorous", "Fabian Giesen", "https://fgiesen.wordpress.com/" },
    { "schme", "Kasper Sauramo" },
    { "sssmcgrath", "Shawn McGrath", "http://www.dyadgame.com/" },
    { "thehappiecat", "Anne", "https://www.youtube.com/c/TheHappieCat", ICON_PATREON, "https://www.patreon.com/thehappiecat"},
    { "theinternetftw", "Ben Craddock" },
    { "wheatdog", "Tim Liou", "http://stringbulbs.com/" },
    { "williamchyr", "William Chyr", "http://williamchyr.com/" },
    { "wonchun", "Won Chun", "https://twitter.com/won3d" },
695 696 697 698 699 700 701 702 703 704 705 706
};

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

category_medium CategoryMedium[] =
{
    // medium        icon         written name
707
    { "admin",       "&#128505;", "Administrivia"},
708
    { "afk",         "&#8230;"  , "Away from Keyboard"},
709
    { "authored",    "&#128490;", "Chat Comment"},
710
    { "blackboard",  "&#128396;", "Blackboard"},
711
    { "drawing",     "&#127912;", "Drawing"},
712
    { "experience",  "&#127863;", "Experience"},
713 714
    { "hat",         "&#127913;", "Hat"},
    { "multimedia",  "&#127916;", "Media Clip"},
715
    { "owl",         "&#129417;", "Owl of Shame"},
716
    { "programming", "&#128430;", "Programming"},
717 718
    { "rant",        "&#128162;", "Rant"},
    { "research",    "&#128214;", "Research"},
719
    { "run",         "&#127939;", "In-Game"}, // TODO(matt): Potentially make this written name configurable per project
720
    { "speech",      "&#128489;", "Speech"},
721
    { "trivia",      "&#127922;", "Trivia"},
722 723
};

Matt Mascarenhas's avatar
Matt Mascarenhas committed
724 725 726 727 728 729 730 731 732
enum
{
    NS_CALENDRICAL,
    NS_LINEAR,
    NS_SEASONAL,
} numbering_schemes;

typedef struct
{
733 734 735 736 737 738 739
    char                    *ProjectID;
    char                    *FullName;
    char                    *Unit; // e.g. Day, Episode, Session
    enum8(numbering_schemes) 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
740 741 742 743
} project_info;

project_info ProjectInfo[] =
{
744 745
    { "bitwise", "Bitwise", "Day", NS_LINEAR, "programming", "" },

746 747 748 749 750
    { "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",      "" },
751

752 753 754 755 756
    { "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", "" },
757

758 759 760 761
    { "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", "" },
762

763
    { "obbg", "Open Block Building Game", "Episode", NS_LINEAR, "programming", "" },
764

765
    { "sysadmin", "SysAdmin", "Session", NS_LINEAR, "admin", "" },
Matt Mascarenhas's avatar
Matt Mascarenhas committed
766 767
};

768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
char *ColourStrings[] =
{
    "\e[0m",
    "\e[0;33m",
    "\e[0;34m",
    "\e[0;35m", "\e[0;35m",
    "\e[1;30m", "\e[1;30m",
    "\e[1;31m",
    "\e[1;32m", "\e[1;32m",
    "\e[1;33m",
};

enum
{
    CS_END,
    CS_WARNING,
    CS_PRIVATE,
    CS_ONGOING, CS_PURPLE,
    CS_COMMENT, CS_DELETION,
    CS_ERROR,
    CS_ADDITION, CS_SUCCESS,
    CS_REINSERTION,
} colour_strings;

typedef struct
{
    char *Name;
    enum8(colour_strings) Colour;
} edit_type;

enum
{
    EDIT_INSERTION,
    EDIT_APPEND,
    EDIT_REINSERTION,
    EDIT_DELETION,
    EDIT_ADDITION,
} edit_types;

edit_type EditTypes[] =
{
    { "Inserted", CS_ADDITION },
    { "Appended", CS_ADDITION },
    { "Reinserted", CS_REINSERTION },
    { "Deleted", CS_DELETION },
    { "Added", CS_ADDITION }
};
815

816 817
void
Clear(char *String, int Size)
818
{
819 820 821 822
    for(int i = 0; i < Size; ++i)
    {
        String[i] = 0;
    }
823 824
}

825
int
826
StringLength(char *String)
827
{
828 829
    int i = 0;
    while(String[i])
830
    {
831
        ++i;
832
    }
833
    return i;
834 835
}

836 837 838 839
#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, ...)
840
{
841 842 843 844 845
    int Length = 0;
    va_list Args;
    va_start(Args, Format);
    Length = vsnprintf(Dest, DestSize, Format, Args);
    if(Length >= DestSize)
846
    {
847 848
        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");
849
    }
850 851
    va_end(Args);
    return Length;
852 853
}

854
#define CopyStringNoFormat(Dest, DestSize, String) CopyStringNoFormat_(__LINE__, Dest, DestSize, String)
855
int
856
CopyStringNoFormat_(int LineNumber, char *Dest, int DestSize, char *String)
857
{
858
    int Length = 0;
859
    char *Start = String;
860 861 862
    while(*String)
    {
        *Dest++ = *String++;
863
        ++Length;
864
    }
865 866 867 868 869 870
    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");
    }
871
    *Dest = '\0';
872
    return Length;
873 874
}

875
#define ClearCopyStringNoFormat(Dest, DestSize, String) ClearCopyStringNoFormat_(__LINE__, Dest, DestSize, String)
876
int
877
ClearCopyStringNoFormat_(int LineNumber, char *Dest, int DestSize, char *String)
878 879
{
    Clear(Dest, DestSize);
880 881 882 883 884 885 886 887 888 889 890 891 892 893 894
    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;
895 896
}

897
// TODO(matt): Maybe do a version of this that takes a string as a Terminator
898
#define CopyStringNoFormatT(Dest, DestSize, String, Terminator) CopyStringNoFormatT_(__LINE__, Dest, DestSize, String, Terminator)
899
int
900
CopyStringNoFormatT_(int LineNumber, char *Dest, int DestSize, char *String, char Terminator)
901 902
{
    int Length = 0;
903
    char *Start = String;
904 905 906 907 908
    while(*String != Terminator)
    {
        *Dest++ = *String++;
        ++Length;
    }
909 910 911 912 913 914
    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");
    }
915 916 917 918
    *Dest = '\0';
    return Length;
}

919 920
#define CopyStringToBuffer(Dest, Format, ...) CopyStringToBuffer_(__LINE__, Dest, Format, ##__VA_ARGS__)
__attribute__ ((format (printf, 3, 4)))
921
void
922
CopyStringToBuffer_(int LineNumber, buffer *Dest, char *Format, ...)
923
{
924 925
    va_list Args;
    va_start(Args, Format);
926
    int Length = vsnprintf(Dest->Ptr, Dest->Size - (Dest->Ptr - Dest->Location), Format, Args);
927
    va_end(Args);
928
    if(Length + (Dest->Ptr - Dest->Location) >= Dest->Size)
929
    {
930 931 932
        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");
933
    }
934
    Dest->Ptr += Length;
935 936
}

937
#define CopyStringToBufferNoFormat(Dest, String) CopyStringToBufferNoFormat_(__LINE__, Dest, String)
938
void
939
CopyStringToBufferNoFormat_(int LineNumber, buffer *Dest, char *String)
940
{
941
    char *Start = String;
942 943 944 945
    while(*String)
    {
        *Dest->Ptr++ = *String++;
    }
946 947 948 949 950 951
    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");
    }
952 953 954
    *Dest->Ptr = '\0';
}

955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972
#define CopyStringToBufferNoFormatL(Dest, Length, String) CopyStringToBufferNoFormatL_(__LINE__, Dest, Length, String)
void
CopyStringToBufferNoFormatL_(int LineNumber, buffer *Dest, int Length, char *String)
{
    char *Start = String;
    for(int i = 0; i < Length; ++i)
    {
        *Dest->Ptr++ = *String++;
    }
    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");
    }
    *Dest->Ptr = '\0';
}

973
#define CopyStringToBufferHTMLSafe(Dest, String) CopyStringToBufferHTMLSafe_(__LINE__, Dest, String)
974
void
975
CopyStringToBufferHTMLSafe_(int LineNumber, buffer *Dest, char *String)
976
{
977 978
    char *Start = String;
    int Length = StringLength(String);
979 980 981 982
    while(*String)
    {
        switch(*String)
        {
983 984 985 986 987
            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;
988 989 990 991
            default: *Dest->Ptr++ = *String; break;
        }
        ++String;
    }
992 993 994 995 996 997 998
    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';
999 1000
}

1001
#define CopyStringToBufferHTMLSafeBreakingOnSlash(Dest, String) CopyStringToBufferHTMLSafeBreakingOnSlash_(__LINE__, Dest, String)
1002
void
1003
CopyStringToBufferHTMLSafeBreakingOnSlash_(int LineNumber, buffer *Dest, char *String)
1004
{
1005 1006
    char *Start = String;
    int Length = StringLength(String);
1007 1008 1009 1010
    while(*String)
    {
        switch(*String)
        {
1011 1012 1013 1014 1015 1016
            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;
1017 1018 1019
            default: *Dest->Ptr++ = *String; break;
        }
        ++String;
1020
    }
1021 1022
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059
        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");
    }
    *Dest->Ptr = '\0';
}

#define CopyStringToBufferHTMLPercentEncoded(Dest, String) CopyStringToBufferHTMLPercentEncoded_(__LINE__, Dest, String)
void
CopyStringToBufferHTMLPercentEncoded_(int LineNumber, buffer *Dest, char *String)
{
    char *Start = String;
    int Length = StringLength(String);
    while(*String)
    {
        switch(*String)
        {
            case ' ':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '2'; *Dest->Ptr++ = '0'; Length += 2; break;
            case '\"': *Dest->Ptr++ = '%'; *Dest->Ptr++ = '2'; *Dest->Ptr++ = '2'; Length += 2; break;
            case '%':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '2'; *Dest->Ptr++ = '5'; Length += 2; break;
            case '&':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '2'; *Dest->Ptr++ = '6'; Length += 2; break;
            case '<':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '3'; *Dest->Ptr++ = 'C'; Length += 2; break;
            case '>':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '3'; *Dest->Ptr++ = 'E'; Length += 2; break;
            case '?':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '3'; *Dest->Ptr++ = 'F'; Length += 2; break;
            case '\\': *Dest->Ptr++ = '%'; *Dest->Ptr++ = '5'; *Dest->Ptr++ = 'C'; Length += 2; break;
            case '^':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '5'; *Dest->Ptr++ = 'E'; Length += 2; break;
            case '`':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '6'; *Dest->Ptr++ = '0'; Length += 2; break;
            case '{':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '7'; *Dest->Ptr++ = 'B'; Length += 2; break;
            case '|':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '7'; *Dest->Ptr++ = 'C'; Length += 2; break;
            case '}':  *Dest->Ptr++ = '%'; *Dest->Ptr++ = '7'; *Dest->Ptr++ = 'D'; Length += 2; break;
            default: *Dest->Ptr++ = *String; break;
        }
        ++String;
    }
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
        fprintf(stderr, "CopyStringToBufferHTMLPercentEncodedL(%s) call on line %d cannot accommodate %d(+1)-character percent-encoded string:\n"
1060 1061 1062
                "%s\n", Dest->ID, LineNumber, Length, Start);
        __asm__("int3");
    }
1063
    *Dest->Ptr = '\0';
1064 1065
}

1066
#define CopyBuffer(Dest, Src) CopyBuffer_(__LINE__, Dest, Src)
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1067
void
1068
CopyBuffer_(int LineNumber, buffer *Dest, buffer *Src)
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1069
{
1070 1071
    Src->Ptr = Src->Location;
    while(*Src->Ptr)
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1072
    {
1073
        *Dest->Ptr++ = *Src->Ptr++;
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1074
    }
1075 1076
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
1077
        fprintf(stderr, "CopyBuffer(%s) call on line %d cannot accommodate %d(+1)-character %s\n", Dest->ID, LineNumber, StringLength(Src->Location), Src->ID);
1078 1079
        __asm__("int3");
    }
1080
    *Dest->Ptr = '\0';
1081 1082
}

1083
#define CopyBufferSized(Dest, Src, Size) CopyBufferSized_(__LINE__, Dest, Src, Size)
1084
void
1085
CopyBufferSized_(int LineNumber, buffer *Dest, buffer *Src, int Size)
1086
{
1087
    // NOTE(matt): Similar to CopyBuffer(), just without null-terminating
1088
    Src->Ptr = Src->Location;
1089
    while(Src->Ptr - Src->Location < Size)
1090 1091 1092 1093 1094
    {
        *Dest->Ptr++ = *Src->Ptr++;
    }
    if(Dest->Ptr - Dest->Location >= Dest->Size)
    {
1095
        fprintf(stderr, "CopyBufferNoNull(%s) call on line %d cannot accommodate %d(+1)-character %s\n", Dest->ID, LineNumber, StringLength(Src->Location), Src->ID);
1096 1097
        __asm__("int3");
    }
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1098 1099
}

1100
int
1101
StringsDiffer(char *A, char *B) // NOTE(matt): Two null-terminated strings
1102
{
1103
    while(*A && *B && *A == *B)
1104 1105 1106 1107 1108 1109
    {
        ++A, ++B;
    }
    return *A - *B;
}

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121
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;
}

1122
bool
1123 1124
StringsDifferT(char *A, // NOTE(matt): Null-terminated string
               char *B, // NOTE(matt): Not null-terminated string (e.g. one mid-buffer)
1125
               char Terminator // NOTE(matt): Caller definable terminator. Pass 0 to only match on the extent of A
1126
               )
1127
{
1128
    // TODO(matt): Make sure this can't crash upon reaching the end of B's buffer
1129
    int ALength = StringLength(A);
1130
    int i = 0;
1131
    while(i < ALength && A[i] && A[i] == B[i])
1132 1133 1134
    {
        ++i;
    }
1135
    if((!Terminator && !A[i] && ALength == i) ||
1136
       (!A[i] && ALength == i && (B[i] == Terminator)))
1137 1138 1139 1140 1141 1142 1143 1144 1145
    {
        return FALSE;
    }
    else
    {
        return TRUE;
    }
}

1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 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 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
enum
{
    C_SEEK_FORWARDS,
    C_SEEK_BACKWARDS
} seek_directions;

enum
{
    C_SEEK_START,     // First character of string
    C_SEEK_BEFORE,    // Character before first character
    C_SEEK_END,       // Last character of string
    C_SEEK_AFTER      // Character after last character
} seek_positions;

int
SeekBufferForString(buffer *Buffer, char *String,
        enum8(seek_directions) Direction,
        enum8(seek_positions) Position)
{
    // TODO(matt): Optimise? Some means of analysing the String to increment
    // the pointer in bigger strides

    // Perhaps count up runs of consecutive chars and seek for the char with
    // the longest run, in strides of that run-length

    char *InitialLocation = Buffer->Ptr;
    if(Direction == C_SEEK_FORWARDS)
    {
        while(Buffer->Ptr - Buffer->Location < Buffer->Size - StringLength(String)
                && StringsDifferT(String, Buffer->Ptr, 0))
        {
            ++Buffer->Ptr;
        }
    }
    else
    {
        while(Buffer->Ptr > Buffer->Location
                && StringsDifferT(String, Buffer->Ptr, 0))
        {
            --Buffer->Ptr;
        }
    }

    if(StringsDifferT(String, Buffer->Ptr, 0))
    {
        Buffer->Ptr = InitialLocation;
        return RC_UNFOUND;
    }

    switch(Position)
    {
        case C_SEEK_START:
            break;
        case C_SEEK_BEFORE:
            if(Buffer->Ptr > Buffer->Location)
            {
                --Buffer->Ptr;
                break;
            }
            else
            {
                return RC_ERROR_SEEK; // Ptr remains at string start
            }
        case C_SEEK_END:
            Buffer->Ptr += StringLength(String) - 1;
            break;
        case C_SEEK_AFTER:
            if(Buffer->Size >= Buffer->Ptr - Buffer->Location + StringLength(String))
            {
                Buffer->Ptr += StringLength(String);
                break;
            }
            else
            {
                return RC_ERROR_SEEK; // Ptr remains at string start
                                      // NOTE(matt): Should it, however, be left at the end of the string?
            }
    }
    return RC_SUCCESS;
}

int
StripComponentFromPath(char *Path)
{
    char *Ptr = Path + StringLength(Path) - 1;
    if(Ptr < Path) { return RC_ERROR_DIRECTORY; }
    while(Ptr > Path && *Ptr != '/')
    {
        --Ptr;
    }
    *Ptr = '\0';
    return RC_SUCCESS;
}

int ClaimBuffer(buffer *Buffer, char *ID, int Size);
void DeclaimBuffer(buffer *Buffer);

int
ResolvePath(char *Path)
{
    buffer B;
    ClaimBuffer(&B, "ResolvedPath", StringLength(Path) + 1);
    CopyStringToBufferNoFormat(&B, Path);

    B.Ptr = B.Location;
    while(SeekBufferForString(&B, "/../", C_SEEK_FORWARDS, C_SEEK_END) == RC_SUCCESS)
    {
        char *NextComponentHead = B.Ptr;
        int RemainingChars = StringLength(NextComponentHead);
        --B.Ptr;
        SeekBufferForString(&B, "/", C_SEEK_BACKWARDS, C_SEEK_BEFORE);
        SeekBufferForString(&B, "/", C_SEEK_BACKWARDS, C_SEEK_START);
        CopyStringToBufferNoFormat(&B, NextComponentHead);
        Clear(B.Ptr, B.Size - (B.Ptr - B.Location));
        B.Ptr -= RemainingChars;
    }

    CopyStringNoFormat(Path, StringLength(Path) + 1, B.Location);

    DeclaimBuffer(&B);
    return RC_SUCCESS;
}

1269 1270 1271
int
MakeDir(char *Path)
{
1272
    // TODO(matt): Correctly check for permissions
1273 1274 1275 1276
    int i = StringLength(Path);
    int Ancestors = 0;
    while(mkdir(Path, 00755) == -1)
    {
1277 1278 1279 1280
        if(errno == EACCES)
        {
            return RC_ERROR_DIRECTORY;
        }
1281
        if(StripComponentFromPath(Path) == RC_ERROR_DIRECTORY) { return RC_ERROR_DIRECTORY; }
1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293
        ++Ancestors;
    }
    while(Ancestors > 0)
    {
        while(Path[i] != '\0')
        {
            ++i;
        }
        Path[i] = '/';
        --Ancestors;
        if((mkdir(Path, 00755)) == -1)
        {
1294
            return RC_ERROR_DIRECTORY;
1295 1296
        }
    }
1297 1298 1299 1300
    return RC_SUCCESS;
}

void
1301
LogUsage(buffer *Buffer)
1302
{
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1303
#if DEBUG
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1304
    char LogPath[256];
1305
    CopyString(LogPath, "%s/%s", Config.CacheDir, "buffers.log");
1306 1307 1308
    FILE *LogFile;
    if(!(LogFile = fopen(LogPath, "a+")))
    {
1309
        MakeDir(Config.CacheDir);
1310 1311 1312 1313 1314 1315 1316 1317
        if(!(LogFile = fopen(LogPath, "a+")))
        {
            perror("LogUsage");
            return;
        }
    }

    fprintf(LogFile, "%s,%ld,%d\n",
1318 1319 1320
            Buffer->ID,
            Buffer->Ptr - Buffer->Location,
            Buffer->Size);
1321
    fclose(LogFile);
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1322
#endif
1323 1324
}

1325
__attribute__ ((format (printf, 2, 3)))
1326
void
1327
LogError(int LogLevel, char *Format, ...)
1328 1329 1330
{
    if(Config.LogLevel >= LogLevel)
    {
Matt Mascarenhas's avatar
Matt Mascarenhas committed
1331
        char LogPath[256];
1332
        CopyString(LogPath, sizeof(LogPath), "%s/%s", Config.CacheDir, "errors.log");
1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351
        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);
    }
1352 1353
}

1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381
int
ReadFileIntoBuffer(file_buffer *File, int BufferPadding)
{
    if(!(File->Handle = fopen(File->Path, "r"))) // TODO(matt): Fuller error handling
    {
        return RC_ERROR_FILE;
    }

    fseek(File->Handle, 0, SEEK_END);
    File->FileSize = ftell(File->Handle);
    File->Buffer.Size = File->FileSize + 1 + BufferPadding; // NOTE(matt): +1 to accommodate a NULL terminator
    fseek(File->Handle, 0, SEEK_SET);

    // TODO(matt): Consider using the MemoryArena? Maybe have separate ReadFileIntoMemory() and ReadFileIntoArena()
    if(!(File->Buffer.Location = malloc(File->Buffer.Size)))
    {
        fclose(File->Handle);
        return RC_ERROR_MEMORY;
    }
    File->Buffer.Ptr = File->Buffer.Location;

    fread(File->Buffer.Location, File->FileSize, 1, File->Handle);
    File->Buffer.Location[File->FileSize] = '\0';
    fclose(File->Handle);
    File->Buffer.ID = File->Path;
    return RC_SUCCESS;
}

1382 1383 1384 1385
void
FreeBuffer(buffer *Buffer)
{
    free(Buffer->Location);
1386
    Buffer->Location = 0;
1387 1388
    Buffer->Ptr = 0;
    Buffer->Size = 0;
1389 1390 1391 1392 1393 1394
#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
1395
    Buffer->ID = 0;
1396 1397 1398
}

int
1399
ClaimBuffer(buffer *Buffer, char *ID, int Size)
1400
{
1401
    if(MemoryArena.Ptr - MemoryArena.Location + Size > MemoryArena.Size)
1402
    {
1403
        return RC_ARENA_FULL;
1404
    }
1405
    Buffer->Location = (char *)MemoryArena.Ptr;
1406 1407
    Buffer->Size = Size;
    Buffer->ID = ID;
1408
    MemoryArena.Ptr += Buffer->Size;
1409 1410 1411
    *Buffer->Location = '\0';
    Buffer->Ptr = Buffer->Location;
#if DEBUG
1412
    float PercentageUsed = (float)(MemoryArena.Ptr - MemoryArena.Location) / MemoryArena.Size * 100;
1413
    printf("  ClaimBuffer(%s): %d\n"
1414
           "    Total ClaimedMemory: %ld (%.2f%%, leaving %ld free)\n\n", Buffer->ID, Buffer->Size, MemoryArena.Ptr - MemoryArena.Location, PercentageUsed, MemoryArena.Size - (MemoryArena.Ptr - MemoryArena.Location));
1415
#endif
1416
    return RC_SUCCESS;
1417 1418 1419
}

void
1420
DeclaimBuffer(buffer *Buffer)
1421 1422
{
    *Buffer->Location = '\0';
1423
    MemoryArena.Ptr -= Buffer->Size;
1424 1425
    float PercentageUsed = (float)(Buffer->Ptr - Buffer->Location) / Buffer->Size * 100;
#if DEBUG
1426
    printf("DeclaimBuffer(%s)\n"
1427 1428
           "    Used: %ld / %d (%.2f%%)\n"
           "\n"
1429
           "    Total ClaimedMemory: %ld\n\n",
1430