2017-03-10 14:19:25 +00:00
#if 0
ctime - begin $ { 0 % . * } . ctm
2018-02-21 21:50:23 +00:00
# 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
2017-03-10 14:19:25 +00:00
ctime - end $ { 0 % . * } . ctm
exit
# endif
2017-11-11 00:34:47 +00:00
typedef struct
{
unsigned int Major , Minor , Patch ;
} version ;
version CINERA_APP_VERSION = {
. Major = 0 ,
. Minor = 5 ,
2018-04-07 05:30:28 +00:00
. Patch = 44
2017-11-11 00:34:47 +00:00
} ;
2017-12-24 01:57:11 +00:00
// TODO(matt): Copy in the DB 3 stuff from cinera_working.c
2018-02-21 21:50:23 +00:00
# define CINERA_DB_VERSION 3
2017-10-18 21:07:29 +00:00
2017-04-13 00:21:04 +00:00
# include <stdarg.h> // NOTE(matt): varargs
# include <stdio.h> // NOTE(matt): printf, sprintf, vsprintf, fprintf, perror
# include <stdlib.h> // NOTE(matt): calloc, malloc, free
2017-03-23 00:34:59 +00:00
# include "hmmlib.h"
2017-06-25 18:05:58 +00:00
# include <getopt.h> // NOTE(matt): getopts
2017-05-25 20:28:52 +00:00
//#include "config.h" // TODO(matt): Implement config.h
2017-08-09 00:57:09 +00:00
# include <curl/curl.h>
2017-08-10 01:05:41 +00:00
# include <time.h>
# include <sys/stat.h>
2017-08-23 20:30:08 +00:00
# include <sys/types.h>
# include <dirent.h>
2017-09-07 21:41:08 +00:00
# include <string.h> // NOTE(matt): strerror
# include <errno.h> //NOTE(matt): errno
2017-09-15 01:11:39 +00:00
# include <sys/inotify.h> // NOTE(matt): inotify
# include <unistd.h> // NOTE(matt): sleep()
2017-03-23 00:34:59 +00:00
2018-04-01 20:58:53 +00:00
typedef unsigned int bool ;
# define TRUE 1
# define FALSE 0
# define DEBUG 0
# define DEBUG_MEM 0
bool PROFILING = 0 ;
2018-03-06 20:40:12 +00:00
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);}
2017-05-24 21:56:36 +00:00
# define Kilobytes(Bytes) Bytes << 10
# define Megabytes(Bytes) Bytes << 20
2018-02-21 21:50:23 +00:00
# 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
2018-02-23 23:36:42 +00:00
# define MAX_CUSTOM_SNIPPET_SHORT_LENGTH 255
# define MAX_CUSTOM_SNIPPET_LONG_LENGTH 1023
2017-06-10 16:47:47 +00:00
enum
{
2017-08-19 21:41:51 +00:00
EDITION_SINGLE ,
EDITION_PROJECT ,
EDITION_NETWORK
2017-08-29 20:35:28 +00:00
} editions ;
2017-09-07 21:41:08 +00:00
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 ;
2017-08-29 20:35:28 +00:00
enum
{
2018-04-01 20:58:53 +00:00
MODE_FORCEINTEGRATION = 1 < < 0 ,
MODE_ONESHOT = 1 < < 1 ,
MODE_EXAMINE = 1 < < 2 ,
MODE_NOCACHE = 1 < < 3 ,
MODE_NOPRIVACY = 1 < < 4 ,
2017-08-29 20:35:28 +00:00
} modes ;
2017-09-07 21:41:08 +00:00
enum
{
RC_ARENA_FULL ,
RC_ERROR_DIRECTORY ,
RC_ERROR_FATAL ,
RC_ERROR_FILE ,
RC_ERROR_HMML ,
RC_ERROR_MAX_REFS ,
RC_ERROR_MEMORY ,
2017-11-11 00:34:47 +00:00
RC_ERROR_PROJECT ,
2017-09-07 21:41:08 +00:00
RC_ERROR_QUOTE ,
2017-11-11 00:34:47 +00:00
RC_ERROR_SEEK ,
2017-09-07 21:41:08 +00:00
RC_FOUND ,
RC_UNFOUND ,
RC_INVALID_REFERENCE ,
2017-11-11 00:34:47 +00:00
RC_INVALID_TEMPLATE ,
2018-04-01 20:58:53 +00:00
RC_PRIVATE_VIDEO ,
2017-09-07 21:41:08 +00:00
RC_NOOP ,
RC_RIP ,
RC_SUCCESS
} returns ;
typedef struct
{
void * Location ;
void * Ptr ;
char * ID ;
int Size ;
} arena ;
2018-02-28 01:04:06 +00:00
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 ;
2018-04-01 20:58:53 +00:00
time_t LastPrivacyCheck ;
2018-02-28 01:04:06 +00:00
time_t LastQuoteFetch ;
//
2017-03-10 14:19:25 +00:00
typedef struct
{
char * Location ;
char * Ptr ;
2017-05-21 06:35:16 +00:00
char * ID ;
2017-03-10 14:19:25 +00:00
int Size ;
} buffer ;
2017-10-18 21:07:29 +00:00
typedef struct
{
buffer Buffer ;
FILE * Handle ;
2018-02-21 21:50:23 +00:00
char Path [ 256 ] ; // NOTE(matt): Could this just be a char *?
2017-10-18 21:07:29 +00:00
int FileSize ;
} file_buffer ;
2018-02-21 21:50:23 +00:00
// TODO(matt): Increment CINERA_DB_VERSION!
typedef struct
{
// NOTE(matt): Consider augmenting this to contain such stuff as: "hex signature"
unsigned int CurrentDBVersion ; // NOTE(matt): Put this first to aid reliability
version CurrentAppVersion ;
version CurrentHMMLVersion ;
unsigned int InitialDBVersion ;
version InitialAppVersion ;
version InitialHMMLVersion ;
unsigned short int EntryCount ;
char ProjectID [ MAX_PROJECT_ID_LENGTH + 1 ] ;
char ProjectName [ MAX_PROJECT_NAME_LENGTH + 1 ] ;
char BaseURL [ MAX_BASE_URL_LENGTH + 1 ] ;
char IndexLocation [ MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 ] ;
char PlayerLocation [ MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 ] ;
char PlayerURLPrefix [ MAX_PLAYER_URL_PREFIX_LENGTH + 1 ] ;
} index_header ;
typedef struct
{
unsigned int PrevStart , NextStart ;
unsigned short int PrevEnd , NextEnd ;
} link_insertion_offsets ; // NOTE(matt): Relative
typedef struct
{
link_insertion_offsets LinkOffsets ;
unsigned short int Size ;
char BaseFilename [ MAX_BASE_FILENAME_LENGTH + 1 ] ;
char Title [ MAX_TITLE_LENGTH + 1 ] ;
} index_metadata ;
typedef struct
{
file_buffer File ;
file_buffer Metadata ;
index_header Header ;
index_metadata Entry ;
} index ;
// TODO(matt): Increment CINERA_DB_VERSION!
2018-01-03 22:16:20 +00:00
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 ;
2018-02-23 23:36:42 +00:00
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 ] ;
2018-02-21 21:50:23 +00:00
char ProjectName [ MAX_PROJECT_NAME_LENGTH + 1 ] ;
char Title [ MAX_TITLE_LENGTH + 1 ] ;
char URLIndex [ MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 ] ;
char URLPlayer [ MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 ] ;
2018-01-03 22:16:20 +00:00
char VideoID [ 16 ] ;
} buffers ;
2017-09-07 21:41:08 +00:00
enum
{
2018-02-23 23:36:42 +00:00
// Contents and Player Pages Mandatory
TAG_INCLUDES ,
// Contents Page Mandatory
2017-09-07 21:41:08 +00:00
TAG_INDEX ,
2018-02-23 23:36:42 +00:00
// Player Page Mandatory
2017-09-07 21:41:08 +00:00
TAG_MENUS ,
TAG_PLAYER ,
TAG_SCRIPT ,
2018-02-23 23:36:42 +00:00
// 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 ,
2017-12-12 23:24:10 +00:00
TAG_TITLE ,
TAG_VIDEO_ID ,
2018-02-23 23:36:42 +00:00
// Anywhere Optional
TAG_PROJECT ,
TAG_URL ,
2017-09-07 21:41:08 +00:00
} template_tags ;
typedef struct
{
int Code ; // template_tags
char * Tag ;
} tag ;
tag Tags [ ] = {
{ TAG_INCLUDES , " __CINERA_INCLUDES__ " } ,
2018-02-23 23:36:42 +00:00
{ TAG_INDEX , " __CINERA_INDEX__ " } ,
2017-09-07 21:41:08 +00:00
{ TAG_MENUS , " __CINERA_MENUS__ " } ,
{ TAG_PLAYER , " __CINERA_PLAYER__ " } ,
{ TAG_SCRIPT , " __CINERA_SCRIPT__ " } ,
2018-02-23 23:36:42 +00:00
{ 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__ " } ,
2017-09-07 21:41:08 +00:00
{ TAG_TITLE , " __CINERA_TITLE__ " } ,
2017-12-12 23:24:10 +00:00
{ TAG_VIDEO_ID , " __CINERA_VIDEO_ID__ " } ,
2018-02-23 23:36:42 +00:00
{ TAG_PROJECT , " __CINERA_PROJECT__ " } ,
{ TAG_URL , " __CINERA_URL__ " } ,
2017-09-07 21:41:08 +00:00
} ;
typedef struct
{
int Offset ;
int TagCode ;
} tag_offset ;
typedef struct
{
2017-11-11 00:34:47 +00:00
char Filename [ 256 ] ;
2017-09-07 21:41:08 +00:00
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 ;
2017-10-18 21:07:29 +00:00
} template_metadata ;
typedef struct
{
template_metadata Metadata ;
buffer Buffer ;
2017-09-07 21:41:08 +00:00
} template ;
2017-05-21 06:35:16 +00:00
// 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 ;
2017-03-19 01:23:44 +00:00
typedef struct
{
2017-03-30 02:57:04 +00:00
char Timecode [ 8 ] ;
int Identifier ;
} identifier ;
2017-07-29 23:01:39 +00:00
# define REF_MAX_IDENTIFIER 64
2017-05-13 15:38:10 +00:00
2017-03-30 02:57:04 +00:00
typedef struct
{
2017-04-13 00:21:04 +00:00
char RefTitle [ 620 ] ;
char ID [ 512 ] ;
char URL [ 512 ] ;
char Source [ 256 ] ;
2017-05-13 15:38:10 +00:00
identifier Identifier [ REF_MAX_IDENTIFIER ] ;
2017-03-30 02:57:04 +00:00
int IdentifierCount ;
2017-03-19 01:23:44 +00:00
} ref_info ;
2017-04-23 02:47:42 +00:00
typedef struct
{
2017-05-24 21:56:36 +00:00
char Marker [ 32 ] ;
char WrittenText [ 32 ] ;
2017-05-21 06:35:16 +00:00
} category_info ;
2017-04-23 02:47:42 +00:00
2017-08-19 21:41:51 +00:00
typedef struct
{
category_info Category [ 64 ] ;
int Count ;
} categories ;
2017-05-25 20:28:52 +00:00
// TODO(matt): Parse this stuff out of a config file
2017-08-18 22:46:58 +00:00
typedef struct
{
char * Username ;
char * CreditedName ;
char * HomepageURL ;
char * SupportIcon ;
char * SupportURL ;
} credential_info ;
credential_info Credentials [ ] =
2017-05-25 20:28:52 +00:00
{
2017-11-18 00:27:33 +00:00
{ " /a_waterman " , " Andrew Waterman " , " https://www.linkedin.com/in/andrew-waterman-76805788 " , " " , " " } ,
{ " /y_lee " , " Yunsup Lee " , " https://www.linkedin.com/in/yunsup-lee-385b692b/ " , " " , " " } ,
2017-12-03 00:47:27 +00:00
{ " AndrewJDR " , " Andrew Johnson " , " " , " " , " " } ,
2017-12-07 01:15:13 +00:00
{ " AsafGartner " , " Asaf Gartner " , " " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " 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 " , " " , " " } ,
2017-12-03 00:47:27 +00:00
{ " Mannilie " , " Emmanuel Vaccaro " , " http://emmanuelvaccaro.com/ " , " " , " " } ,
2018-02-04 21:52:32 +00:00
{ " Miblo " , " Matt Mascarenhas " , " https://miblodelcarpio.co.uk/ " , " cinera_sprite_sendowl.png " , " https://miblodelcarpio.co.uk/cinera#pledge " } ,
2017-12-17 20:44:00 +00:00
{ " Mr4thDimention " , " Allen Webster " , " http://www.4coder.net/ " , " " , " " } ,
2018-04-02 20:52:45 +00:00
{ " Pseudonym73 " , " Andrew Bromage " , " https://twitter.com/deguerre " , " " , " " } ,
2017-12-17 20:44:00 +00:00
{ " Quel_Solaar " , " Eskil Steenberg " , " http://quelsolaar.com/ " , " " , " " } ,
2017-12-03 00:47:27 +00:00
{ " ZedZull " , " Jay Waggle " , " " , " " , " " } ,
2017-12-17 20:44:00 +00:00
{ " abnercoimbre " , " Abner Coimbre " , " https://handmade.network/m/abnercoimbre " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " brianwill " , " Brian Will " , " http://brianwill.net/blog/ " , " " , " " } ,
2017-12-03 00:55:01 +00:00
{ " cbloom " , " Charles Bloom " , " http://cbloomrants.blogspot.co.uk/ " , " " , " " } ,
2017-12-10 17:29:00 +00:00
{ " cmuratori " , " Casey Muratori " , " https://handmadehero.org " , " cinera_sprite_sendowl.png " , " https://handmadehero.org/patreon.html " } ,
2017-12-03 18:25:20 +00:00
{ " csnover " , " Colin Snover " , " https://zetafleet.com/ " , " " , " " } ,
2017-08-18 22:46:58 +00:00
{ " debiatan " , " Miguel Lechón " , " http://blog.debiatan.net/ " , " " , " " } ,
2017-12-03 00:47:27 +00:00
{ " dspecht " , " Dustin Specht " , " " , " " , " " } ,
{ " effect0r " , " Cory Henderlite " , " " , " " , " " } ,
{ " ffsjs " , " ffsjs " , " " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " fierydrake " , " Mike Tunnicliffe " , " " , " " , " " } ,
{ " garlandobloom " , " Matthew VanDevander " , " https://lowtideproductions.com/ " , " cinera_sprite_patreon.png " , " https://www.patreon.com/mv " } ,
2017-12-03 00:47:27 +00:00
{ " ikerms " , " Iker Murga " , " " , " " , " " } ,
{ " insofaras " , " Alex Baines " , " https://abaines.me.uk/ " , " " , " " } ,
{ " jacebennett " , " Jace Bennett " , " " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " jon " , " Jonathan Blow " , " http://the-witness.net/news/ " , " " , " " } ,
2017-12-07 01:15:13 +00:00
{ " jpike " , " Jacob Pike " , " " , " " , " " } ,
2017-12-03 00:47:27 +00:00
{ " martincohen " , " Martin Cohen " , " http://blog.coh.io/ " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " miotatsu " , " Mio Iwakura " , " http://riscy.tv/ " , " cinera_sprite_patreon.png " , " https://patreon.com/miotatsu " } ,
{ " nothings " , " Sean Barrett " , " https://nothings.org/ " , " " , " " } ,
2018-03-23 15:33:37 +00:00
{ " pervognsen " , " Per Vognsen " , " https://github.com/pervognsen/bitwise/ " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " philipbuuck " , " Philip Buuck " , " http://philipbuuck.com/ " , " " , " " } ,
2017-12-03 18:14:19 +00:00
{ " powerc9000 " , " Clay Murray " , " http://claymurray.website/ " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " rygorous " , " Fabian Giesen " , " https://fgiesen.wordpress.com/ " , " " , " " } ,
2017-12-03 00:47:27 +00:00
{ " schme " , " Kasper Sauramo " , " " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " sssmcgrath " , " Shawn McGrath " , " http://www.dyadgame.com/ " , " " , " " } ,
{ " thehappiecat " , " Anne " , " https://www.youtube.com/c/TheHappieCat " , " cinera_sprite_patreon.png " , " https://www.patreon.com/thehappiecat " } ,
2017-12-07 01:15:13 +00:00
{ " theinternetftw " , " Ben Craddock " , " " , " " , " " } ,
2017-12-03 00:47:27 +00:00
{ " wheatdog " , " Tim Liou " , " http://stringbulbs.com/ " , " " , " " } ,
2017-11-18 00:27:33 +00:00
{ " williamchyr " , " William Chyr " , " http://williamchyr.com/ " , " " , " " } ,
{ " wonchun " , " Won Chun " , " https://twitter.com/won3d " , " " , " " } ,
2017-08-18 22:46:58 +00:00
} ;
typedef struct
{
char * Medium ;
char * Icon ;
char * WrittenName ;
} category_medium ;
category_medium CategoryMedium [ ] =
{
// medium icon written name
2017-11-28 23:02:00 +00:00
{ " admin " , " 🗹 " , " Administrivia " } ,
2017-12-03 00:55:01 +00:00
{ " afk " , " … " , " Away from Keyboard " } ,
2017-08-18 22:46:58 +00:00
{ " authored " , " 🗪 " , " Chat Comment " } , // TODO(matt): Conditionally handle Chat vs Guest Comments
{ " blackboard " , " 🖌 " , " Blackboard " } ,
{ " experience " , " 🍷 " , " Experience " } ,
2017-11-28 23:02:00 +00:00
{ " hat " , " 🎩 " , " Hat " } ,
{ " multimedia " , " 🎬 " , " Media Clip " } ,
2017-08-18 22:46:58 +00:00
{ " owl " , " 🦉 " , " Owl of Shame " } ,
{ " programming " , " 🖮 " , " Programming " } , // TODO(matt): Potentially make this configurable per project
{ " rant " , " 💢 " , " Rant " } ,
{ " research " , " 📖 " , " Research " } ,
{ " run " , " 🏃 " , " In-Game " } , // TODO(matt): Potentially make this configurable per project
2017-10-18 21:07:29 +00:00
{ " speech " , " 🗩 " , " Speech " } ,
2017-08-18 22:46:58 +00:00
{ " trivia " , " 🎲 " , " Trivia " } ,
2017-05-25 20:28:52 +00:00
} ;
2017-11-11 00:34:47 +00:00
enum
{
NS_CALENDRICAL ,
NS_LINEAR ,
NS_SEASONAL ,
} numbering_schemes ;
typedef struct
{
char * ProjectID ;
char * FullName ;
char * Unit ; // e.g. Day, Episode, Session
2017-12-07 21:07:36 +00:00
int NumberingScheme ; // numbering_schemes
char * Medium ;
char * AltURLPrefix ; // NOTE(matt): This currently just straight up replaces the ProjectID in the player pages' output directories
2017-11-11 00:34:47 +00:00
} project_info ;
project_info ProjectInfo [ ] =
{
2018-03-23 15:33:37 +00:00
{ " bitwise " , " Bitwise " , " Day " , NS_LINEAR , " programming " , " " } ,
2017-12-07 21:07:36 +00:00
{ " book " , " Book Club " , " Day " , NS_LINEAR , " research " , " " } ,
{ " pcalc " , " pcalc " , " Day " , NS_LINEAR , " programming " , " " } ,
{ " riscy " , " RISCY BUSINESS " , " Day " , NS_LINEAR , " programming " , " " } ,
2017-12-07 01:15:13 +00:00
2017-12-07 21:07:36 +00:00
{ " 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 " , " " } ,
2017-12-07 01:15:13 +00:00
2018-02-23 23:36:42 +00:00
{ " 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 " , " " } ,
2017-12-07 01:15:13 +00:00
2017-12-07 21:07:36 +00:00
{ " obbg " , " Open Block Building Game " , " Episode " , NS_LINEAR , " programming " , " " } ,
2017-12-07 01:15:13 +00:00
2017-12-07 21:07:36 +00:00
{ " sysadmin " , " SysAdmin " , " Session " , NS_LINEAR , " admin " , " " } ,
2017-11-11 00:34:47 +00:00
} ;
2017-03-25 02:07:55 +00:00
# define ArrayCount(A) sizeof(A) / sizeof(*(A))
2017-08-20 21:23:15 +00:00
__attribute__ ( ( format ( printf , 2 , 3 ) ) )
2017-11-11 00:34:47 +00:00
int
2017-08-20 21:23:15 +00:00
CopyString ( char Dest [ ] , char * Format , . . . )
2017-05-21 06:35:16 +00:00
{
2017-11-11 00:34:47 +00:00
int Length = 0 ;
2017-08-20 21:23:15 +00:00
va_list Args ;
va_start ( Args , Format ) ;
2017-11-11 00:34:47 +00:00
Length = vsprintf ( Dest , Format , Args ) ;
2017-08-20 21:23:15 +00:00
va_end ( Args ) ;
2017-11-11 00:34:47 +00:00
return Length ;
2017-03-16 00:56:58 +00:00
}
2017-03-10 14:19:25 +00:00
int
2017-08-20 21:23:15 +00:00
StringLength ( char * String )
2017-03-10 14:19:25 +00:00
{
2017-08-20 21:23:15 +00:00
int i = 0 ;
while ( String [ i ] )
2017-03-10 14:19:25 +00:00
{
2017-08-20 21:23:15 +00:00
+ + i ;
2017-03-10 14:19:25 +00:00
}
2017-08-20 21:23:15 +00:00
return i ;
2017-03-10 14:19:25 +00:00
}
2017-03-16 00:56:58 +00:00
void
2017-03-30 02:57:04 +00:00
CopyBuffer ( buffer * Dest , buffer * Src )
2017-03-16 00:56:58 +00:00
{
Src - > Ptr = Src - > Location ;
while ( * Src - > Ptr )
{
2017-05-21 06:35:16 +00:00
// TODO(matt)
{
if ( Dest - > Ptr - Dest - > Location > = Dest - > Size )
{
2017-05-24 21:56:36 +00:00
fprintf ( stderr , " CopyBuffer: %s cannot accommodate %s \n " , Dest - > ID , Src - > ID ) ;
2017-05-21 06:35:16 +00:00
__asm__ ( " int3 " ) ;
}
}
2017-03-16 00:56:58 +00:00
* Dest - > Ptr + + = * Src - > Ptr + + ;
}
2017-08-19 21:41:51 +00:00
* Dest - > Ptr = ' \0 ' ;
2017-03-16 00:56:58 +00:00
}
2018-02-21 21:50:23 +00:00
void
Clear ( char * String , int Size )
{
for ( int i = 0 ; i < Size ; + + i )
{
String [ i ] = 0 ;
}
}
2017-06-13 22:13:03 +00:00
int
2017-06-07 23:47:47 +00:00
CopyStringNoFormat ( char * Dest , char * String )
{
2017-06-13 22:13:03 +00:00
int Length = 0 ;
2017-06-07 23:47:47 +00:00
while ( * String )
{
* Dest + + = * String + + ;
2017-06-13 22:13:03 +00:00
+ + Length ;
2017-06-07 23:47:47 +00:00
}
2017-06-15 22:10:36 +00:00
* Dest = ' \0 ' ;
2017-06-13 22:13:03 +00:00
return Length ;
2017-06-07 23:47:47 +00:00
}
2018-02-21 21:50:23 +00:00
int
ClearCopyStringNoFormat ( char * Dest , int DestSize , char * String )
{
Clear ( Dest , DestSize ) ;
return ( CopyStringNoFormat ( Dest , String ) ) ;
}
2017-08-29 20:35:28 +00:00
// TODO(matt): Maybe do a version of this that takes a string as a Terminator
2017-08-18 22:46:58 +00:00
int
CopyStringNoFormatT ( char * Dest , char * String , char Terminator )
{
int Length = 0 ;
while ( * String ! = Terminator )
{
* Dest + + = * String + + ;
+ + Length ;
}
* Dest = ' \0 ' ;
return Length ;
}
2017-03-29 03:38:12 +00:00
__attribute__ ( ( format ( printf , 2 , 3 ) ) )
2017-03-16 00:56:58 +00:00
void
2017-03-23 00:34:59 +00:00
CopyStringToBuffer ( buffer * Dest , char * Format , . . . )
2017-03-16 00:56:58 +00:00
{
2017-03-23 00:34:59 +00:00
va_list Args ;
va_start ( Args , Format ) ;
2017-06-03 01:32:18 +00:00
int Length = vsnprintf ( Dest - > Ptr , Dest - > Size - ( Dest - > Ptr - Dest - > Location ) , Format , Args ) ;
2017-03-23 00:34:59 +00:00
va_end ( Args ) ;
2017-11-11 00:34:47 +00:00
// TODO(matt)
2017-05-21 06:35:16 +00:00
{
if ( Length + ( Dest - > Ptr - Dest - > Location ) > = Dest - > Size )
{
2017-06-21 23:16:09 +00:00
fprintf ( stderr , " CopyStringToBuffer: %s cannot accommodate %d-character string: \n "
" \n "
2017-06-23 14:47:48 +00:00
" %s \n " , Dest - > ID , Length , Format ) ;
2017-05-21 06:35:16 +00:00
__asm__ ( " int3 " ) ;
}
}
2017-03-23 00:34:59 +00:00
Dest - > Ptr + = Length ;
2017-03-16 00:56:58 +00:00
}
2018-02-23 23:36:42 +00:00
void
CopyStringToBufferNoFormat ( buffer * Dest , char * String )
{
while ( * String )
{
* Dest - > Ptr + + = * String + + ;
}
* Dest - > Ptr = ' \0 ' ;
}
2017-06-07 23:47:47 +00:00
void
CopyStringToBufferHTMLSafe ( buffer * Dest , char * String )
{
while ( * String )
{
if ( Dest - > Ptr - Dest - > Location > = Dest - > Size )
{
fprintf ( stderr , " CopyStringToBufferHTMLSafe: %s cannot accommodate %d-character string \n " , Dest - > ID , StringLength ( String ) ) ;
__asm__ ( " int3 " ) ;
}
switch ( * String )
{
case ' < ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( Dest , " < " ) ;
String + + ;
break ;
2017-06-07 23:47:47 +00:00
case ' > ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( Dest , " > " ) ;
String + + ;
break ;
2017-06-07 23:47:47 +00:00
case ' & ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( Dest , " & " ) ;
String + + ;
break ;
2017-06-07 23:47:47 +00:00
case ' \" ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( Dest , " " " ) ;
String + + ;
break ;
2017-06-07 23:47:47 +00:00
case ' \' ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( Dest , " ' " ) ;
String + + ;
break ;
2017-06-07 23:47:47 +00:00
default :
2017-06-25 18:05:58 +00:00
* Dest - > Ptr + + = * String + + ;
break ;
2017-06-07 23:47:47 +00:00
}
}
}
2017-11-11 00:34:47 +00:00
void
CopyStringToBufferJSONSafe ( buffer * Dest , char * String )
{
while ( * String )
{
if ( Dest - > Ptr - Dest - > Location > = Dest - > Size )
{
fprintf ( stderr , " CopyStringToBufferHTMLSafe: %s cannot accommodate %d-character string \n " , Dest - > ID , StringLength ( String ) ) ;
__asm__ ( " int3 " ) ;
}
switch ( * String )
{
case ' \\ ' :
case ' \" ' :
* Dest - > Ptr + + = ' \\ ' ;
default :
* Dest - > Ptr + + = * String + + ;
break ;
}
}
}
2017-03-17 01:45:16 +00:00
int
2017-06-13 22:13:03 +00:00
StringsDiffer ( char * A , char * B ) // NOTE(matt): Two null-terminated strings
2017-03-17 01:45:16 +00:00
{
2017-03-21 02:38:18 +00:00
while ( * A & & * B & & * A = = * B )
2017-03-17 01:45:16 +00:00
{
+ + A , + + B ;
}
return * A - * B ;
}
2018-04-04 22:39:38 +00:00
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 ;
}
2017-04-23 00:30:37 +00:00
bool
2017-06-13 22:13:03 +00:00
StringsDifferT ( char * A , // NOTE(matt): Null-terminated string
char * B , // NOTE(matt): Not null-terminated string (e.g. one mid-buffer)
2017-06-16 07:55:59 +00:00
char Terminator // NOTE(matt): Caller definable terminator. Pass 0 to only match on the extent of A
2017-06-13 22:13:03 +00:00
)
2017-04-23 00:30:37 +00:00
{
2017-06-13 22:13:03 +00:00
int ALength = StringLength ( A ) ;
2017-04-23 00:30:37 +00:00
int i = 0 ;
2017-06-13 22:13:03 +00:00
while ( i < ALength & & A [ i ] & & A [ i ] = = B [ i ] )
2017-04-23 00:30:37 +00:00
{
+ + i ;
}
2017-06-13 22:13:03 +00:00
if ( ( ! Terminator & & ! A [ i ] & & ALength = = i ) | |
2017-06-25 18:05:58 +00:00
( ! A [ i ] & & ALength = = i & & ( B [ i ] = = Terminator ) ) )
2017-04-23 00:30:37 +00:00
{
return FALSE ;
}
else
{
return TRUE ;
}
}
2017-08-20 21:23:15 +00:00
int
MakeDir ( char * Path )
{
2017-09-07 21:41:08 +00:00
// TODO(matt): Correctly check for permissions
2017-08-20 21:23:15 +00:00
int i = StringLength ( Path ) ;
int Ancestors = 0 ;
while ( mkdir ( Path , 00755 ) = = - 1 )
{
2017-09-07 21:41:08 +00:00
if ( errno = = EACCES )
{
return RC_ERROR_DIRECTORY ;
}
2017-08-20 21:23:15 +00:00
while ( Path [ i ] ! = ' / ' & & i > 0 )
{
- - i ;
}
+ + Ancestors ;
Path [ i ] = ' \0 ' ;
2017-09-07 21:41:08 +00:00
if ( i = = 0 ) { return RC_ERROR_DIRECTORY ; }
2017-08-20 21:23:15 +00:00
}
while ( Ancestors > 0 )
{
while ( Path [ i ] ! = ' \0 ' )
{
+ + i ;
}
Path [ i ] = ' / ' ;
- - Ancestors ;
if ( ( mkdir ( Path , 00755 ) ) = = - 1 )
{
2017-09-07 21:41:08 +00:00
return RC_ERROR_DIRECTORY ;
2017-08-20 21:23:15 +00:00
}
}
2017-09-07 21:41:08 +00:00
return RC_SUCCESS ;
}
void
2017-10-18 21:07:29 +00:00
LogUsage ( buffer * Buffer )
2017-09-07 21:41:08 +00:00
{
2017-09-15 01:11:39 +00:00
# if DEBUG
2017-11-11 00:34:47 +00:00
char LogPath [ 256 ] ;
2017-10-18 21:07:29 +00:00
CopyString ( LogPath , " %s/%s " , Config . CacheDir , " buffers.log " ) ;
2017-09-07 21:41:08 +00:00
FILE * LogFile ;
if ( ! ( LogFile = fopen ( LogPath , " a+ " ) ) )
{
2018-01-03 22:16:20 +00:00
MakeDir ( Config . CacheDir ) ;
2017-09-07 21:41:08 +00:00
if ( ! ( LogFile = fopen ( LogPath , " a+ " ) ) )
{
perror ( " LogUsage " ) ;
return ;
}
}
fprintf ( LogFile , " %s,%ld,%d \n " ,
2017-10-18 21:07:29 +00:00
Buffer - > ID ,
Buffer - > Ptr - Buffer - > Location ,
Buffer - > Size ) ;
2017-09-07 21:41:08 +00:00
fclose ( LogFile ) ;
2017-09-15 01:11:39 +00:00
# endif
2017-09-07 21:41:08 +00:00
}
2017-10-18 21:07:29 +00:00
__attribute__ ( ( format ( printf , 2 , 3 ) ) )
2017-09-07 21:41:08 +00:00
void
2017-10-18 21:07:29 +00:00
LogError ( int LogLevel , char * Format , . . . )
2017-09-07 21:41:08 +00:00
{
if ( Config . LogLevel > = LogLevel )
{
2017-11-11 00:34:47 +00:00
char LogPath [ 256 ] ;
2017-09-07 21:41:08 +00:00
CopyString ( LogPath , " %s/%s " , Config . CacheDir , " errors.log " ) ;
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 ) ;
}
2017-08-20 21:23:15 +00:00
}
void
FreeBuffer ( buffer * Buffer )
{
free ( Buffer - > Location ) ;
2017-11-11 00:34:47 +00:00
Buffer - > Location = ' \0 ' ;
2017-10-18 21:07:29 +00:00
# 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
2017-08-20 21:23:15 +00:00
}
2017-08-29 20:35:28 +00:00
#if 0
# define ClaimBuffer(MemoryArena, Buffer, ID, Size) if(__ClaimBuffer(MemoryArena, Buffer, ID, Size))\
2017-08-20 21:23:15 +00:00
{ \
fprintf ( stderr , " %s:%d: MemoryArena cannot contain %s of size %d \n " , __FILE__ , __LINE__ , ID , Size ) ; \
hmml_free ( & HMML ) ; \
FreeBuffer ( MemoryArena ) ; \
return 1 ; \
} ;
2017-08-29 20:35:28 +00:00
# endif
2017-08-20 21:23:15 +00:00
int
2017-10-18 21:07:29 +00:00
ClaimBuffer ( buffer * Buffer , char * ID , int Size )
2017-08-20 21:23:15 +00:00
{
2017-10-18 21:07:29 +00:00
if ( MemoryArena . Ptr - MemoryArena . Location + Size > MemoryArena . Size )
2017-08-20 21:23:15 +00:00
{
2017-09-07 21:41:08 +00:00
return RC_ARENA_FULL ;
2017-08-20 21:23:15 +00:00
}
2017-10-18 21:07:29 +00:00
Buffer - > Location = ( char * ) MemoryArena . Ptr ;
2017-08-20 21:23:15 +00:00
Buffer - > Size = Size ;
Buffer - > ID = ID ;
2017-10-18 21:07:29 +00:00
MemoryArena . Ptr + = Buffer - > Size ;
2017-08-20 21:23:15 +00:00
* Buffer - > Location = ' \0 ' ;
Buffer - > Ptr = Buffer - > Location ;
# if DEBUG
2018-01-03 22:16:20 +00:00
float PercentageUsed = ( float ) ( MemoryArena . Ptr - MemoryArena . Location ) / MemoryArena . Size * 100 ;
2017-10-18 21:07:29 +00:00
printf ( " ClaimBuffer(%s): %d \n "
2018-01-03 22:16:20 +00:00
" Total ClaimedMemory: %ld (%.2f%%, leaving %ld free) \n \n " , Buffer - > ID , Buffer - > Size , MemoryArena . Ptr - MemoryArena . Location , PercentageUsed , MemoryArena . Size - ( MemoryArena . Ptr - MemoryArena . Location ) ) ;
2017-08-20 21:23:15 +00:00
# endif
2017-09-07 21:41:08 +00:00
return RC_SUCCESS ;
2017-08-20 21:23:15 +00:00
}
void
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( buffer * Buffer )
2017-08-20 21:23:15 +00:00
{
* Buffer - > Location = ' \0 ' ;
2017-10-18 21:07:29 +00:00
MemoryArena . Ptr - = Buffer - > Size ;
2017-08-20 21:23:15 +00:00
float PercentageUsed = ( float ) ( Buffer - > Ptr - Buffer - > Location ) / Buffer - > Size * 100 ;
# if DEBUG
2017-10-18 21:07:29 +00:00
printf ( " DeclaimBuffer(%s) \n "
2017-08-20 21:23:15 +00:00
" Used: %ld / %d (%.2f%%) \n "
" \n "
2017-08-29 20:35:28 +00:00
" Total ClaimedMemory: %ld \n \n " ,
2017-08-20 21:23:15 +00:00
Buffer - > ID ,
Buffer - > Ptr - Buffer - > Location ,
Buffer - > Size ,
PercentageUsed ,
2017-10-18 21:07:29 +00:00
MemoryArena . Ptr - MemoryArena . Location ) ;
2017-08-20 21:23:15 +00:00
# endif
2017-10-18 21:07:29 +00:00
LogUsage ( Buffer ) ;
2018-03-06 20:40:12 +00:00
if ( PercentageUsed > = 95.0f )
{
// TODO(matt): Implement either dynamically growing buffers, or phoning home to matt@handmadedev.org
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 )
2017-08-20 21:23:15 +00:00
{
// TODO(matt): Implement either dynamically growing buffers, or phoning home to matt@handmadedev.org
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " %s used %.2f%% of its allotted memory \n " , Buffer - > ID , PercentageUsed ) ;
2018-03-06 20:40:12 +00:00
fprintf ( stderr , " \ e[0;33mWarning \ e[0m: %s used %.2f%% of its allotted memory \n " , Buffer - > ID , PercentageUsed ) ;
2017-08-20 21:23:15 +00:00
}
2017-11-11 00:34:47 +00:00
Buffer - > Size = 0 ;
2017-08-20 21:23:15 +00:00
}
2017-10-18 21:07:29 +00:00
void RewindBuffer ( buffer * Buffer )
{
# 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 ;
}
2018-01-03 22:16:20 +00:00
enum
{
TEMPLATE_INDEX ,
TEMPLATE_PLAYER ,
TEMPLATE_BESPOKE
} templates ;
2018-01-04 20:55:51 +00:00
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 ;
}
2018-01-03 22:16:20 +00:00
void
ConstructTemplatePath ( template * Template , int TemplateType )
{
2018-01-04 20:55:51 +00:00
// NOTE(matt): Bespoke template paths are set relative to:
// in Project Edition: ProjectDir
// in Single Edition: Parent directory of .hmml file
2018-01-03 22:16:20 +00:00
if ( Template - > Metadata . Filename [ 0 ] ! = ' / ' )
{
char Temp [ 256 ] ;
CopyString ( Temp , Template - > Metadata . Filename ) ;
char * Ptr = Template - > Metadata . Filename ;
if ( TemplateType = = TEMPLATE_BESPOKE )
{
2018-01-04 20:55:51 +00:00
if ( Config . Edition = = EDITION_SINGLE )
{
Ptr + = CopyString ( Ptr , " %s/ " , GetDirectoryPath ( Config . SingleHMMLFilePath ) ) ;
}
else
{
Ptr + = CopyString ( Ptr , " %s/ " , Config . ProjectDir ) ;
}
2018-01-03 22:16:20 +00:00
}
else
{
Ptr + = CopyString ( Ptr , " %s/ " , Config . TemplatesDir ) ;
}
CopyString ( Ptr , " %s " , Temp ) ;
}
}
2017-09-07 21:41:08 +00:00
int
2018-01-03 22:16:20 +00:00
InitTemplate ( template * * Template )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
if ( MemoryArena . Ptr - MemoryArena . Location + sizeof ( template ) > MemoryArena . Size )
{
return RC_ARENA_FULL ;
}
2017-10-18 21:07:29 +00:00
* Template = ( template * ) MemoryArena . Ptr ;
2018-01-03 22:16:20 +00:00
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]
2017-12-07 01:15:13 +00:00
{
2018-01-03 22:16:20 +00:00
( * Template ) - > Metadata . Tag [ i ] . Offset = 0 ;
( * Template ) - > Metadata . Tag [ i ] . TagCode = 0 ;
2017-12-07 01:15:13 +00:00
}
2018-01-03 22:16:20 +00:00
MemoryArena . Ptr + = sizeof ( template ) ;
return RC_SUCCESS ;
}
int
ClaimTemplate ( template * * Template , char * Location , int TemplateType )
{
CopyString ( ( * Template ) - > Metadata . Filename , Location ) ;
ConstructTemplatePath ( ( * Template ) , TemplateType ) ;
if ( TemplateType = = TEMPLATE_BESPOKE )
2017-12-07 01:15:13 +00:00
{
2018-01-03 22:16:20 +00:00
fprintf ( stderr , " \ e[0;35mPacking \ e[0m template: %s \n " , ( * Template ) - > Metadata . Filename ) ;
2017-12-07 01:15:13 +00:00
}
2017-10-18 21:07:29 +00:00
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 ) ) ;
2018-01-03 22:16:20 +00:00
Clear ( ( * Template ) - > Metadata . Filename , 256 ) ; // NOTE(matt): template_metadata specifies Filename[256]
2017-10-18 21:07:29 +00:00
return RC_ERROR_FILE ;
}
fseek ( File , 0 , SEEK_END ) ;
( * Template ) - > Buffer . Size = ftell ( File ) ;
2018-01-03 22:16:20 +00:00
if ( MemoryArena . Ptr - MemoryArena . Location + ( * Template ) - > Buffer . Size > MemoryArena . Size )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
Clear ( ( * Template ) - > Metadata . Filename , 256 ) ; // NOTE(matt): template_metadata specifies Filename[256]
2017-09-07 21:41:08 +00:00
return RC_ARENA_FULL ;
}
2017-10-18 21:07:29 +00:00
2018-01-03 22:16:20 +00:00
( * Template ) - > Buffer . Location = MemoryArena . Ptr ;
( * Template ) - > Buffer . Ptr = ( * Template ) - > Buffer . Location ;
( * Template ) - > Buffer . ID = ( * Template ) - > Metadata . Filename ;
2017-10-18 21:07:29 +00:00
fseek ( File , 0 , SEEK_SET ) ;
fread ( ( * Template ) - > Buffer . Location , ( * Template ) - > Buffer . Size , 1 , File ) ;
fclose ( File ) ;
2018-01-03 22:16:20 +00:00
MemoryArena . Ptr + = ( * Template ) - > Buffer . Size ;
2017-09-07 21:41:08 +00:00
# if DEBUG
2018-01-03 22:16:20 +00:00
printf ( " ClaimTemplate(%s): %d \n "
" Total ClaimedMemory: %ld \n \n " , ( * Template ) - > Metadata . Filename , ( * Template ) - > Buffer . Size , MemoryArena . Ptr - MemoryArena . Location ) ;
2017-09-07 21:41:08 +00:00
# endif
return RC_SUCCESS ;
}
int
2017-10-18 21:07:29 +00:00
DeclaimTemplate ( template * Template )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
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 ;
2017-09-07 21:41:08 +00:00
# if DEBUG
2017-10-18 21:07:29 +00:00
printf ( " DeclaimTemplate(%s) \n "
2017-09-07 21:41:08 +00:00
" Total ClaimedMemory: %ld \n \n " ,
2017-10-18 21:07:29 +00:00
( * Template ) . Metadata . Filename ,
MemoryArena . Ptr - MemoryArena . Location ) ;
2017-09-07 21:41:08 +00:00
# endif
return RC_SUCCESS ;
}
2017-08-20 21:23:15 +00:00
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 ] ;
}
2017-04-13 23:46:21 +00:00
typedef struct
{
2017-04-21 01:25:40 +00:00
unsigned int Hue : 16 ;
unsigned int Saturation : 8 ;
unsigned int Lightness : 8 ;
2017-04-13 23:46:21 +00:00
} hsl_colour ;
hsl_colour
2017-03-18 02:04:13 +00:00
CharToColour ( char Char )
{
2017-04-13 23:46:21 +00:00
hsl_colour Colour ;
2017-03-18 02:04:13 +00:00
if ( Char > = ' a ' & & Char < = ' z ' )
{
2017-04-19 01:10:45 +00:00
Colour . Hue = ( ( ( float ) Char - ' a ' ) / ( ' z ' - ' a ' ) * 360 ) ;
Colour . Saturation = ( ( ( float ) Char - ' a ' ) / ( ' z ' - ' a ' ) * 26 + 74 ) ;
2017-03-18 02:04:13 +00:00
}
else if ( Char > = ' A ' & & Char < = ' Z ' )
{
2017-04-19 01:10:45 +00:00
Colour . Hue = ( ( ( float ) Char - ' A ' ) / ( ' Z ' - ' A ' ) * 360 ) ;
Colour . Saturation = ( ( ( float ) Char - ' A ' ) / ( ' Z ' - ' A ' ) * 26 + 74 ) ;
2017-03-18 02:04:13 +00:00
}
else if ( Char > = ' 0 ' & & Char < = ' 9 ' )
{
2017-04-19 01:10:45 +00:00
Colour . Hue = ( ( ( float ) Char - ' 0 ' ) / ( ' 9 ' - ' 0 ' ) * 360 ) ;
Colour . Saturation = ( ( ( float ) Char - ' 0 ' ) / ( ' 9 ' - ' 0 ' ) * 26 + 74 ) ;
2017-03-18 02:04:13 +00:00
}
else
{
2017-04-13 23:46:21 +00:00
Colour . Hue = 180 ;
Colour . Saturation = 50 ;
2017-03-18 02:04:13 +00:00
}
2017-06-25 18:22:54 +00:00
2017-04-13 23:46:21 +00:00
return Colour ;
2017-03-18 02:04:13 +00:00
}
2018-04-04 22:39:38 +00:00
void
2017-06-09 22:04:07 +00:00
StringToColourHash ( hsl_colour * Colour , char * String )
2017-03-18 02:04:13 +00:00
{
2017-06-09 22:04:07 +00:00
Colour - > Hue = 0 ;
Colour - > Saturation = 0 ;
2017-11-11 00:34:47 +00:00
Colour - > Lightness = 74 ;
2017-06-25 18:22:54 +00:00
2017-03-18 02:04:13 +00:00
int i ;
for ( i = 0 ; String [ i ] ; + + i )
{
2017-06-09 22:04:07 +00:00
Colour - > Hue + = CharToColour ( String [ i ] ) . Hue ;
Colour - > Saturation + = CharToColour ( String [ i ] ) . Saturation ;
2017-03-18 02:04:13 +00:00
}
2017-06-25 18:22:54 +00:00
2017-06-09 22:04:07 +00:00
Colour - > Hue = Colour - > Hue % 360 ;
Colour - > Saturation = Colour - > Saturation % 26 + 74 ;
2017-03-18 02:04:13 +00:00
}
2017-03-25 02:07:55 +00:00
char *
SanitisePunctuation ( char * String )
{
char * Ptr = String ;
while ( * Ptr )
{
if ( * Ptr = = ' ' )
{
* Ptr = ' _ ' ;
}
if ( ( * Ptr < ' 0 ' | | * Ptr > ' 9 ' ) & &
2017-06-25 18:05:58 +00:00
( * Ptr < ' a ' | | * Ptr > ' z ' ) & &
( * Ptr < ' A ' | | * Ptr > ' Z ' ) )
2017-03-25 02:07:55 +00:00
{
* Ptr = ' - ' ;
}
+ + Ptr ;
}
return String ;
}
2017-12-07 21:07:36 +00:00
enum
{
INCLUDE_CSS ,
INCLUDE_Images ,
INCLUDE_JS ,
} include_types ;
enum
{
PAGE_PLAYER = 1 < < 0 ,
PAGE_INDEX = 1 < < 1
} pages ;
void
ConstructURLPrefix ( buffer * URLPrefix , int IncludeType , int PageType )
{
2017-12-12 23:24:10 +00:00
RewindBuffer ( URLPrefix ) ;
2017-12-07 21:07:36 +00:00
if ( StringsDiffer ( Config . RootURL , " " ) )
{
URLPrefix - > Ptr + = CopyString ( URLPrefix - > Ptr , " %s/ " , Config . RootURL ) ;
}
else
{
if ( Config . Edition = = EDITION_PROJECT )
{
if ( PageType = = PAGE_PLAYER )
{
URLPrefix - > Ptr + = CopyString ( URLPrefix - > Ptr , " ../ " ) ;
}
URLPrefix - > Ptr + = CopyString ( URLPrefix - > Ptr , " ../ " ) ;
}
}
switch ( IncludeType )
{
case INCLUDE_CSS :
if ( StringsDiffer ( Config . CSSDir , " " ) )
{
URLPrefix - > Ptr + = CopyString ( URLPrefix - > Ptr , " %s/ " , Config . CSSDir ) ;
}
break ;
case INCLUDE_Images :
if ( StringsDiffer ( Config . ImagesDir , " " ) )
{
URLPrefix - > Ptr + = CopyString ( URLPrefix - > Ptr , " %s/ " , Config . ImagesDir ) ;
}
break ;
case INCLUDE_JS :
if ( StringsDiffer ( Config . JSDir , " " ) )
{
URLPrefix - > Ptr + = CopyString ( URLPrefix - > Ptr , " %s/ " , Config . JSDir ) ;
}
break ;
}
}
2018-04-04 22:39:38 +00:00
typedef struct
{
char Abbreviation [ 32 ] ;
hsl_colour Colour ;
credential_info * Credential ;
bool Seen ;
} speaker ;
typedef struct
{
speaker Speaker [ 16 ] ;
int Count ;
} speakers ;
2017-08-18 22:46:58 +00:00
enum
2017-05-25 20:28:52 +00:00
{
2017-08-19 21:41:51 +00:00
CreditsError_NoHost ,
CreditsError_NoAnnotator ,
CreditsError_NoCredentials
2017-11-11 00:34:47 +00:00
} credits_errors ;
2017-06-25 18:22:54 +00:00
2017-08-18 22:46:58 +00:00
int
2018-04-04 22:39:38 +00:00
SearchCredentials ( buffer * CreditsMenu , bool * HasCreditsMenu , char * Person , char * Role , speakers * Speakers )
2017-08-18 22:46:58 +00:00
{
bool Found = FALSE ;
2017-05-25 20:28:52 +00:00
for ( int CredentialIndex = 0 ; CredentialIndex < ArrayCount ( Credentials ) ; + + CredentialIndex )
{
2017-08-18 22:46:58 +00:00
if ( ! StringsDiffer ( Person , Credentials [ CredentialIndex ] . Username ) )
2017-05-25 20:28:52 +00:00
{
2018-04-04 22:39:38 +00:00
if ( Speakers )
{
Speakers - > Speaker [ Speakers - > Count ] . Credential = & Credentials [ CredentialIndex ] ;
+ + Speakers - > Count ;
}
2017-08-18 22:46:58 +00:00
Found = TRUE ;
if ( * HasCreditsMenu = = FALSE )
{
CopyStringToBuffer ( CreditsMenu ,
2017-11-11 00:34:47 +00:00
" <div class= \" menu credits \" > \n "
" <div class= \" mouse_catcher \" ></div> \n "
" <span>Credits</span> \n "
" <div class= \" credits_container \" > \n " ) ;
2017-08-18 22:46:58 +00:00
* HasCreditsMenu = TRUE ;
}
CopyStringToBuffer ( CreditsMenu ,
" <span class= \" credit \" > \n " ) ;
if ( * Credentials [ CredentialIndex ] . HomepageURL )
{
CopyStringToBuffer ( CreditsMenu ,
" <a class= \" person \" href= \" %s \" target= \" _blank \" > \n "
" <div class= \" role \" >%s</div> \n "
" <div class= \" name \" >%s</div> \n "
" </a> \n " ,
Credentials [ CredentialIndex ] . HomepageURL ,
Role ,
Credentials [ CredentialIndex ] . CreditedName ) ;
2017-05-25 20:28:52 +00:00
}
else
{
2017-08-18 22:46:58 +00:00
CopyStringToBuffer ( CreditsMenu ,
" <div class= \" person \" > \n "
" <div class= \" role \" >%s</div> \n "
" <div class= \" name \" >%s</div> \n "
" </div> \n " ,
Role ,
Credentials [ CredentialIndex ] . CreditedName ) ;
2017-05-25 20:28:52 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-18 22:46:58 +00:00
if ( * Credentials [ CredentialIndex ] . SupportIcon & & * Credentials [ CredentialIndex ] . SupportURL )
2017-05-25 20:28:52 +00:00
{
2017-12-07 21:07:36 +00:00
buffer URLPrefix ;
2017-12-12 23:24:10 +00:00
ClaimBuffer ( & URLPrefix , " URLPrefix " , 1024 ) ;
2017-12-18 00:15:03 +00:00
ConstructURLPrefix ( & URLPrefix , INCLUDE_Images , PAGE_PLAYER ) ;
2017-11-11 00:34:47 +00:00
CopyStringToBuffer ( CreditsMenu ,
" <a class= \" support \" href= \" %s \" target= \" _blank \" ><div class= \" support_icon \" style= \" background-image: url(%s%s); \" ></div></a> \n " ,
Credentials [ CredentialIndex ] . SupportURL ,
2017-12-07 21:07:36 +00:00
URLPrefix . Location ,
2017-11-11 00:34:47 +00:00
Credentials [ CredentialIndex ] . SupportIcon ) ;
2017-12-07 21:07:36 +00:00
DeclaimBuffer ( & URLPrefix ) ;
2017-05-25 20:28:52 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-18 22:46:58 +00:00
CopyStringToBuffer ( CreditsMenu ,
" </span> \n " ) ;
2017-05-25 20:28:52 +00:00
}
2017-08-18 22:46:58 +00:00
}
2018-04-04 22:39:38 +00:00
return Found ? RC_SUCCESS : CreditsError_NoCredentials ;
2017-08-18 22:46:58 +00:00
}
2017-06-25 18:22:54 +00:00
2018-04-04 22:39:38 +00:00
void
ClearString ( char * String )
{
while ( * String )
{
* String + + = ' \0 ' ;
}
}
void
InitialString ( char * Dest , char * Src )
2017-08-18 22:46:58 +00:00
{
2018-04-04 22:39:38 +00:00
ClearString ( Dest ) ;
* Dest + + = * Src + + ;
while ( * Src + + )
2017-08-18 22:46:58 +00:00
{
2018-04-04 22:39:38 +00:00
if ( * Src = = ' ' )
2017-05-25 20:28:52 +00:00
{
2018-04-04 22:39:38 +00:00
+ + Src ;
if ( * Src )
{
* Dest + + = * Src ;
}
2017-08-18 22:46:58 +00:00
}
}
2018-04-04 22:39:38 +00:00
}
void
GetFirstSubstring ( char * Dest , char * Src )
{
ClearString ( Dest ) ;
while ( * Src & & * Src ! = ' ' )
2017-08-18 22:46:58 +00:00
{
2018-04-04 22:39:38 +00:00
* Dest + + = * Src + + ;
}
}
void
InitialAndGetFinalString ( char * Dest , char * Src )
{
ClearString ( Dest ) ;
int SrcLength = StringLength ( Src ) ;
char * SrcPtr = Src + SrcLength - 1 ;
while ( SrcPtr > Src & & * SrcPtr ! = ' ' )
{
- - SrcPtr ;
}
if ( * SrcPtr = = ' ' & & SrcPtr - Src < SrcLength - 1 )
{
+ + SrcPtr ;
}
if ( Src < SrcPtr )
{
* Dest + + = * Src + + ;
* Dest + + = ' . ' ;
* Dest + + = ' ' ;
while ( Src < SrcPtr - 1 )
2017-08-18 22:46:58 +00:00
{
2018-04-04 22:39:38 +00:00
if ( * Src = = ' ' )
{
+ + Src ;
if ( * Src )
{
* Dest + + = * Src ;
* Dest + + = ' . ' ;
* Dest + + = ' ' ;
}
}
+ + Src ;
}
}
CopyString ( Dest , SrcPtr ) ;
}
bool
AbbreviationsClash ( speakers * Speakers )
{
for ( int i = 0 ; i < Speakers - > Count ; + + i )
{
for ( int j = i + 1 ; j < Speakers - > Count ; + + j )
{
if ( ! StringsDiffer ( Speakers - > Speaker [ i ] . Abbreviation , Speakers - > Speaker [ j ] . Abbreviation ) )
{
return TRUE ;
}
}
}
return FALSE ;
}
void
SortAndAbbreviateSpeakers ( speakers * Speakers )
{
for ( int i = 0 ; i < Speakers - > Count ; + + i )
{
for ( int j = i + 1 ; j < Speakers - > Count ; + + j )
{
if ( StringsDiffer ( Speakers - > Speaker [ i ] . Credential - > Username , Speakers - > Speaker [ j ] . Credential - > Username ) > 0 )
{
credential_info * Temp = Speakers - > Speaker [ j ] . Credential ;
Speakers - > Speaker [ j ] . Credential = Speakers - > Speaker [ i ] . Credential ;
Speakers - > Speaker [ i ] . Credential = Temp ;
break ;
}
}
}
for ( int i = 0 ; i < Speakers - > Count ; + + i )
{
StringToColourHash ( & Speakers - > Speaker [ i ] . Colour , Speakers - > Speaker [ i ] . Credential - > Username ) ;
InitialString ( Speakers - > Speaker [ i ] . Abbreviation , Speakers - > Speaker [ i ] . Credential - > CreditedName ) ;
}
int Attempt = 0 ;
while ( AbbreviationsClash ( Speakers ) )
{
for ( int i = 0 ; i < Speakers - > Count ; + + i )
{
switch ( Attempt )
{
case 0 : GetFirstSubstring ( Speakers - > Speaker [ i ] . Abbreviation , Speakers - > Speaker [ i ] . Credential - > CreditedName ) ; break ;
case 1 : InitialAndGetFinalString ( Speakers - > Speaker [ i ] . Abbreviation , Speakers - > Speaker [ i ] . Credential - > CreditedName ) ; break ;
case 2 : ClearCopyStringNoFormat ( Speakers - > Speaker [ i ] . Abbreviation , sizeof ( Speakers - > Speaker [ i ] . Abbreviation ) , Speakers - > Speaker [ i ] . Credential - > Username ) ; break ;
}
2017-08-18 22:46:58 +00:00
}
2018-04-04 22:39:38 +00:00
+ + Attempt ;
}
}
int
BuildCredits ( buffer * CreditsMenu , bool * HasCreditsMenu , HMML_VideoMetaData * Metadata , speakers * Speakers )
// TODO(matt): Make this take the Credentials, once we are parsing them from a config
{
if ( SearchCredentials ( CreditsMenu , HasCreditsMenu , Metadata - > member , " Host " , Speakers ) = = CreditsError_NoCredentials )
{
printf ( " No credentials for member %s. Please contact matt@handmadedev.org with their: \n "
" Full name \n "
" Homepage URL (optional) \n "
" Financial support info, e.g. Patreon URL (optional) \n " , Metadata - > member ) ;
return CreditsError_NoCredentials ;
2017-08-18 22:46:58 +00:00
}
2017-10-18 21:07:29 +00:00
if ( Metadata - > co_host_count > 0 )
2017-08-18 22:46:58 +00:00
{
2017-10-18 21:07:29 +00:00
for ( int i = 0 ; i < Metadata - > co_host_count ; + + i )
2017-08-18 22:46:58 +00:00
{
2018-04-04 22:39:38 +00:00
if ( SearchCredentials ( CreditsMenu , HasCreditsMenu , Metadata - > co_hosts [ i ] , " Co-host " , Speakers ) = = CreditsError_NoCredentials )
2017-05-25 20:28:52 +00:00
{
2018-02-05 23:57:17 +00:00
printf ( " No credentials for co-host %s. Please contact matt@handmadedev.org with their: \n "
2017-08-18 22:46:58 +00:00
" Full name \n "
" Homepage URL (optional) \n "
2017-10-18 21:07:29 +00:00
" Financial support info, e.g. Patreon URL (optional) \n " , Metadata - > co_hosts [ i ] ) ;
2018-02-05 23:57:17 +00:00
return CreditsError_NoCredentials ;
2017-05-25 20:28:52 +00:00
}
2017-08-18 22:46:58 +00:00
}
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
if ( Metadata - > guest_count > 0 )
2017-08-18 22:46:58 +00:00
{
2017-10-18 21:07:29 +00:00
for ( int i = 0 ; i < Metadata - > guest_count ; + + i )
2017-08-18 22:46:58 +00:00
{
2018-04-04 22:39:38 +00:00
if ( SearchCredentials ( CreditsMenu , HasCreditsMenu , Metadata - > guests [ i ] , " Guest " , Speakers ) = = CreditsError_NoCredentials )
2017-05-25 20:28:52 +00:00
{
2018-02-05 23:57:17 +00:00
printf ( " No credentials for guest %s. Please contact matt@handmadedev.org with their: \n "
2017-08-18 22:46:58 +00:00
" Full name \n "
" Homepage URL (optional) \n "
2017-10-18 21:07:29 +00:00
" Financial support info, e.g. Patreon URL (optional) \n " , Metadata - > guests [ i ] ) ;
2018-02-05 23:57:17 +00:00
return CreditsError_NoCredentials ;
2017-05-25 20:28:52 +00:00
}
}
}
2017-06-25 18:22:54 +00:00
2018-04-04 22:39:38 +00:00
if ( Speakers - > Count > 1 )
{
SortAndAbbreviateSpeakers ( Speakers ) ;
}
2017-10-18 21:07:29 +00:00
if ( Metadata - > annotator_count > 0 )
2017-05-25 20:28:52 +00:00
{
2017-10-18 21:07:29 +00:00
for ( int i = 0 ; i < Metadata - > annotator_count ; + + i )
2017-05-25 20:28:52 +00:00
{
2018-04-04 22:39:38 +00:00
if ( SearchCredentials ( CreditsMenu , HasCreditsMenu , Metadata - > annotators [ i ] , " Annotator " , 0 ) = = CreditsError_NoCredentials )
2017-08-18 22:46:58 +00:00
{
2018-02-05 23:57:17 +00:00
printf ( " No credentials for annotator %s. Please contact matt@handmadedev.org with their: \n "
2017-08-18 22:46:58 +00:00
" Full name \n "
" Homepage URL (optional) \n "
2017-10-18 21:07:29 +00:00
" Financial support info, e.g. Patreon URL (optional) \n " , Metadata - > annotators [ i ] ) ;
2018-02-05 23:57:17 +00:00
return CreditsError_NoCredentials ;
2017-08-18 22:46:58 +00:00
}
2017-05-25 20:28:52 +00:00
}
2017-08-18 22:46:58 +00:00
}
else
{
if ( * HasCreditsMenu = = TRUE )
2017-05-25 20:28:52 +00:00
{
2017-08-18 22:46:58 +00:00
CopyStringToBuffer ( CreditsMenu ,
" </div> \n "
" </div> \n " ) ;
2017-05-25 20:28:52 +00:00
}
2018-02-05 23:57:17 +00:00
fprintf ( stderr , " Missing \" annotator \" in the [video] node \n " ) ;
2017-08-18 22:46:58 +00:00
return CreditsError_NoAnnotator ;
2017-05-25 20:28:52 +00:00
}
2017-08-18 22:46:58 +00:00
if ( * HasCreditsMenu = = TRUE )
2017-05-25 20:28:52 +00:00
{
2017-08-18 22:46:58 +00:00
CopyStringToBuffer ( CreditsMenu ,
" </div> \n "
" </div> \n " ) ;
2017-05-25 20:28:52 +00:00
}
2017-06-25 18:22:54 +00:00
2018-02-05 23:57:17 +00:00
return RC_SUCCESS ;
2017-05-25 20:28:52 +00:00
}
2018-04-05 21:25:23 +00:00
enum
{
REF_SITE = 1 < < 0 ,
REF_PAGE = 1 < < 1 ,
REF_URL = 1 < < 2 ,
REF_TITLE = 1 < < 3 ,
REF_ARTICLE = 1 < < 4 ,
REF_AUTHOR = 1 < < 5 ,
REF_EDITOR = 1 < < 6 ,
REF_PUBLISHER = 1 < < 7 ,
REF_ISBN = 1 < < 8 ,
} reference_fields ;
2017-04-21 01:25:40 +00:00
int
2017-10-18 21:07:29 +00:00
BuildReference ( ref_info * ReferencesArray , int RefIdentifier , int UniqueRefs , HMML_Reference * Ref , HMML_Annotation * Anno )
2017-04-19 01:10:45 +00:00
{
2017-07-29 23:01:39 +00:00
int Mask = 0 ;
2017-10-18 21:07:29 +00:00
if ( Ref - > site ) { Mask | = REF_SITE ; }
if ( Ref - > page ) { Mask | = REF_PAGE ; }
if ( Ref - > url ) { Mask | = REF_URL ; }
if ( Ref - > title ) { Mask | = REF_TITLE ; }
if ( Ref - > article ) { Mask | = REF_ARTICLE ; }
if ( Ref - > author ) { Mask | = REF_AUTHOR ; }
if ( Ref - > editor ) { Mask | = REF_EDITOR ; }
if ( Ref - > publisher ) { Mask | = REF_PUBLISHER ; }
if ( Ref - > isbn ) { Mask | = REF_ISBN ; }
2017-07-29 23:01:39 +00:00
2018-04-05 21:25:23 +00:00
// TODO(matt): Consider handling the various combinations more flexibly
switch ( Mask )
2017-04-21 01:25:40 +00:00
{
2018-04-05 21:25:23 +00:00
case ( REF_URL | REF_TITLE | REF_AUTHOR | REF_PUBLISHER | REF_ISBN ) :
{
CopyString ( ReferencesArray [ UniqueRefs ] . ID , Ref - > isbn ) ;
CopyString ( ReferencesArray [ UniqueRefs ] . Source , " %s (%s) " , Ref - > author , Ref - > publisher ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > title ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_AUTHOR | REF_SITE | REF_PAGE | REF_URL ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . Source , Ref - > site ) ;
CopyString ( ReferencesArray [ UniqueRefs ] . RefTitle , " %s: \" %s \" " , Ref - > author , Ref - > page ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_PAGE | REF_URL | REF_TITLE ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . Source , Ref - > title ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > page ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_SITE | REF_PAGE | REF_URL ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . Source , Ref - > site ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > page ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_SITE | REF_URL | REF_TITLE ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . Source , Ref - > site ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > title ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_TITLE | REF_AUTHOR | REF_ISBN ) :
{
CopyString ( ReferencesArray [ UniqueRefs ] . ID , Ref - > isbn ) ;
CopyString ( ReferencesArray [ UniqueRefs ] . Source , Ref - > author ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > title ) ;
CopyString ( ReferencesArray [ UniqueRefs ] . URL , " http://www.openisbn.com/isbn/%s " , Ref - > isbn ) ;
} break ;
case ( REF_URL | REF_ARTICLE | REF_AUTHOR ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyString ( ReferencesArray [ UniqueRefs ] . Source , Ref - > author ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > article ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_URL | REF_TITLE | REF_AUTHOR ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyString ( ReferencesArray [ UniqueRefs ] . Source , Ref - > author ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > title ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_URL | REF_TITLE | REF_PUBLISHER ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyString ( ReferencesArray [ UniqueRefs ] . Source , Ref - > publisher ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > title ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_URL | REF_TITLE ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > title ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
case ( REF_SITE | REF_URL ) :
{
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . ID , Ref - > url ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . RefTitle , Ref - > site ) ;
CopyStringNoFormat ( ReferencesArray [ UniqueRefs ] . URL , Ref - > url ) ;
} break ;
default : return RC_INVALID_REFERENCE ; break ;
2017-04-21 01:25:40 +00:00
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
CopyString ( ReferencesArray [ UniqueRefs ] . Identifier [ ReferencesArray [ UniqueRefs ] . IdentifierCount ] . Timecode , Anno - > time ) ;
2017-04-19 01:10:45 +00:00
ReferencesArray [ UniqueRefs ] . Identifier [ ReferencesArray [ UniqueRefs ] . IdentifierCount ] . Identifier = RefIdentifier ;
2018-04-05 21:25:23 +00:00
return RC_SUCCESS ;
2017-04-19 01:10:45 +00:00
}
2017-05-21 06:35:16 +00:00
void
2017-10-18 21:07:29 +00:00
InsertCategory ( categories * GlobalTopics , categories * LocalTopics , categories * GlobalMedia , categories * LocalMedia , char * Marker )
2017-05-21 06:35:16 +00:00
{
2017-05-24 21:56:36 +00:00
bool IsMedium = FALSE ;
2017-06-25 18:22:54 +00:00
2017-08-19 21:41:51 +00:00
int CategoryMediumIndex ;
for ( CategoryMediumIndex = 0 ; CategoryMediumIndex < ArrayCount ( CategoryMedium ) ; + + CategoryMediumIndex )
2017-05-21 06:35:16 +00:00
{
2017-08-19 21:41:51 +00:00
if ( ! StringsDiffer ( CategoryMedium [ CategoryMediumIndex ] . Medium , Marker ) )
2017-05-21 06:35:16 +00:00
{
2017-05-24 21:56:36 +00:00
IsMedium = TRUE ;
break ;
2017-05-21 06:35:16 +00:00
}
2017-05-24 21:56:36 +00:00
}
2017-06-25 18:22:54 +00:00
2017-05-24 21:56:36 +00:00
if ( IsMedium )
{
2018-01-15 00:03:11 +00:00
int MediumIndex ;
for ( MediumIndex = 0 ; MediumIndex < LocalMedia - > Count ; + + MediumIndex )
2017-05-22 21:37:00 +00:00
{
2018-01-15 00:03:11 +00:00
if ( ! StringsDiffer ( CategoryMedium [ CategoryMediumIndex ] . Medium , LocalMedia - > Category [ MediumIndex ] . Marker ) )
2017-05-22 21:37:00 +00:00
{
2017-05-24 21:56:36 +00:00
return ;
2017-05-22 21:37:00 +00:00
}
2018-01-15 00:03:11 +00:00
if ( ( StringsDiffer ( CategoryMedium [ CategoryMediumIndex ] . WrittenName , LocalMedia - > Category [ MediumIndex ] . WrittenText ) ) < 0 )
2017-05-24 21:56:36 +00:00
{
2017-08-19 21:41:51 +00:00
int CategoryCount ;
2018-01-15 00:03:11 +00:00
for ( CategoryCount = LocalMedia - > Count ; CategoryCount > MediumIndex ; - - CategoryCount )
2017-05-24 21:56:36 +00:00
{
2017-10-18 21:07:29 +00:00
CopyString ( LocalMedia - > Category [ CategoryCount ] . Marker , LocalMedia - > Category [ CategoryCount - 1 ] . Marker ) ;
CopyString ( LocalMedia - > Category [ CategoryCount ] . WrittenText , LocalMedia - > Category [ CategoryCount - 1 ] . WrittenText ) ;
2017-05-24 21:56:36 +00:00
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
CopyString ( LocalMedia - > Category [ CategoryCount ] . Marker , CategoryMedium [ CategoryMediumIndex ] . Medium ) ;
CopyString ( LocalMedia - > Category [ CategoryCount ] . WrittenText , CategoryMedium [ CategoryMediumIndex ] . WrittenName ) ;
2017-05-24 21:56:36 +00:00
break ;
}
2017-05-22 21:37:00 +00:00
}
2017-06-25 18:22:54 +00:00
2018-01-15 00:03:11 +00:00
if ( MediumIndex = = LocalMedia - > Count )
2017-05-24 21:56:36 +00:00
{
2018-01-15 00:03:11 +00:00
CopyString ( LocalMedia - > Category [ MediumIndex ] . Marker , CategoryMedium [ CategoryMediumIndex ] . Medium ) ;
CopyString ( LocalMedia - > Category [ MediumIndex ] . WrittenText , CategoryMedium [ CategoryMediumIndex ] . WrittenName ) ;
2017-05-24 21:56:36 +00:00
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
+ + LocalMedia - > Count ;
2018-01-15 00:03:11 +00:00
for ( MediumIndex = 0 ; MediumIndex < GlobalMedia - > Count ; + + MediumIndex )
2017-10-18 21:07:29 +00:00
{
2018-01-15 00:03:11 +00:00
if ( ! StringsDiffer ( CategoryMedium [ CategoryMediumIndex ] . Medium , GlobalMedia - > Category [ MediumIndex ] . Marker ) )
2017-10-18 21:07:29 +00:00
{
return ;
}
2018-01-15 00:03:11 +00:00
if ( ( StringsDiffer ( CategoryMedium [ CategoryMediumIndex ] . WrittenName , GlobalMedia - > Category [ MediumIndex ] . WrittenText ) ) < 0 )
2017-10-18 21:07:29 +00:00
{
int CategoryCount ;
2018-01-15 00:03:11 +00:00
for ( CategoryCount = GlobalMedia - > Count ; CategoryCount > MediumIndex ; - - CategoryCount )
2017-10-18 21:07:29 +00:00
{
CopyString ( GlobalMedia - > Category [ CategoryCount ] . Marker , GlobalMedia - > Category [ CategoryCount - 1 ] . Marker ) ;
CopyString ( GlobalMedia - > Category [ CategoryCount ] . WrittenText , GlobalMedia - > Category [ CategoryCount - 1 ] . WrittenText ) ;
}
CopyString ( GlobalMedia - > Category [ CategoryCount ] . Marker , CategoryMedium [ CategoryMediumIndex ] . Medium ) ;
CopyString ( GlobalMedia - > Category [ CategoryCount ] . WrittenText , CategoryMedium [ CategoryMediumIndex ] . WrittenName ) ;
break ;
}
}
2018-01-15 00:03:11 +00:00
if ( MediumIndex = = GlobalMedia - > Count )
2017-10-18 21:07:29 +00:00
{
2018-01-15 00:03:11 +00:00
CopyString ( GlobalMedia - > Category [ MediumIndex ] . Marker , CategoryMedium [ CategoryMediumIndex ] . Medium ) ;
CopyString ( GlobalMedia - > Category [ MediumIndex ] . WrittenText , CategoryMedium [ CategoryMediumIndex ] . WrittenName ) ;
2017-10-18 21:07:29 +00:00
}
+ + GlobalMedia - > Count ;
2017-05-21 06:35:16 +00:00
}
2017-05-24 21:56:36 +00:00
else
{
2018-01-15 00:03:11 +00:00
int TopicIndex ;
for ( TopicIndex = 0 ; TopicIndex < LocalTopics - > Count ; + + TopicIndex )
2017-05-24 21:56:36 +00:00
{
2018-01-15 00:03:11 +00:00
if ( ! StringsDiffer ( Marker , LocalTopics - > Category [ TopicIndex ] . Marker ) )
2017-05-24 21:56:36 +00:00
{
return ;
}
2018-01-15 00:03:11 +00:00
if ( ( StringsDiffer ( Marker , LocalTopics - > Category [ TopicIndex ] . Marker ) ) < 0 )
2017-05-24 21:56:36 +00:00
{
2017-08-19 21:41:51 +00:00
int CategoryCount ;
2018-01-15 00:03:11 +00:00
for ( CategoryCount = LocalTopics - > Count ; CategoryCount > TopicIndex ; - - CategoryCount )
2017-05-24 21:56:36 +00:00
{
2017-10-18 21:07:29 +00:00
CopyString ( LocalTopics - > Category [ CategoryCount ] . Marker , LocalTopics - > Category [ CategoryCount - 1 ] . Marker ) ;
2017-05-24 21:56:36 +00:00
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
CopyString ( LocalTopics - > Category [ CategoryCount ] . Marker , Marker ) ;
2017-05-24 21:56:36 +00:00
break ;
}
}
2017-06-25 18:22:54 +00:00
2018-01-15 00:03:11 +00:00
if ( TopicIndex = = LocalTopics - > Count )
2017-05-21 06:35:16 +00:00
{
2018-01-15 00:03:11 +00:00
CopyString ( LocalTopics - > Category [ TopicIndex ] . Marker , Marker ) ;
2017-05-21 06:35:16 +00:00
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
+ + LocalTopics - > Count ;
2018-01-15 00:03:11 +00:00
for ( TopicIndex = 0 ; TopicIndex < GlobalTopics - > Count ; + + TopicIndex )
2017-10-18 21:07:29 +00:00
{
2018-01-15 00:03:11 +00:00
if ( ! StringsDiffer ( Marker , GlobalTopics - > Category [ TopicIndex ] . Marker ) )
2017-10-18 21:07:29 +00:00
{
return ;
}
2018-01-15 00:03:11 +00:00
// NOTE(matt): This successfully sorts "nullTopic" at the end, but maybe figure out a more general way to force the
// order of stuff, perhaps blocks of dudes that should sort to the start / end
if ( ( ( StringsDiffer ( Marker , GlobalTopics - > Category [ TopicIndex ] . Marker ) ) < 0 | | ! StringsDiffer ( GlobalTopics - > Category [ TopicIndex ] . Marker , " nullTopic " ) ) )
2017-10-18 21:07:29 +00:00
{
2018-01-15 00:03:11 +00:00
if ( StringsDiffer ( Marker , " nullTopic " ) ) // NOTE(matt): This test (with the above || condition) forces nullTopic never to be inserted, only appended
2017-10-18 21:07:29 +00:00
{
2018-01-15 00:03:11 +00:00
int CategoryCount ;
for ( CategoryCount = GlobalTopics - > Count ; CategoryCount > TopicIndex ; - - CategoryCount )
{
CopyString ( GlobalTopics - > Category [ CategoryCount ] . Marker , GlobalTopics - > Category [ CategoryCount - 1 ] . Marker ) ;
}
2017-10-18 21:07:29 +00:00
2018-01-15 00:03:11 +00:00
CopyString ( GlobalTopics - > Category [ CategoryCount ] . Marker , Marker ) ;
break ;
}
2017-10-18 21:07:29 +00:00
}
}
2018-01-15 00:03:11 +00:00
if ( TopicIndex = = GlobalTopics - > Count )
2017-10-18 21:07:29 +00:00
{
2018-01-15 00:03:11 +00:00
CopyString ( GlobalTopics - > Category [ TopicIndex ] . Marker , Marker ) ;
2017-10-18 21:07:29 +00:00
}
+ + GlobalTopics - > Count ;
2017-05-21 06:35:16 +00:00
}
}
2017-03-25 03:10:15 +00:00
void
2018-01-15 00:03:11 +00:00
BuildCategories ( buffer * AnnotationClass , buffer * CategoryIcons , categories * LocalTopics , categories * LocalMedia , int * MarkerIndex , char * DefaultMedium )
2017-03-25 03:10:15 +00:00
{
2018-01-15 00:03:11 +00:00
bool CategoriesSpan = FALSE ;
if ( ! ( LocalTopics - > Count = = 1 & & ! StringsDiffer ( LocalTopics - > Category [ 0 ] . Marker , " nullTopic " )
& & LocalMedia - > Count = = 1 & & ! StringsDiffer ( LocalMedia - > Category [ 0 ] . Marker , DefaultMedium ) ) )
{
CategoriesSpan = TRUE ;
CopyStringToBuffer ( CategoryIcons , " <span class= \" cineraCategories \" > " ) ;
}
if ( LocalTopics - > Count = = 1 & & ! StringsDiffer ( LocalTopics - > Category [ 0 ] . Marker , " nullTopic " ) )
{
char SanitisedMarker [ StringLength ( LocalTopics - > Category [ 0 ] . Marker ) ] ;
CopyString ( SanitisedMarker , LocalTopics - > Category [ 0 ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
CopyStringToBuffer ( AnnotationClass , " cat_%s " , SanitisedMarker ) ;
}
else
2017-03-25 03:10:15 +00:00
{
2017-10-18 21:07:29 +00:00
for ( int i = 0 ; i < LocalTopics - > Count ; + + i )
2017-03-25 03:10:15 +00:00
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( LocalTopics - > Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , LocalTopics - > Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
CopyStringToBuffer ( CategoryIcons , " <div title= \" %s \" class= \" category %s \" ></div> " ,
LocalTopics - > Category [ i ] . Marker ,
SanitisedMarker ) ;
2017-06-25 18:22:54 +00:00
2017-08-19 21:41:51 +00:00
CopyStringToBuffer ( AnnotationClass , " cat_%s " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker ) ;
2017-06-11 22:49:04 +00:00
}
}
2017-06-25 18:22:54 +00:00
2018-01-15 00:03:11 +00:00
if ( LocalMedia - > Count = = 1 & & ! StringsDiffer ( LocalMedia - > Category [ 0 ] . Marker , DefaultMedium ) )
2017-06-11 22:49:04 +00:00
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( LocalMedia - > Category [ 0 ] . Marker ) ] ;
CopyString ( SanitisedMarker , LocalMedia - > Category [ 0 ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
CopyStringToBuffer ( AnnotationClass , " %s " , SanitisedMarker ) ;
}
else
{
for ( int i = 0 ; i < LocalMedia - > Count ; + + i )
2017-11-11 00:34:47 +00:00
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( LocalMedia - > Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , LocalMedia - > Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
if ( ! StringsDiffer ( LocalMedia - > Category [ i ] . Marker , " afk " ) ) // TODO(matt): Initially hidden config
{
CopyStringToBuffer ( AnnotationClass , " off_%s skip " , SanitisedMarker ) ; // TODO(matt): Bulletproof this?
}
else
{
for ( int j = 0 ; j < ArrayCount ( CategoryMedium ) ; + + j )
{
if ( ! StringsDiffer ( LocalMedia - > Category [ i ] . Marker , CategoryMedium [ j ] . Medium ) )
{
CopyStringToBuffer ( CategoryIcons , " <div title= \" %s \" class= \" categoryMedium %s \" >%s</div> " ,
LocalMedia - > Category [ i ] . WrittenText ,
LocalMedia - > Category [ i ] . Marker ,
CategoryMedium [ j ] . Icon ) ;
CopyStringToBuffer ( AnnotationClass , " %s " , SanitisedMarker ) ;
}
}
}
2017-11-11 00:34:47 +00:00
}
2017-06-11 22:49:04 +00:00
}
2017-06-25 18:22:54 +00:00
2018-01-15 00:03:11 +00:00
if ( CategoriesSpan )
{
CopyStringToBuffer ( CategoryIcons , " </span> " ) ;
}
2017-08-19 21:41:51 +00:00
CopyStringToBuffer ( AnnotationClass , " \" " ) ;
2017-03-25 03:10:15 +00:00
}
2017-04-23 02:47:42 +00:00
int
StringToInt ( char * String )
{
int Result = 0 ;
while ( * String )
{
Result = Result * 10 + ( * String - ' 0 ' ) ;
+ + String ;
}
return Result ;
}
2017-08-18 22:46:58 +00:00
size_t
CurlIntoBuffer ( char * InPtr , size_t CharLength , size_t Chars , char * * OutputPtr )
{
int Length = CharLength * Chars ;
int i ;
for ( i = 0 ; InPtr [ i ] & & i < Length ; + + i )
{
* ( ( * OutputPtr ) + + ) = InPtr [ i ] ;
}
* * OutputPtr = ' \0 ' ;
return Length ;
} ;
2017-08-11 22:41:52 +00:00
void
CurlQuotes ( buffer * QuoteStaging , char * QuotesURL )
{
2018-02-28 01:04:06 +00:00
fprintf ( stderr , " \ e[0;35mFetching \ e[0m quotes: %s \n " , QuotesURL ) ;
2017-08-11 22:41:52 +00:00
CURL * curl = curl_easy_init ( ) ;
if ( curl ) {
2018-02-28 20:18:11 +00:00
CURLcode CurlReturnCode ;
2017-08-11 22:41:52 +00:00
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , & QuoteStaging - > Ptr ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , CurlIntoBuffer ) ;
curl_easy_setopt ( curl , CURLOPT_URL , QuotesURL ) ;
2018-02-28 20:18:11 +00:00
if ( ( CurlReturnCode = curl_easy_perform ( curl ) ) )
2017-08-11 22:41:52 +00:00
{
2018-02-28 20:18:11 +00:00
fprintf ( stderr , " %s \n " , curl_easy_strerror ( CurlReturnCode ) ) ;
2017-08-11 22:41:52 +00:00
}
curl_easy_cleanup ( curl ) ;
}
}
2017-08-18 22:46:58 +00:00
int
2017-10-18 21:07:29 +00:00
SearchQuotes ( buffer * QuoteStaging , int CacheSize , quote_info * Info , int ID )
2017-08-18 22:46:58 +00:00
{
2017-10-18 21:07:29 +00:00
QuoteStaging - > Ptr = QuoteStaging - > Location ;
while ( QuoteStaging - > Ptr - QuoteStaging - > Location < CacheSize )
2017-08-18 22:46:58 +00:00
{
char InID [ 4 ] = { 0 } ;
char InTime [ 16 ] = { 0 } ;
char * OutPtr = InID ;
2017-10-18 21:07:29 +00:00
QuoteStaging - > Ptr + = CopyStringNoFormatT ( OutPtr , QuoteStaging - > Ptr , ' , ' ) ;
2017-08-18 22:46:58 +00:00
if ( StringToInt ( InID ) = = ID )
{
2017-10-18 21:07:29 +00:00
QuoteStaging - > Ptr + = 1 ;
2017-08-18 22:46:58 +00:00
OutPtr = InTime ;
2017-10-18 21:07:29 +00:00
QuoteStaging - > Ptr + = CopyStringNoFormatT ( OutPtr , QuoteStaging - > Ptr , ' , ' ) ;
2017-08-18 22:46:58 +00:00
long int Time = StringToInt ( InTime ) ;
char DayString [ 3 ] ;
strftime ( DayString , 3 , " %d " , gmtime ( & Time ) ) ;
int Day = StringToInt ( DayString ) ;
char DaySuffix [ 3 ] ; if ( DayString [ 1 ] = = ' 1 ' & & Day ! = 11 ) { CopyString ( DaySuffix , " st " ) ; }
else if ( DayString [ 1 ] = = ' 2 ' & & Day ! = 12 ) { CopyString ( DaySuffix , " nd " ) ; }
else if ( DayString [ 1 ] = = ' 3 ' & & Day ! = 13 ) { CopyString ( DaySuffix , " rd " ) ; }
else { CopyString ( DaySuffix , " th " ) ; }
char MonthYear [ 32 ] ;
strftime ( MonthYear , 32 , " %B, %Y " , gmtime ( & Time ) ) ;
CopyString ( Info - > Date , " %d%s %s " , Day , DaySuffix , MonthYear ) ;
2017-10-18 21:07:29 +00:00
QuoteStaging - > Ptr + = 1 ;
2017-08-18 22:46:58 +00:00
OutPtr = Info - > Text ;
2017-10-18 21:07:29 +00:00
QuoteStaging - > Ptr + = CopyStringNoFormatT ( OutPtr , QuoteStaging - > Ptr , ' \n ' ) ;
2017-08-18 22:46:58 +00:00
2017-10-18 21:07:29 +00:00
FreeBuffer ( QuoteStaging ) ;
2017-09-07 21:41:08 +00:00
return RC_FOUND ;
2017-08-18 22:46:58 +00:00
}
else
{
2017-10-18 21:07:29 +00:00
while ( * QuoteStaging - > Ptr ! = ' \n ' )
2017-08-18 22:46:58 +00:00
{
2017-10-18 21:07:29 +00:00
+ + QuoteStaging - > Ptr ;
2017-08-18 22:46:58 +00:00
}
2017-10-18 21:07:29 +00:00
+ + QuoteStaging - > Ptr ;
2017-08-18 22:46:58 +00:00
}
}
2017-09-07 21:41:08 +00:00
return RC_UNFOUND ;
2017-08-18 22:46:58 +00:00
}
2017-08-10 01:05:41 +00:00
int
2018-02-28 01:04:06 +00:00
BuildQuote ( quote_info * Info , char * Speaker , int ID , bool ShouldFetchQuotes )
2017-08-10 01:05:41 +00:00
{
2017-11-11 00:34:47 +00:00
char QuoteCacheDir [ 256 ] ;
2017-10-18 21:07:29 +00:00
CopyString ( QuoteCacheDir , " %s/quotes " , Config . CacheDir ) ;
2017-11-11 00:34:47 +00:00
char QuoteCachePath [ 256 ] ;
2017-08-10 01:05:41 +00:00
CopyString ( QuoteCachePath , " %s/%s " , QuoteCacheDir , Speaker ) ;
2018-02-28 01:04:06 +00:00
2017-08-10 01:05:41 +00:00
FILE * QuoteCache ;
2017-08-11 22:41:52 +00:00
char QuotesURL [ 256 ] ;
2018-02-28 20:18:11 +00:00
// TODO(matt): Make the URL configurable and also handle the case in which the .raw isn't available
2017-08-11 22:41:52 +00:00
CopyString ( QuotesURL , " https://dev.abaines.me.uk/quotes/%s.raw " , Speaker ) ;
bool CacheAvailable = FALSE ;
2017-08-10 01:05:41 +00:00
2017-08-11 22:41:52 +00:00
if ( ! ( QuoteCache = fopen ( QuoteCachePath , " a+ " ) ) )
2017-08-10 01:05:41 +00:00
{
2017-09-07 21:41:08 +00:00
if ( MakeDir ( QuoteCacheDir ) = = RC_SUCCESS )
2017-08-10 01:05:41 +00:00
{
2017-08-11 22:41:52 +00:00
CacheAvailable = TRUE ;
2018-02-28 01:04:06 +00:00
}
2017-08-11 22:41:52 +00:00
if ( ! ( QuoteCache = fopen ( QuoteCachePath , " a+ " ) ) )
{
2017-09-07 21:41:08 +00:00
fprintf ( stderr , " Unable to open quote cache %s: %s \n " , QuoteCachePath , strerror ( errno ) ) ;
2017-08-11 22:41:52 +00:00
}
else
{
CacheAvailable = TRUE ;
2017-08-10 01:05:41 +00:00
}
}
2017-08-11 22:41:52 +00:00
else
{
CacheAvailable = TRUE ;
}
2017-08-10 01:05:41 +00:00
buffer QuoteStaging ;
QuoteStaging . ID = " QuoteStaging " ;
QuoteStaging . Size = Kilobytes ( 256 ) ;
if ( ! ( QuoteStaging . Location = malloc ( QuoteStaging . Size ) ) )
{
2017-09-07 21:41:08 +00:00
fclose ( QuoteCache ) ;
return RC_ERROR_MEMORY ;
2017-08-10 01:05:41 +00:00
}
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
FILE * MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
fprintf ( MemLog , " Allocated QuoteStaging (%d) \n " , QuoteStaging . Size ) ;
fclose ( MemLog ) ;
printf ( " Allocated QuoteStaging (%d) \n " , QuoteStaging . Size ) ;
# endif
2017-08-10 01:05:41 +00:00
QuoteStaging . Ptr = QuoteStaging . Location ;
2017-08-11 22:41:52 +00:00
if ( CacheAvailable )
2017-08-10 01:05:41 +00:00
{
2017-08-11 22:41:52 +00:00
fseek ( QuoteCache , 0 , SEEK_END ) ;
int FileSize = ftell ( QuoteCache ) ;
fseek ( QuoteCache , 0 , SEEK_SET ) ;
fread ( QuoteStaging . Location , FileSize , 1 , QuoteCache ) ;
fclose ( QuoteCache ) ;
2017-08-10 01:05:41 +00:00
2018-02-28 20:18:11 +00:00
if ( ShouldFetchQuotes | | SearchQuotes ( & QuoteStaging , FileSize , Info , ID ) = = RC_UNFOUND )
2017-08-11 22:41:52 +00:00
{
CurlQuotes ( & QuoteStaging , QuotesURL ) ;
if ( ! ( QuoteCache = fopen ( QuoteCachePath , " w " ) ) )
2017-08-10 01:05:41 +00:00
{
2017-08-11 22:41:52 +00:00
perror ( QuoteCachePath ) ;
2017-08-10 01:05:41 +00:00
}
2017-08-11 22:41:52 +00:00
fwrite ( QuoteStaging . Location , QuoteStaging . Ptr - QuoteStaging . Location , 1 , QuoteCache ) ;
fclose ( QuoteCache ) ;
2017-08-10 01:05:41 +00:00
2017-08-11 22:41:52 +00:00
int CacheSize = QuoteStaging . Ptr - QuoteStaging . Location ;
QuoteStaging . Ptr = QuoteStaging . Location ;
2018-01-04 20:55:51 +00:00
if ( SearchQuotes ( & QuoteStaging , CacheSize , Info , ID ) = = RC_UNFOUND )
2017-08-11 22:41:52 +00:00
{
FreeBuffer ( & QuoteStaging ) ;
2018-01-04 20:55:51 +00:00
return RC_UNFOUND ;
2017-08-11 22:41:52 +00:00
}
2017-08-10 01:05:41 +00:00
}
2017-08-11 22:41:52 +00:00
}
else
{
CurlQuotes ( & QuoteStaging , QuotesURL ) ;
2017-08-10 01:05:41 +00:00
int CacheSize = QuoteStaging . Ptr - QuoteStaging . Location ;
QuoteStaging . Ptr = QuoteStaging . Location ;
2018-01-04 20:55:51 +00:00
if ( SearchQuotes ( & QuoteStaging , CacheSize , Info , ID ) = = RC_UNFOUND )
2017-08-10 01:05:41 +00:00
{
FreeBuffer ( & QuoteStaging ) ;
2018-01-04 20:55:51 +00:00
return RC_UNFOUND ;
2017-08-10 01:05:41 +00:00
}
}
2017-08-11 22:41:52 +00:00
2018-01-04 20:55:51 +00:00
return RC_SUCCESS ;
2017-08-10 01:05:41 +00:00
}
2017-09-07 21:41:08 +00:00
int
2017-10-18 21:07:29 +00:00
GenerateTopicColours ( char * Topic )
2017-03-31 00:56:50 +00:00
{
2018-01-15 00:03:11 +00:00
char SanitisedTopic [ StringLength ( Topic ) ] ;
CopyString ( SanitisedTopic , Topic ) ;
SanitisePunctuation ( SanitisedTopic ) ;
2017-03-31 00:56:50 +00:00
for ( int i = 0 ; i < ArrayCount ( CategoryMedium ) ; + + i )
{
2017-08-18 22:46:58 +00:00
if ( ! StringsDiffer ( Topic , CategoryMedium [ i ] . Medium ) )
2017-03-31 00:56:50 +00:00
{
2017-09-07 21:41:08 +00:00
return RC_NOOP ;
2017-03-31 00:56:50 +00:00
}
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
file_buffer Topics ;
Topics . Buffer . ID = " Topics " ;
2017-11-11 00:34:47 +00:00
if ( StringsDiffer ( Config . CSSDir , " " ) )
{
CopyString ( Topics . Path , " %s/%s/cinera_topics.css " , Config . RootDir , Config . CSSDir ) ;
}
else
{
CopyString ( Topics . Path , " %s/cinera_topics.css " , Config . RootDir ) ;
}
2017-10-18 21:07:29 +00:00
2017-12-24 01:57:11 +00:00
char * Ptr = Topics . Path + StringLength ( Topics . Path ) - 1 ;
while ( * Ptr ! = ' / ' )
{
- - Ptr ;
}
* Ptr = ' \0 ' ;
DIR * CSSDirHandle ; // TODO(matt): open()
if ( ! ( CSSDirHandle = opendir ( Topics . Path ) ) )
{
if ( MakeDir ( Topics . Path ) = = RC_ERROR_DIRECTORY )
{
LogError ( LOG_ERROR , " Unable to create directory %s: %s " , Topics . Path , strerror ( errno ) ) ;
fprintf ( stderr , " Unable to create directory %s: %s \n " , Topics . Path , strerror ( errno ) ) ;
return RC_ERROR_DIRECTORY ;
} ;
}
closedir ( CSSDirHandle ) ;
* Ptr = ' / ' ;
2017-10-18 21:07:29 +00:00
if ( ( Topics . Handle = fopen ( Topics . Path , " a+ " ) ) )
2017-03-31 00:56:50 +00:00
{
2017-10-18 21:07:29 +00:00
fseek ( Topics . Handle , 0 , SEEK_END ) ;
Topics . FileSize = ftell ( Topics . Handle ) ;
Topics . Buffer . Size = Topics . FileSize ;
fseek ( Topics . Handle , 0 , SEEK_SET ) ;
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
if ( ! ( Topics . Buffer . Location = malloc ( Topics . Buffer . Size ) ) )
2017-03-31 00:56:50 +00:00
{
2017-09-07 21:41:08 +00:00
return RC_ERROR_MEMORY ;
2017-03-31 00:56:50 +00:00
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
2017-11-11 00:34:47 +00:00
FILE * MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
2018-02-23 23:36:42 +00:00
fprintf ( MemLog , " Allocated Topics (%d) \n " , Topics . Buffer . Size ) ;
2017-11-11 00:34:47 +00:00
fclose ( MemLog ) ;
2018-02-23 23:36:42 +00:00
printf ( " Allocated Topics (%d) \n " , Topics . Buffer . Size ) ;
2017-10-18 21:07:29 +00:00
# endif
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
Topics . Buffer . Ptr = Topics . Buffer . Location ;
fread ( Topics . Buffer . Location , Topics . Buffer . Size , 1 , Topics . Handle ) ;
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
while ( Topics . Buffer . Ptr - Topics . Buffer . Location < Topics . Buffer . Size )
2017-03-31 00:56:50 +00:00
{
2017-10-18 21:07:29 +00:00
Topics . Buffer . Ptr + = StringLength ( " .category. " ) ;
2018-01-15 00:03:11 +00:00
if ( ! StringsDifferT ( SanitisedTopic , Topics . Buffer . Ptr , ' ' ) )
2017-03-31 00:56:50 +00:00
{
2017-10-18 21:07:29 +00:00
FreeBuffer ( & Topics . Buffer ) ;
fclose ( Topics . Handle ) ;
2017-09-07 21:41:08 +00:00
return RC_NOOP ;
2017-03-31 00:56:50 +00:00
}
2017-10-18 21:07:29 +00:00
while ( Topics . Buffer . Ptr - Topics . Buffer . Location < Topics . Buffer . Size & & * Topics . Buffer . Ptr ! = ' \n ' )
2017-03-31 00:56:50 +00:00
{
2017-10-18 21:07:29 +00:00
+ + Topics . Buffer . Ptr ;
2017-03-31 00:56:50 +00:00
}
2017-10-18 21:07:29 +00:00
+ + Topics . Buffer . Ptr ;
2017-03-31 00:56:50 +00:00
}
2017-06-25 18:22:54 +00:00
2018-01-15 00:03:11 +00:00
if ( ! StringsDiffer ( Topic , " nullTopic " ) )
{
fprintf ( Topics . Handle , " .category.%s { border: 1px solid transparent; background: transparent; } \n " ,
SanitisedTopic ) ;
}
else
{
hsl_colour Colour ;
StringToColourHash ( & Colour , Topic ) ;
fprintf ( Topics . Handle , " .category.%s { border: 1px solid hsl(%d, %d%%, %d%%); background: hsl(%d, %d%%, %d%%); } \n " ,
SanitisedTopic , Colour . Hue , Colour . Saturation , Colour . Lightness , Colour . Hue , Colour . Saturation , Colour . Lightness ) ;
}
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
fclose ( Topics . Handle ) ;
FreeBuffer ( & Topics . Buffer ) ;
2017-09-07 21:41:08 +00:00
return RC_SUCCESS ;
2017-03-31 00:56:50 +00:00
}
else
{
2017-12-24 01:57:11 +00:00
// NOTE(matt): Maybe it shouldn't be possible to hit this case now that we MakeDir the actually dir...
perror ( Topics . Path ) ;
2017-09-07 21:41:08 +00:00
return RC_ERROR_FILE ;
2017-03-31 00:56:50 +00:00
}
}
2017-08-29 20:35:28 +00:00
void
2017-10-18 21:07:29 +00:00
PrintUsage ( char * BinaryLocation , config * DefaultConfig )
2017-08-29 20:35:28 +00:00
{
2018-01-03 22:16:20 +00:00
fprintf ( stderr ,
" Usage: %s [option(s)] filename(s) \n "
2017-08-29 20:35:28 +00:00
" \n "
" Options: \n "
2018-01-03 22:16:20 +00:00
" Paths: \ e[1;30m(advisedly universal, but may be set per-(sub)project as required) \ e[0m \n "
2017-10-18 21:07:29 +00:00
" -r <root directory> \n "
" Override default root directory ( \" %s \" ) \n "
2017-12-12 23:24:10 +00:00
" -R <root URL> \n "
2017-11-11 00:34:47 +00:00
" Override default root URL ( \" %s \" ) \n "
2017-12-24 01:57:11 +00:00
" \ e[1;31mIMPORTANT \ e[0m: -r and -R must correspond to the same location \n "
2018-01-03 22:16:20 +00:00
" \ e[1;30mUNSUPPORTED: If you move files from RootDir, the RootURL should \n "
" correspond to the resulting location \ e[0m \n "
" \n "
" -c <CSS directory path> \n "
" Override default CSS directory ( \" %s \" ), relative to root \n "
" -i <images directory path> \n "
" Override default images directory ( \" %s \" ), relative to root \n "
" -j <JS directory path> \n "
" Override default JS directory ( \" %s \" ), relative to root \n "
" \n "
" Project Settings: \n "
" -p <project ID> \n "
" Set the project ID, equal to the \" project \" field in the HMML files \n "
" NOTE: Setting the project ID triggers PROJECT EDITION \n "
" -m <default medium> \n "
" Override default default medium ( \" %s \" ) \n "
" \ e[1;30mKnown project defaults: \n " ,
BinaryLocation ,
DefaultConfig - > RootDir ,
DefaultConfig - > RootURL ,
DefaultConfig - > CSSDir ,
DefaultConfig - > ImagesDir ,
DefaultConfig - > JSDir ,
DefaultConfig - > DefaultMedium ) ;
for ( int ProjectIndex = 0 ; ProjectIndex < ArrayCount ( ProjectInfo ) ; + + ProjectIndex )
{
fprintf ( stderr , " %s: " ,
ProjectInfo [ ProjectIndex ] . ProjectID ) ;
// NOTE(matt): This kind of thing really needs to loop over the dudes once to find the longest one
for ( int i = 11 ; i > StringLength ( ProjectInfo [ ProjectIndex ] . ProjectID ) ; i - = 4 )
{
fprintf ( stderr , " \t " ) ;
}
fprintf ( stderr , " %s \n " ,
ProjectInfo [ ProjectIndex ] . Medium ) ;
}
fprintf ( stderr ,
" \ e[0m -s <style> \n "
" Set the style / theme, corresponding to a cinera__*.css file \n "
" This is equal to the \" project \" field in the HMML files by default \n "
" -q \n "
" Quit after syncing with annotation files in project input directory \n "
" \ e[1;30mUNSUPPORTED: This is likely to be removed in the future \ e[0m \n "
" \n "
" Project Input Paths \n "
" -d <annotations directory> \n "
" Override default annotations directory ( \" %s \" ) \n "
" -t <templates directory> \n "
" Override default templates directory ( \" %s \" ) \n "
2017-12-12 23:24:10 +00:00
" \n "
2018-01-03 22:16:20 +00:00
" -x <index template location> \n "
" Set index template file path, either absolute or relative to \n "
" template directory, and enable integration \n "
" -y <player template location> \n "
" Set player template file path, either absolute or relative \n "
" to template directory, and enable integration \n "
" \n "
" Project Output Paths \n "
2017-12-12 23:24:10 +00:00
" -b <base output directory> \n "
" Override project's default base output directory ( \" %s \" ) \n "
" -B <base URL> \n "
" Override default base URL ( \" %s \" ) \n "
2018-01-03 22:16:20 +00:00
" NOTE: This must be set, if -n or -a are to be used \n "
2017-12-12 23:24:10 +00:00
" \n "
2018-01-03 22:16:20 +00:00
" -n <index location> \n "
" Override default index location ( \" %s \" ), relative to base \n "
" -a <player location> \n "
" Override default player location ( \" %s \" ), relative to base \n "
" NOTE: The PlayerURLPrefix is currently hardcoded in cinera.c but \n "
" will be configurable in the full configuration system \n "
2017-10-18 21:07:29 +00:00
" \n "
2018-01-03 22:16:20 +00:00
" Single Edition Output Path \n "
2017-12-12 23:24:10 +00:00
" -o <output location> \n "
2018-01-03 22:16:20 +00:00
" Override default output player location ( \" %s \" ) \n "
2017-10-18 21:07:29 +00:00
" \n "
2018-02-21 21:50:23 +00:00
" -e \n "
" Display (examine) index file and exit \n "
2017-12-12 23:24:10 +00:00
" -f \n "
" Force integration with an incomplete template \n "
2018-04-01 20:58:53 +00:00
" -g \n "
" Ignore video privacy status \n "
" NOTE: For use with projects whose videos are known to all be public, \n "
" to save us having to check their privacy status \n "
2018-02-28 01:04:06 +00:00
" -w \n "
" Force quote cache rebuild \ e[1;30m(memory aid: \" wget \" ) \ e[0m \n "
2017-12-12 23:24:10 +00:00
" \n "
" -l <n> \n "
" Override default log level (%d), where n is from 0 (terse) to 7 (verbose) \n "
2018-02-21 21:50:23 +00:00
" -u <seconds> \n "
2018-01-03 22:16:20 +00:00
" Override default update interval (%d) \n "
2017-08-29 20:35:28 +00:00
//" -c config location\n"
2017-11-18 00:27:33 +00:00
" -v \n "
2018-02-21 21:50:23 +00:00
" Display version and exit \n "
2017-08-29 20:35:28 +00:00
" -h \n "
2018-02-21 21:50:23 +00:00
" Display this help \n "
2017-08-29 20:35:28 +00:00
" \n "
" Template: \n "
2018-02-28 01:04:06 +00:00
" A complete Index Template shall contain exactly one each of the following tags: \n "
2017-12-12 23:24:10 +00:00
" <!-- __CINERA_INCLUDES__ --> \n "
" to put inside your own <head></head> \n "
2017-12-07 01:15:13 +00:00
" <!-- __CINERA_INDEX__ --> \n "
" \n "
2018-02-28 01:04:06 +00:00
" A complete Player Template shall contain exactly one each of the following tags: \n "
2017-12-12 23:24:10 +00:00
" <!-- __CINERA_INCLUDES__ --> \n "
" to put inside your own <head></head> \n "
2017-08-29 20:35:28 +00:00
" <!-- __CINERA_MENUS__ --> \n "
" <!-- __CINERA_PLAYER__ --> \n "
2017-12-12 23:24:10 +00:00
" <!-- __CINERA_SCRIPT__ --> \n "
" must come after <!-- __CINERA_MENUS__ --> and <!-- __CINERA_PLAYER__ --> \n "
2017-12-07 01:15:13 +00:00
" \n "
2018-02-28 01:04:06 +00:00
" Optional tags available for use in your Player Template: \n "
2018-02-21 21:50:23 +00:00
" <!-- __CINERA_TITLE__ --> \n "
" <!-- __CINERA_VIDEO_ID__ --> \n "
" \n "
2017-12-07 01:15:13 +00:00
" Other available tags: \n "
" <!-- __CINERA_PROJECT__ --> \n "
2017-12-12 23:24:10 +00:00
" <!-- __CINERA_URL__ --> \n "
2018-01-03 22:16:20 +00:00
" Only really usable if BaseURL is set \ e[1;30m(-B) \ e[0m \n "
2018-02-23 23:36:42 +00:00
" <!-- __CINERA_CUSTOM0__ --> \n "
" <!-- __CINERA_CUSTOM1__ --> \n "
" <!-- __CINERA_CUSTOM2__ --> \n "
" ⋮ \n "
" <!-- __CINERA_CUSTOM15__ --> \n "
" Freeform buffers for small snippets of localised information, e.g. a \n "
" single <a> element or perhaps a <!-- comment --> \n "
" They correspond to the custom0 to custom15 attributes in the [video] \n "
" node in your .hmml files \n "
" 0 to 11 may hold up to 255 characters \n "
" 12 to 15 may hold up to 1023 characters \n "
2017-08-29 20:35:28 +00:00
" \n "
" HMML Specification: \n "
" https://git.handmade.network/Annotation-Pushers/Annotation-System/wikis/hmmlspec \n " ,
2018-01-03 22:16:20 +00:00
DefaultConfig - > ProjectDir ,
DefaultConfig - > TemplatesDir ,
DefaultConfig - > BaseDir ,
DefaultConfig - > BaseURL ,
DefaultConfig - > IndexLocation ,
DefaultConfig - > PlayerLocation ,
DefaultConfig - > OutLocation ,
DefaultConfig - > LogLevel ,
DefaultConfig - > UpdateInterval ) ;
2017-08-29 20:35:28 +00:00
}
2017-09-07 21:41:08 +00:00
void
DepartComment ( buffer * Template )
2017-05-13 15:38:10 +00:00
{
2017-09-07 21:41:08 +00:00
while ( Template - > Ptr - Template - > Location < Template - > Size )
{
if ( ! StringsDifferT ( " --> " , Template - > Ptr , 0 ) )
{
Template - > Ptr + = StringLength ( " --> " ) ;
break ;
}
+ + Template - > Ptr ;
}
}
2017-05-13 15:38:10 +00:00
2017-08-29 20:35:28 +00:00
int
2018-01-03 22:16:20 +00:00
ValidateTemplate ( template * * Template , char * Location , int TemplateType )
2017-08-29 20:35:28 +00:00
{
2018-01-03 22:16:20 +00:00
# if 1
int Return = ClaimTemplate ( Template , Location , TemplateType ) ;
switch ( Return )
{
case RC_ARENA_FULL :
case RC_ERROR_FILE :
return Return ;
case RC_SUCCESS :
break ;
}
# endif
buffer Errors ;
if ( ClaimBuffer ( & Errors , " Errors " , Kilobytes ( 1 ) ) = = RC_ARENA_FULL ) { DeclaimTemplate ( * Template ) ; return RC_ARENA_FULL ; } ;
2017-09-07 21:41:08 +00:00
2017-10-18 21:07:29 +00:00
bool HaveErrors = FALSE ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
bool FoundIncludes = FALSE ;
bool FoundMenus = FALSE ;
bool FoundPlayer = FALSE ;
bool FoundScript = FALSE ;
2017-09-07 21:41:08 +00:00
bool FoundIndex = FALSE ;
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
char * Previous = ( * Template ) - > Buffer . Location ;
2017-09-07 21:41:08 +00:00
2017-10-18 21:07:29 +00:00
while ( ( * Template ) - > Buffer . Ptr - ( * Template ) - > Buffer . Location < ( * Template ) - > Buffer . Size )
2017-05-13 15:38:10 +00:00
{
2018-02-23 23:36:42 +00:00
NextTagSearch :
2017-10-18 21:07:29 +00:00
if ( * ( * Template ) - > Buffer . Ptr = = ' ! ' & & ( ( * Template ) - > Buffer . Ptr > ( * Template ) - > Buffer . Location & & ! StringsDifferT ( " <!-- " , & ( * Template ) - > Buffer . Ptr [ - 1 ] , 0 ) ) )
2017-05-13 15:38:10 +00:00
{
2017-10-18 21:07:29 +00:00
char * CommentStart = & ( * Template ) - > Buffer . Ptr [ - 1 ] ;
while ( ( * Template ) - > Buffer . Ptr - ( * Template ) - > Buffer . Location < ( * Template ) - > Buffer . Size & & StringsDifferT ( " --> " , ( * Template ) - > Buffer . Ptr , 0 ) )
2017-06-25 18:05:58 +00:00
{
2017-09-07 21:41:08 +00:00
for ( int i = 0 ; i < ArrayCount ( Tags ) ; + + i )
2017-05-13 15:38:10 +00:00
{
2017-10-18 21:07:29 +00:00
if ( ! ( StringsDifferT ( Tags [ i ] . Tag , ( * Template ) - > Buffer . Ptr , 0 ) ) )
2017-05-13 15:38:10 +00:00
{
2017-09-07 21:41:08 +00:00
// TODO(matt): Pack up this data for BuffersToHTML() to use
/*
* Potential ways to compress these cases
*
* bool Found [ TAG_COUNT ]
* - Asaf
*
* int * flags [ ] = { [ TAG_INCLUDES ] = & FoundIncludes , [ TAG_MENUS ] = & FoundMenus } flags [ Tags [ i ] . Code ] = true ;
* - insofaras
*
*/
2018-02-23 23:36:42 +00:00
int ThisTagCode = Tags [ i ] . Code ;
char * ThisTagName = Tags [ i ] . Tag ;
switch ( ThisTagCode )
2017-08-29 20:35:28 +00:00
{
2017-09-07 21:41:08 +00:00
case TAG_INDEX :
FoundIndex = TRUE ;
2018-02-23 23:36:42 +00:00
goto RecordTag ;
2017-09-07 21:41:08 +00:00
case TAG_INCLUDES :
2018-04-01 20:58:53 +00:00
if ( ! ( Config . Mode & MODE_FORCEINTEGRATION ) & & FoundIncludes = = TRUE )
2017-09-07 21:41:08 +00:00
{
2018-02-23 23:36:42 +00:00
CopyStringToBuffer ( & Errors , " Template contains more than one <!-- %s --> tag \n " , ThisTagName ) ;
2017-09-07 21:41:08 +00:00
HaveErrors = TRUE ;
}
FoundIncludes = TRUE ;
2018-02-23 23:36:42 +00:00
goto RecordTag ;
2017-09-07 21:41:08 +00:00
case TAG_MENUS :
2018-04-01 20:58:53 +00:00
if ( ! ( Config . Mode & MODE_FORCEINTEGRATION ) & & FoundMenus = = TRUE )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
CopyStringToBuffer ( & Errors , " Template contains more than one <!-- %s --> tag \n " , Tags [ i ] . Tag ) ;
2017-09-07 21:41:08 +00:00
HaveErrors = TRUE ;
}
FoundMenus = TRUE ;
2018-02-23 23:36:42 +00:00
goto RecordTag ;
2017-09-07 21:41:08 +00:00
case TAG_PLAYER :
2018-04-01 20:58:53 +00:00
if ( ! ( Config . Mode & MODE_FORCEINTEGRATION ) & & FoundPlayer = = TRUE )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
CopyStringToBuffer ( & Errors , " Template contains more than one <!-- %s --> tag \n " , Tags [ i ] . Tag ) ;
2017-09-07 21:41:08 +00:00
HaveErrors = TRUE ;
}
FoundPlayer = TRUE ;
2018-02-23 23:36:42 +00:00
goto RecordTag ;
2017-09-07 21:41:08 +00:00
case TAG_SCRIPT :
2018-04-01 20:58:53 +00:00
if ( ! ( Config . Mode & MODE_FORCEINTEGRATION ) & & ( FoundMenus = = FALSE | | FoundPlayer = = FALSE ) )
2017-09-07 21:41:08 +00:00
{
2018-04-01 20:58:53 +00:00
CopyStringToBuffer ( & Errors , " <!-- %s --> must come after <!-- __CINERA_MENUS__ --> and <!-- __CINERA_PLAYER__ --> \n " , Tags [ i ] . Tag ) ;
2017-09-07 21:41:08 +00:00
HaveErrors = TRUE ;
}
2018-04-01 20:58:53 +00:00
if ( ! ( Config . Mode & MODE_FORCEINTEGRATION ) & & FoundScript = = TRUE )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
CopyStringToBuffer ( & Errors , " Template contains more than one <!-- %s --> tag \n " , Tags [ i ] . Tag ) ;
2017-09-07 21:41:08 +00:00
HaveErrors = TRUE ;
}
FoundScript = TRUE ;
2018-02-23 23:36:42 +00:00
goto RecordTag ;
default : // NOTE(matt): All freely usable tags should hit this case
RecordTag :
2017-10-18 21:07:29 +00:00
( * Template ) - > Metadata . Tag [ ( * Template ) - > Metadata . TagCount ] . Offset = CommentStart - Previous ;
2018-02-23 23:36:42 +00:00
( * Template ) - > Metadata . Tag [ ( * Template ) - > Metadata . TagCount ] . TagCode = ThisTagCode ;
2017-10-18 21:07:29 +00:00
( * Template ) - > Metadata . TagCount + + ;
DepartComment ( & ( * Template ) - > Buffer ) ;
Previous = ( * Template ) - > Buffer . Ptr ;
2018-02-23 23:36:42 +00:00
goto NextTagSearch ;
2017-09-07 21:41:08 +00:00
} ;
2017-05-13 15:38:10 +00:00
}
}
2017-10-18 21:07:29 +00:00
+ + ( * Template ) - > Buffer . Ptr ;
2017-08-29 20:35:28 +00:00
}
}
else
{
2017-10-18 21:07:29 +00:00
+ + ( * Template ) - > Buffer . Ptr ;
2017-08-29 20:35:28 +00:00
}
}
2017-09-07 21:41:08 +00:00
if ( FoundIndex )
{
2017-10-18 21:07:29 +00:00
( * Template ) - > Metadata . Validity | = PAGE_INDEX ;
2017-09-07 21:41:08 +00:00
}
2017-08-29 20:35:28 +00:00
if ( ! HaveErrors & & FoundIncludes & & FoundMenus & & FoundPlayer & & FoundScript )
{
2017-10-18 21:07:29 +00:00
( * Template ) - > Metadata . Validity | = PAGE_PLAYER ;
2017-08-29 20:35:28 +00:00
}
2017-09-07 21:41:08 +00:00
2018-04-01 20:58:53 +00:00
if ( ! ( Config . Mode & MODE_FORCEINTEGRATION ) )
2017-08-29 20:35:28 +00:00
{
2018-01-03 22:16:20 +00:00
if ( TemplateType = = TEMPLATE_INDEX & & ! ( ( * Template ) - > Metadata . Validity & PAGE_INDEX ) )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
CopyStringToBuffer ( & Errors , " Index template %s must include one <!-- __CINERA_INDEX__ --> tag \n " , ( * Template ) - > Metadata . Filename ) ;
fprintf ( stderr , " %s " , Errors . Location ) ;
DeclaimBuffer ( & Errors ) ;
DeclaimTemplate ( * Template ) ;
2017-09-07 21:41:08 +00:00
return RC_INVALID_TEMPLATE ;
}
2018-01-03 22:16:20 +00:00
else if ( ( TemplateType = = TEMPLATE_PLAYER | | TemplateType = = TEMPLATE_BESPOKE ) & & ! ( ( * Template ) - > Metadata . Validity & PAGE_PLAYER ) )
2017-08-29 20:35:28 +00:00
{
2018-01-03 22:16:20 +00:00
if ( ! FoundIncludes ) { CopyStringToBuffer ( & Errors , " Player template %s must include one <!-- __CINERA_INCLUDES__ --> tag \n " , ( * Template ) - > Metadata . Filename ) ; } ;
if ( ! FoundMenus ) { CopyStringToBuffer ( & Errors , " Player template %s must include one <!-- __CINERA_MENUS__ --> tag \n " , ( * Template ) - > Metadata . Filename ) ; } ;
if ( ! FoundPlayer ) { CopyStringToBuffer ( & Errors , " Player template %s must include one <!-- __CINERA_PLAYER__ --> tag \n " , ( * Template ) - > Metadata . Filename ) ; } ;
if ( ! FoundScript ) { CopyStringToBuffer ( & Errors , " Player template %s must include one <!-- __CINERA_SCRIPT__ --> tag \n " , ( * Template ) - > Metadata . Filename ) ; } ;
fprintf ( stderr , " %s " , Errors . Location ) ;
DeclaimBuffer ( & Errors ) ;
DeclaimTemplate ( * Template ) ;
2017-09-07 21:41:08 +00:00
return RC_INVALID_TEMPLATE ;
2017-08-29 20:35:28 +00:00
}
}
2018-01-03 22:16:20 +00:00
DeclaimBuffer ( & Errors ) ;
2017-09-07 21:41:08 +00:00
return RC_SUCCESS ;
}
2017-12-12 23:24:10 +00:00
void
ConstructIndexURL ( buffer * IndexURL )
{
RewindBuffer ( IndexURL ) ;
if ( StringsDiffer ( Config . BaseURL , " " ) )
{
CopyStringToBuffer ( IndexURL , " %s/ " , Config . BaseURL ) ;
if ( StringsDiffer ( Config . IndexLocation , " " ) )
{
CopyStringToBuffer ( IndexURL , " %s/ " , Config . IndexLocation ) ;
}
}
}
void
ConstructPlayerURL ( buffer * PlayerURL , char * BaseFilename )
{
RewindBuffer ( PlayerURL ) ;
if ( StringsDiffer ( Config . BaseURL , " " ) )
{
CopyStringToBuffer ( PlayerURL , " %s/ " , Config . BaseURL ) ;
if ( StringsDiffer ( Config . PlayerLocation , " " ) )
{
CopyStringToBuffer ( PlayerURL , " %s/ " , Config . PlayerLocation ) ;
}
}
if ( StringsDiffer ( BaseFilename , " " ) )
{
if ( StringsDiffer ( Config . PlayerURLPrefix , " " ) )
{
char * Ptr = BaseFilename + StringLength ( Config . ProjectID ) ;
2018-01-03 22:16:20 +00:00
CopyStringToBuffer ( PlayerURL , " %s%s/ " , Config . PlayerURLPrefix , Ptr ) ;
2017-12-12 23:24:10 +00:00
}
else
{
2018-01-03 22:16:20 +00:00
CopyStringToBuffer ( PlayerURL , " %s/ " , BaseFilename ) ;
2017-12-12 23:24:10 +00:00
}
}
}
2018-01-03 22:16:20 +00:00
bool
MediumExists ( char * Medium )
{
for ( int i = 0 ; i < ArrayCount ( CategoryMedium ) ; + + i )
{
if ( ! StringsDiffer ( Medium , CategoryMedium [ i ] . Medium ) )
{
return TRUE ;
}
}
fprintf ( stderr , " Specified default medium \" %s \" not available. Valid media are: \n " , Medium ) ;
for ( int i = 0 ; i < ArrayCount ( CategoryMedium ) ; + + i )
{
fprintf ( stderr , " %s \n " , CategoryMedium [ i ] . Medium ) ;
}
fprintf ( stderr , " To have \" %s \" added to the list, contact matt@handmadedev.org \n " , Medium ) ;
return FALSE ;
}
2018-02-21 21:50:23 +00:00
int
ReadFileIntoBuffer ( file_buffer * File , int BufferPadding )
{
if ( ! ( File - > Handle = fopen ( File - > Path , " r " ) ) )
{
return RC_ERROR_FILE ;
}
fseek ( File - > Handle , 0 , SEEK_END ) ;
File - > FileSize = ftell ( File - > Handle ) ;
File - > Buffer . Size = File - > FileSize + 1 + BufferPadding ; // NOTE(matt): +1 to accommodate a NULL terminator
fseek ( File - > Handle , 0 , SEEK_SET ) ;
// TODO(matt): Consider using the MemoryArena? Maybe have separate ReadFileIntoMemory() and ReadFileIntoArena()
if ( ! ( File - > Buffer . Location = malloc ( File - > Buffer . Size ) ) )
{
fclose ( File - > Handle ) ;
return RC_ERROR_MEMORY ;
}
File - > Buffer . Ptr = File - > Buffer . Location ;
fread ( File - > Buffer . Location , File - > FileSize , 1 , File - > Handle ) ;
File - > Buffer . Location [ File - > FileSize ] = ' \0 ' ;
fclose ( File - > Handle ) ;
2018-04-01 20:58:53 +00:00
File - > Buffer . ID = File - > Path ;
2018-02-21 21:50:23 +00:00
return RC_SUCCESS ;
}
// NOTE(matt): Currently unused
int
WriteBufferToFile ( file_buffer * File , buffer * Buffer , int BytesToWrite , bool KeepFileHandleOpen )
{
if ( ! ( File - > Handle = fopen ( File - > Path , " w " ) ) )
{
return RC_ERROR_FILE ;
}
fwrite ( Buffer - > Location , BytesToWrite , 1 , File - > Handle ) ;
if ( ! KeepFileHandleOpen )
{
fclose ( File - > Handle ) ;
}
return RC_SUCCESS ;
}
typedef struct
{
char * BaseFilename ;
char * Title ;
} neighbour ;
typedef struct
{
neighbour Prev ;
neighbour Next ;
} neighbours ;
int
ExamineIndex ( index * Index )
{
int IndexMetadataFileReadCode = ReadFileIntoBuffer ( & Index - > Metadata , 0 ) ;
switch ( IndexMetadataFileReadCode )
{
case RC_ERROR_MEMORY :
return RC_ERROR_MEMORY ;
case RC_ERROR_FILE :
fprintf ( stderr , " Unable to open index file %s: %s \n " , Index - > Metadata . Path , strerror ( errno ) ) ;
return RC_ERROR_FILE ;
case RC_SUCCESS :
break ;
}
Index - > Header = * ( index_header * ) Index - > Metadata . Buffer . Ptr ;
// TODO(matt): Check that we're the current version, and maybe print out stuff accordingly? Or do we just straight up
// enforce that examining an index requires upgrading to the current version?
printf ( " Current: \n "
" \t DBVersion: %d \n "
" \t AppVersion: %d.%d.%d \n "
" \t HMMLVersion: %d.%d.%d \n "
" \n "
" Initial: \n "
" \t DBVersion: %d \n "
" \t AppVersion: %d.%d.%d \n "
" \t HMMLVersion: %d.%d.%d \n "
" \n "
" Project ID: %s \n "
" Project Full Name: %s \n "
" \n "
" Base URL: %s \n "
" Relative Index Page Location: %s \n "
" Relative Player Page Location: %s \n "
" Player Page URL Prefix: %s \n "
" \n "
" Entries: %d \n " ,
Index - > Header . CurrentDBVersion ,
Index - > Header . CurrentAppVersion . Major , Index - > Header . CurrentAppVersion . Minor , Index - > Header . CurrentAppVersion . Patch ,
Index - > Header . CurrentHMMLVersion . Major , Index - > Header . CurrentHMMLVersion . Minor , Index - > Header . CurrentHMMLVersion . Patch ,
Index - > Header . InitialDBVersion ,
Index - > Header . InitialAppVersion . Major , Index - > Header . InitialAppVersion . Minor , Index - > Header . InitialAppVersion . Patch ,
Index - > Header . InitialHMMLVersion . Major , Index - > Header . InitialHMMLVersion . Minor , Index - > Header . InitialHMMLVersion . Patch ,
Index - > Header . ProjectID ,
Index - > Header . ProjectName ,
Index - > Header . BaseURL ,
StringsDiffer ( Index - > Header . IndexLocation , " " ) ? Index - > Header . IndexLocation : " (same as Base URL) " ,
StringsDiffer ( Index - > Header . PlayerLocation , " " ) ? Index - > Header . PlayerLocation : " (directly descended from Base URL) " ,
StringsDiffer ( Index - > Header . PlayerURLPrefix , " " ) ? Index - > Header . PlayerURLPrefix : " (no special prefix, the player page URLs equal their entry's base filename) " ,
Index - > Header . EntryCount ) ;
Index - > Metadata . Buffer . Ptr + = sizeof ( index_header ) ;
for ( int EntryIndex = 0 ; EntryIndex < Index - > Header . EntryCount ; + + EntryIndex )
{
Index - > Entry = * ( index_metadata * ) Index - > Metadata . Buffer . Ptr ;
printf ( " %3d \t %s%sSize: %4d \t %d \t %d \t %d \t %d \n "
" \t %s \n " ,
EntryIndex + 1 , Index - > Entry . BaseFilename ,
StringLength ( Index - > Entry . BaseFilename ) > 8 ? " \t " : " \t \t " , // NOTE(matt): Janktasm
Index - > Entry . Size ,
Index - > Entry . LinkOffsets . PrevStart ,
Index - > Entry . LinkOffsets . PrevEnd ,
Index - > Entry . LinkOffsets . NextStart ,
Index - > Entry . LinkOffsets . NextEnd ,
Index - > Entry . Title ) ;
Index - > Metadata . Buffer . Ptr + = sizeof ( index_metadata ) ;
}
FreeBuffer ( & Index - > Metadata . Buffer ) ;
return RC_SUCCESS ;
}
2018-04-01 20:58:53 +00:00
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 ,
int Direction , /* seek_directions */
int Position /* seek_positions */ )
{
// 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 ;
}
2018-02-05 23:57:17 +00:00
# define HMMLCleanup() \
DeclaimBuffer ( & FilterState ) ; \
DeclaimBuffer ( & CreditsMenu ) ; \
DeclaimBuffer ( & FilterMedia ) ; \
DeclaimBuffer ( & FilterTopics ) ; \
DeclaimBuffer ( & FilterMenu ) ; \
DeclaimBuffer ( & ReferenceMenu ) ; \
DeclaimBuffer ( & QuoteMenu ) ; \
hmml_free ( & HMML ) ;
2018-04-01 20:58:53 +00:00
void
ClearTerminalRow ( int Length )
{
fprintf ( stderr , " \r " ) ;
for ( int i = 0 ; i < Length ; + + i )
{
fprintf ( stderr , " " ) ;
}
fprintf ( stderr , " \r " ) ;
}
bool
VideoIsPrivate ( char * VideoID )
{
// NOTE(matt): Currently only supports YouTube
char Message [ 128 ] ;
CopyString ( Message , " \ e[0;35mChecking \ e[0m privacy status of: https://youtube.com/watch?v=%s " , VideoID ) ;
fprintf ( stderr , Message ) ;
int MessageLength = StringLength ( Message ) ;
buffer VideoAPIResponse ;
ClaimBuffer ( & VideoAPIResponse , " VideoAPIResponse " , Kilobytes ( 1 ) ) ;
CURL * curl = curl_easy_init ( ) ;
if ( curl ) {
LastPrivacyCheck = time ( 0 ) ;
# define APIKey "AIzaSyAdV2U8ivPk8PHMaPMId0gynksw_gdzr9k"
char URL [ 1024 ] = { 0 } ;
CopyString ( URL , " https://www.googleapis.com/youtube/v3/videos?key=%s&part=status&id=%s " , APIKey , VideoID ) ;
CURLcode CurlReturnCode ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , & VideoAPIResponse . Ptr ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , CurlIntoBuffer ) ;
curl_easy_setopt ( curl , CURLOPT_URL , URL ) ;
if ( ( CurlReturnCode = curl_easy_perform ( curl ) ) )
{
fprintf ( stderr , " %s \n " , curl_easy_strerror ( CurlReturnCode ) ) ;
}
curl_easy_cleanup ( curl ) ;
VideoAPIResponse . Ptr = VideoAPIResponse . Location ;
SeekBufferForString ( & VideoAPIResponse , " { " , C_SEEK_FORWARDS , C_SEEK_AFTER ) ;
SeekBufferForString ( & VideoAPIResponse , " \" totalResults \" : " , C_SEEK_FORWARDS , C_SEEK_AFTER ) ;
if ( * VideoAPIResponse . Ptr = = ' 0 ' )
{
DeclaimBuffer ( & VideoAPIResponse ) ;
// printf("Private video: https://youtube.com/watch?v=%s\n", VideoID);
ClearTerminalRow ( MessageLength ) ;
return TRUE ;
}
SeekBufferForString ( & VideoAPIResponse , " { " , C_SEEK_FORWARDS , C_SEEK_AFTER ) ;
SeekBufferForString ( & VideoAPIResponse , " \" privacyStatus \" : \" " , C_SEEK_FORWARDS , C_SEEK_AFTER ) ;
char Status [ 16 ] ;
CopyStringNoFormatT ( Status , VideoAPIResponse . Ptr , ' \" ' ) ;
if ( ! StringsDiffer ( Status , " public " ) )
{
DeclaimBuffer ( & VideoAPIResponse ) ;
ClearTerminalRow ( MessageLength ) ;
return FALSE ;
}
}
DeclaimBuffer ( & VideoAPIResponse ) ;
// printf("Unlisted video: https://youtube.com/watch?v=%s\n", VideoID);
ClearTerminalRow ( MessageLength ) ;
return TRUE ;
}
typedef struct
{
index_metadata Prev , This , Next ;
bool PrevIsFirst , NextIsFinal ;
short int PrevIndex , ThisIndex , NextIndex ;
} neighbourhood ;
2018-04-04 22:39:38 +00:00
int
LinearSearchForSpeaker ( speakers Speakers , char * Username )
{
for ( int i = 0 ; i < Speakers . Count ; + + i )
{
if ( ! StringsDifferCaseInsensitive ( Speakers . Speaker [ i ] . Credential - > Username , Username ) )
{
return i ;
}
}
return - 1 ;
}
bool
IsCategorisedAFK ( HMML_Annotation Anno )
{
for ( int i = 0 ; i < Anno . marker_count ; + + i )
{
if ( ! StringsDiffer ( Anno . markers [ i ] . marker , " afk " ) )
{
return TRUE ;
}
}
return FALSE ;
}
2017-08-29 20:35:28 +00:00
int
2018-04-01 20:58:53 +00:00
HMMLToBuffers ( buffers * CollationBuffers , template * * BespokeTemplate , char * Filename , neighbourhood * N )
2017-08-29 20:35:28 +00:00
{
2017-09-07 21:41:08 +00:00
RewindBuffer ( & CollationBuffers - > IncludesPlayer ) ;
RewindBuffer ( & CollationBuffers - > Menus ) ;
RewindBuffer ( & CollationBuffers - > Player ) ;
2017-11-11 00:34:47 +00:00
RewindBuffer ( & CollationBuffers - > ScriptPlayer ) ;
2017-09-07 21:41:08 +00:00
RewindBuffer ( & CollationBuffers - > IncludesIndex ) ;
2018-02-23 23:36:42 +00:00
* CollationBuffers - > Custom0 = ' \0 ' ;
* CollationBuffers - > Custom1 = ' \0 ' ;
* CollationBuffers - > Custom2 = ' \0 ' ;
* CollationBuffers - > Custom3 = ' \0 ' ;
* CollationBuffers - > Custom4 = ' \0 ' ;
* CollationBuffers - > Custom5 = ' \0 ' ;
* CollationBuffers - > Custom6 = ' \0 ' ;
* CollationBuffers - > Custom7 = ' \0 ' ;
* CollationBuffers - > Custom8 = ' \0 ' ;
* CollationBuffers - > Custom9 = ' \0 ' ;
* CollationBuffers - > Custom10 = ' \0 ' ;
* CollationBuffers - > Custom11 = ' \0 ' ;
* CollationBuffers - > Custom12 = ' \0 ' ;
* CollationBuffers - > Custom13 = ' \0 ' ;
* CollationBuffers - > Custom14 = ' \0 ' ;
* CollationBuffers - > Custom15 = ' \0 ' ;
2018-01-04 20:55:51 +00:00
* CollationBuffers - > Title = ' \0 ' ;
* CollationBuffers - > ProjectName = ' \0 ' ;
2017-08-29 20:35:28 +00:00
2017-11-11 00:34:47 +00:00
char Filepath [ 256 ] ;
2017-08-29 20:35:28 +00:00
if ( Config . Edition = = EDITION_PROJECT )
{
CopyString ( Filepath , " %s/%s " , Config . ProjectDir , Filename ) ;
}
else
{
CopyString ( Filepath , " %s " , Filename ) ;
}
FILE * InFile ;
if ( ! ( InFile = fopen ( Filepath , " r " ) ) )
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Unable to open (annotations file) %s: %s " , Filename , strerror ( errno ) ) ;
2017-09-07 21:41:08 +00:00
fprintf ( stderr , " Unable to open (annotations file) %s: %s \n " , Filename , strerror ( errno ) ) ;
return RC_ERROR_FILE ;
2017-08-29 20:35:28 +00:00
}
HMML_Output HMML = hmml_parse_file ( InFile ) ;
fclose ( InFile ) ;
if ( HMML . well_formed )
{
2018-02-21 21:50:23 +00:00
bool HaveErrors = FALSE ;
2018-01-04 20:55:51 +00:00
char * BaseFilename = GetBaseFilename ( Filename , " .hmml " ) ;
2018-02-21 21:50:23 +00:00
if ( StringLength ( BaseFilename ) > MAX_BASE_FILENAME_LENGTH )
{
fprintf ( stderr , " \ e[1;31mBase filename \" %s \" is too long (%d/%d characters) \ e[0m \n " , BaseFilename , StringLength ( BaseFilename ) , MAX_BASE_FILENAME_LENGTH ) ;
HaveErrors = TRUE ;
}
2018-01-04 20:55:51 +00:00
if ( ! HMML . metadata . title )
{
fprintf ( stderr , " Please set the title attribute in the [video] node of your .hmml file \n " ) ;
2018-02-21 21:50:23 +00:00
HaveErrors = TRUE ;
}
else if ( StringLength ( HMML . metadata . title ) > MAX_TITLE_LENGTH )
{
fprintf ( stderr , " \ e[1;31mVideo title \" %s \" is too long (%d/%d characters) \ e[0m \n " , HMML . metadata . title , StringLength ( HMML . metadata . title ) , MAX_TITLE_LENGTH ) ;
HaveErrors = TRUE ;
}
else
{
CopyString ( CollationBuffers - > Title , HMML . metadata . title ) ;
2018-01-04 20:55:51 +00:00
}
if ( ! HMML . metadata . member )
{
fprintf ( stderr , " Please set the member attribute in the [video] node of your .hmml file \n " ) ;
2018-02-21 21:50:23 +00:00
HaveErrors = TRUE ;
2018-01-04 20:55:51 +00:00
}
if ( ! HMML . metadata . project & & ! StringsDiffer ( Config . Theme , " " ) )
{
fprintf ( stderr , " Unable to determine which theme to apply to the HTML \n "
" Please set at least one of: \n "
" \t 1. project attribute in the [video] node of your .hmml file \n "
" \t 2. ProjectID on the command line with -p \n "
" \t 3. Style on the command line with -s \n "
) ;
2018-02-21 21:50:23 +00:00
HaveErrors = TRUE ;
2018-01-04 20:55:51 +00:00
}
if ( ! HMML . metadata . id )
{
fprintf ( stderr , " Please set the id attribute in the [video] node of your .hmml file \n " ) ;
2018-02-21 21:50:23 +00:00
HaveErrors = TRUE ;
2018-01-04 20:55:51 +00:00
}
CopyString ( CollationBuffers - > VideoID , HMML . metadata . id ) ;
buffer URLPlayer ;
2018-02-21 21:50:23 +00:00
ClaimBuffer ( & URLPlayer , " URLPlayer " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH ) ;
2018-01-04 20:55:51 +00:00
ConstructPlayerURL ( & URLPlayer , BaseFilename ) ;
CopyString ( CollationBuffers - > URLPlayer , URLPlayer . Location ) ;
DeclaimBuffer ( & URLPlayer ) ;
2017-12-12 23:24:10 +00:00
for ( int ProjectIndex = 0 ; ProjectIndex < ArrayCount ( ProjectInfo ) ; + + ProjectIndex )
2017-12-07 01:15:13 +00:00
{
2018-01-04 20:55:51 +00:00
if ( ! StringsDiffer ( ProjectInfo [ ProjectIndex ] . ProjectID ,
Config . Edition = = EDITION_SINGLE & & HMML . metadata . project ?
HMML . metadata . project :
Config . ProjectID ) )
2017-12-07 01:15:13 +00:00
{
CopyString ( CollationBuffers - > ProjectName , ProjectInfo [ ProjectIndex ] . FullName ) ;
break ;
}
}
2018-01-03 22:16:20 +00:00
char * DefaultMedium = Config . DefaultMedium ;
if ( HMML . metadata . medium )
{
if ( MediumExists ( HMML . metadata . medium ) )
{
DefaultMedium = HMML . metadata . medium ;
}
2018-02-21 21:50:23 +00:00
else { HaveErrors = TRUE ; }
2018-01-03 22:16:20 +00:00
}
2018-02-23 23:36:42 +00:00
// TODO(matt): Consider simply making these as buffers and claiming the necessary amount for them
// The nice thing about doing it this way, though, is that it encourages bespoke template use, which should
// usually be the more convenient way for people to write greater amounts of localised information
for ( int CustomIndex = 0 ; CustomIndex < HMML_CUSTOM_ATTR_COUNT ; + + CustomIndex )
{
if ( HMML . metadata . custom [ CustomIndex ] )
{
int LengthOfString = StringLength ( HMML . metadata . custom [ CustomIndex ] ) ;
if ( LengthOfString > ( CustomIndex < 12 ? MAX_CUSTOM_SNIPPET_SHORT_LENGTH : MAX_CUSTOM_SNIPPET_LONG_LENGTH ) )
{
fprintf ( stderr , " \ e[1;31mCustom string %d \" \ e[0m%s \ e[1;31m \" is too long (%d/%d characters) \ e[0m \n " , CustomIndex , HMML . metadata . custom [ CustomIndex ] , LengthOfString , CustomIndex < 12 ? MAX_CUSTOM_SNIPPET_SHORT_LENGTH : MAX_CUSTOM_SNIPPET_LONG_LENGTH ) ;
if ( LengthOfString < MAX_CUSTOM_SNIPPET_LONG_LENGTH )
{
fprintf ( stderr , " Consider using custom12 to custom15, which can hold %d characters \n " , MAX_CUSTOM_SNIPPET_LONG_LENGTH ) ;
}
else
{
fprintf ( stderr , " Consider using a bespoke template for longer amounts of localised information \n " ) ;
}
HaveErrors = TRUE ;
}
else
{
switch ( CustomIndex )
{
case 0 : CopyStringNoFormat ( CollationBuffers - > Custom0 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 1 : CopyStringNoFormat ( CollationBuffers - > Custom1 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 2 : CopyStringNoFormat ( CollationBuffers - > Custom2 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 3 : CopyStringNoFormat ( CollationBuffers - > Custom3 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 4 : CopyStringNoFormat ( CollationBuffers - > Custom4 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 5 : CopyStringNoFormat ( CollationBuffers - > Custom5 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 6 : CopyStringNoFormat ( CollationBuffers - > Custom6 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 7 : CopyStringNoFormat ( CollationBuffers - > Custom7 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 8 : CopyStringNoFormat ( CollationBuffers - > Custom8 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 9 : CopyStringNoFormat ( CollationBuffers - > Custom9 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 10 : CopyStringNoFormat ( CollationBuffers - > Custom10 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 11 : CopyStringNoFormat ( CollationBuffers - > Custom11 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 12 : CopyStringNoFormat ( CollationBuffers - > Custom12 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 13 : CopyStringNoFormat ( CollationBuffers - > Custom13 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 14 : CopyStringNoFormat ( CollationBuffers - > Custom14 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
case 15 : CopyStringNoFormat ( CollationBuffers - > Custom15 , HMML . metadata . custom [ CustomIndex ] ) ; break ;
}
}
}
}
2018-02-21 21:50:23 +00:00
if ( ! HaveErrors )
2018-01-03 22:16:20 +00:00
{
2018-02-21 21:50:23 +00:00
if ( HMML . metadata . template )
2018-01-03 22:16:20 +00:00
{
2018-02-21 21:50:23 +00:00
switch ( ValidateTemplate ( BespokeTemplate , HMML . metadata . template , TEMPLATE_BESPOKE ) )
{
case RC_ARENA_FULL :
case RC_INVALID_TEMPLATE : // Invalid template
case RC_ERROR_FILE : // Could not load template
case RC_ERROR_MEMORY : // Could not allocate memory for template
HaveErrors = TRUE ;
case RC_SUCCESS :
break ;
}
2018-01-03 22:16:20 +00:00
}
}
2018-02-21 21:50:23 +00:00
if ( HaveErrors )
{
fprintf ( stderr , " \ e[1;31mSkipping \ e[0m %s " , BaseFilename ) ;
if ( HMML . metadata . title ) { fprintf ( stderr , " - %s " , HMML . metadata . title ) ; }
fprintf ( stderr , " \n " ) ;
hmml_free ( & HMML ) ;
return RC_ERROR_HMML ;
}
2018-04-01 20:58:53 +00:00
if ( N )
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
N - > This . LinkOffsets . PrevStart = 0 ;
N - > This . LinkOffsets . PrevEnd = 0 ;
N - > This . LinkOffsets . NextStart = 0 ;
N - > This . LinkOffsets . NextEnd = 0 ;
2018-02-21 21:50:23 +00:00
}
2017-08-29 20:35:28 +00:00
# if DEBUG
printf (
" ================================================================================ \n "
" %s \n "
" ================================================================================ \n " ,
Filename ) ;
# endif
// NOTE(matt): Tree structure of "global" buffer dependencies
// Master
// IncludesPlayer
// Menus
// QuoteMenu
// ReferenceMenu
// FilterMenu
// FilterTopics
// FilterMedia
// CreditsMenu
// Player
// Script
// FilterState
buffer QuoteMenu ;
buffer ReferenceMenu ;
buffer FilterMenu ;
buffer FilterTopics ;
buffer FilterMedia ;
buffer CreditsMenu ;
buffer Annotation ;
buffer AnnotationHeader ;
buffer AnnotationClass ;
buffer AnnotationData ;
buffer Text ;
2018-01-15 00:03:11 +00:00
buffer CategoryIcons ;
2017-08-29 20:35:28 +00:00
buffer FilterState ;
2017-12-07 01:15:13 +00:00
if ( ClaimBuffer ( & QuoteMenu , " QuoteMenu " , Kilobytes ( 32 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & ReferenceMenu , " ReferenceMenu " , Kilobytes ( 32 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
2017-10-18 21:07:29 +00:00
if ( ClaimBuffer ( & FilterMenu , " FilterMenu " , Kilobytes ( 16 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & FilterTopics , " FilterTopics " , Kilobytes ( 8 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & FilterMedia , " FilterMedia " , Kilobytes ( 8 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & CreditsMenu , " CreditsMenu " , Kilobytes ( 8 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & FilterState , " FilterState " , Kilobytes ( 4 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
2017-08-29 20:35:28 +00:00
ref_info ReferencesArray [ 200 ] = { 0 } ;
categories Topics = { 0 } ;
categories Media = { 0 } ;
bool HasQuoteMenu = FALSE ;
bool HasReferenceMenu = FALSE ;
bool HasFilterMenu = FALSE ;
bool HasCreditsMenu = FALSE ;
int QuoteIdentifier = 0x3b1 ;
int RefIdentifier = 1 ;
int UniqueRefs = 0 ;
CopyStringToBuffer ( & CollationBuffers - > Menus ,
2018-01-08 22:59:36 +00:00
" <div class= \" cineraMenus %s \" > \n "
2017-12-07 01:15:13 +00:00
" <span class= \" episode_name \" > " , StringsDiffer ( Config . Theme , " " ) ? Config . Theme : HMML . metadata . project ) ;
2017-08-29 20:35:28 +00:00
CopyStringToBufferHTMLSafe ( & CollationBuffers - > Menus , HMML . metadata . title ) ;
CopyStringToBuffer ( & CollationBuffers - > Menus , " </span> \n "
" <span id= \" focus-warn \" >⚠ Click here to regain focus ⚠</span> \n " ) ;
CopyStringToBuffer ( & CollationBuffers - > Player ,
2018-01-08 22:59:36 +00:00
" <div class= \" cineraPlayerContainer \" > \n "
2017-08-29 20:35:28 +00:00
" <div class= \" video_container \" data-videoId= \" %s \" ></div> \n "
2017-12-07 01:15:13 +00:00
" <div class= \" markers_container %s \" > \n " , HMML . metadata . id , StringsDiffer ( Config . Theme , " " ) ? Config . Theme : HMML . metadata . project ) ;
2017-08-29 20:35:28 +00:00
2018-04-01 20:58:53 +00:00
if ( N )
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
N - > This . LinkOffsets . PrevStart = ( CollationBuffers - > Player . Ptr - CollationBuffers - > Player . Location ) ;
if ( N - > Prev . Size | | N - > Next . Size )
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
if ( N - > Prev . Size )
2018-02-21 21:50:23 +00:00
{
// TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here
buffer PreviousPlayerURL ;
ClaimBuffer ( & PreviousPlayerURL , " PreviousPlayerURL " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH ) ;
2018-04-01 20:58:53 +00:00
ConstructPlayerURL ( & PreviousPlayerURL , N - > Prev . BaseFilename ) ;
2018-02-21 21:50:23 +00:00
CopyStringToBuffer ( & CollationBuffers - > Player ,
" <a class= \" episodeMarker prev \" href= \" %s \" ><div>⏫</div><div>Previous: '%s'</div><div>⏫</div></a> \n " ,
PreviousPlayerURL . Location ,
2018-04-01 20:58:53 +00:00
N - > Prev . Title ) ;
2018-02-21 21:50:23 +00:00
DeclaimBuffer ( & PreviousPlayerURL ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Player ,
" <div class= \" episodeMarker first \" ><div>•</div><div>Welcome to <cite>%s</cite></div><div>•</div></div> \n " , CollationBuffers - > ProjectName ) ;
}
}
2018-04-01 20:58:53 +00:00
N - > This . LinkOffsets . PrevEnd = ( CollationBuffers - > Player . Ptr - CollationBuffers - > Player . Location - N - > This . LinkOffsets . PrevStart ) ;
2018-02-21 21:50:23 +00:00
}
CopyStringToBuffer ( & CollationBuffers - > Player ,
" <div class= \" markers \" > \n " ) ;
2018-04-04 22:39:38 +00:00
speakers Speakers = { 0 } ;
switch ( BuildCredits ( & CreditsMenu , & HasCreditsMenu , & HMML . metadata , & Speakers ) )
2017-08-29 20:35:28 +00:00
{
2018-02-05 23:57:17 +00:00
case CreditsError_NoHost :
case CreditsError_NoAnnotator :
case CreditsError_NoCredentials :
fprintf ( stderr , " \ e[1;31mSkipping \ e[0m %s - %s \n " , BaseFilename , HMML . metadata . title ) ;
HMMLCleanup ( ) ;
return RC_ERROR_HMML ;
2017-08-29 20:35:28 +00:00
}
2018-04-01 20:58:53 +00:00
bool PrivateVideo = FALSE ;
if ( Config . Edition ! = EDITION_SINGLE & & N - > This . Size = = 0 & & ! ( Config . Mode & MODE_NOPRIVACY ) )
{
if ( VideoIsPrivate ( HMML . metadata . id ) )
{
// TODO(matt): Actually generate these guys, just putting them in a secret location
N - > This . LinkOffsets . PrevStart = 0 ;
N - > This . LinkOffsets . PrevEnd = 0 ;
PrivateVideo = TRUE ;
HMMLCleanup ( ) ;
return RC_PRIVATE_VIDEO ;
}
}
if ( Config . Edition ! = EDITION_SINGLE & & ! PrivateVideo )
2018-01-04 20:55:51 +00:00
{
RewindBuffer ( & CollationBuffers - > Search ) ;
CopyStringToBuffer ( & CollationBuffers - > Search , " name: \" %s \" \n "
" title: \" " , BaseFilename ) ;
CopyStringToBufferJSONSafe ( & CollationBuffers - > Search , HMML . metadata . title ) ;
CopyStringToBuffer ( & CollationBuffers - > Search , " \" \n "
" markers: \n " ) ;
}
2017-08-29 20:35:28 +00:00
# if DEBUG
printf ( " \n \n --- Entering Annotations Loop --- \n \n \n \n " ) ;
2017-06-25 18:05:58 +00:00
# endif
2017-11-11 00:34:47 +00:00
2018-02-05 23:57:17 +00:00
int PreviousTimecode = 0 ;
2017-08-29 20:35:28 +00:00
for ( int AnnotationIndex = 0 ; AnnotationIndex < HMML . annotation_count ; + + AnnotationIndex )
{
# if DEBUG
printf ( " %d \n " , AnnotationIndex ) ;
# endif
2017-11-11 00:34:47 +00:00
2017-08-29 20:35:28 +00:00
HMML_Annotation * Anno = HMML . annotations + AnnotationIndex ;
2017-11-11 00:34:47 +00:00
2018-02-05 23:57:17 +00:00
if ( TimecodeToSeconds ( Anno - > time ) < PreviousTimecode )
{
fprintf ( stderr , " %s:%d: Timecode %s is chronologically before previous timecode \n "
" \ e[1;31mSkipping \ e[0m %s - %s \n " ,
Filename , Anno - > line , Anno - > time ,
BaseFilename , HMML . metadata . title ) ;
HMMLCleanup ( ) ;
return RC_ERROR_HMML ;
}
PreviousTimecode = TimecodeToSeconds ( Anno - > time ) ;
2018-04-01 20:58:53 +00:00
if ( Config . Edition ! = EDITION_SINGLE & & ! PrivateVideo )
2018-01-05 20:03:02 +00:00
{
CopyStringToBuffer ( & CollationBuffers - > Search , " \" %d \" : \" " , TimecodeToSeconds ( Anno - > time ) ) ;
CopyStringToBufferJSONSafe ( & CollationBuffers - > Search , Anno - > text ) ;
CopyStringToBuffer ( & CollationBuffers - > Search , " \" \n " ) ;
}
2017-11-11 00:34:47 +00:00
2017-08-29 20:35:28 +00:00
categories LocalTopics = { 0 } ;
categories LocalMedia = { 0 } ;
bool HasQuote = FALSE ;
bool HasReference = FALSE ;
quote_info QuoteInfo = { 0 } ;
// NOTE(matt): Tree structure of "annotation local" buffer dependencies
// Annotation
// AnnotationHeader
// AnnotationClass
// AnnotationData
// Text
2018-01-15 00:03:11 +00:00
// CategoryIcons
2017-08-29 20:35:28 +00:00
2018-02-05 23:57:17 +00:00
// TODO(matt): Shouldn't all of these guys DeclaimBuffer() before returning?
2017-10-18 21:07:29 +00:00
if ( ClaimBuffer ( & Annotation , " Annotation " , Kilobytes ( 8 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & AnnotationHeader , " AnnotationHeader " , 512 ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & AnnotationClass , " AnnotationClass " , 256 ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & AnnotationData , " AnnotationData " , 512 ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
if ( ClaimBuffer ( & Text , " Text " , Kilobytes ( 4 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
2018-03-06 20:40:12 +00:00
if ( ClaimBuffer ( & CategoryIcons , " CategoryIcons " , Kilobytes ( 1 ) ) = = RC_ARENA_FULL ) { hmml_free ( & HMML ) ; return RC_ARENA_FULL ; } ;
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & AnnotationHeader ,
" <div data-timestamp= \" %d \" " ,
TimecodeToSeconds ( Anno - > time ) ) ;
CopyStringToBuffer ( & AnnotationClass ,
" class= \" marker " ) ;
2018-04-04 22:39:38 +00:00
if ( ( Anno - > author | | Speakers . Count > 1 ) & & ! IsCategorisedAFK ( * Anno ) )
2017-08-29 20:35:28 +00:00
{
2018-04-04 22:39:38 +00:00
int SpeakerIndex ;
if ( Anno - > author & & ( SpeakerIndex = LinearSearchForSpeaker ( Speakers , Anno - > author ) ) = = - 1 )
2017-06-25 18:05:58 +00:00
{
2018-04-04 22:39:38 +00:00
if ( ! HasFilterMenu )
{
HasFilterMenu = TRUE ;
}
InsertCategory ( & Topics , & LocalTopics , & Media , & LocalMedia , " authored " ) ;
hsl_colour AuthorColour ;
StringToColourHash ( & AuthorColour , Anno - > author ) ;
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Text ,
2018-04-04 22:39:38 +00:00
" <span class= \" author \" style= \" color: hsl(%d, %d%%, %d%%); \" data-hue= \" %d \" data-saturation= \" %d%% \" >%s</span> " ,
2017-08-29 20:35:28 +00:00
AuthorColour . Hue , AuthorColour . Saturation , AuthorColour . Lightness ,
AuthorColour . Hue , AuthorColour . Saturation ,
Anno - > author ) ;
2017-06-25 18:05:58 +00:00
}
else
{
2018-04-04 22:39:38 +00:00
if ( ! Anno - > author )
{
SpeakerIndex = LinearSearchForSpeaker ( Speakers , HMML . metadata . member ) ;
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Text ,
2018-04-04 22:39:38 +00:00
" <span class= \" author \" style= \" color: hsl(%d, %d%%, %d%%); \" data-hue= \" %d \" data-saturation= \" %d%% \" >%s</span>: " ,
Speakers . Speaker [ SpeakerIndex ] . Colour . Hue ,
Speakers . Speaker [ SpeakerIndex ] . Colour . Saturation ,
Speakers . Speaker [ SpeakerIndex ] . Colour . Lightness ,
Speakers . Speaker [ SpeakerIndex ] . Colour . Hue ,
Speakers . Speaker [ SpeakerIndex ] . Colour . Saturation ,
2017-08-29 20:35:28 +00:00
2018-04-04 22:39:38 +00:00
Speakers . Speaker [ SpeakerIndex ] . Seen = = FALSE ? Speakers . Speaker [ SpeakerIndex ] . Credential - > CreditedName : Speakers . Speaker [ SpeakerIndex ] . Abbreviation ) ;
Speakers . Speaker [ SpeakerIndex ] . Seen = TRUE ;
}
2017-06-25 18:05:58 +00:00
}
2017-08-29 20:35:28 +00:00
char * InPtr = Anno - > text ;
int MarkerIndex = 0 , RefIndex = 0 ;
while ( * InPtr | | RefIndex < Anno - > reference_count )
2017-06-25 18:05:58 +00:00
{
2017-08-29 20:35:28 +00:00
if ( MarkerIndex < Anno - > marker_count & &
InPtr - Anno - > text = = Anno - > markers [ MarkerIndex ] . offset )
2017-05-13 15:38:10 +00:00
{
2017-08-29 20:35:28 +00:00
char * Readable = Anno - > markers [ MarkerIndex ] . parameter
? Anno - > markers [ MarkerIndex ] . parameter
: Anno - > markers [ MarkerIndex ] . marker ;
2018-04-04 22:39:38 +00:00
switch ( Anno - > markers [ MarkerIndex ] . type )
2017-08-29 20:35:28 +00:00
{
2018-04-04 22:39:38 +00:00
case HMML_MEMBER :
case HMML_PROJECT :
{
// TODO(matt): That EDITION_NETWORK site database API-polling stuff
hsl_colour Colour ;
StringToColourHash ( & Colour , Anno - > markers [ MarkerIndex ] . marker ) ;
CopyStringToBuffer ( & Text ,
2018-04-07 05:30:28 +00:00
" <span class= \" %s \" style= \" color: hsl(%d, %d%%, %d%%); \" data-hue= \" %d \" data-saturation= \" %d%% \" >%.*s</span> " ,
2018-04-04 22:39:38 +00:00
Anno - > markers [ MarkerIndex ] . type = = HMML_MEMBER ? " member " : " project " ,
Colour . Hue , Colour . Saturation , Colour . Lightness ,
Colour . Hue , Colour . Saturation ,
2018-04-07 05:30:28 +00:00
StringLength ( Readable ) , InPtr ) ;
2018-04-04 22:39:38 +00:00
} break ;
case HMML_CATEGORY :
2017-05-13 15:38:10 +00:00
{
2018-04-04 22:39:38 +00:00
switch ( GenerateTopicColours ( Anno - > markers [ MarkerIndex ] . marker ) )
{
case RC_SUCCESS :
case RC_NOOP :
break ;
case RC_ERROR_FILE :
case RC_ERROR_MEMORY :
hmml_free ( & HMML ) ;
return RC_ERROR_FATAL ;
} ;
if ( ! HasFilterMenu )
{
HasFilterMenu = TRUE ;
}
InsertCategory ( & Topics , & LocalTopics , & Media , & LocalMedia , Anno - > markers [ MarkerIndex ] . marker ) ;
2018-04-07 05:30:28 +00:00
CopyStringToBuffer ( & Text , " %.*s " , StringLength ( Readable ) , InPtr ) ;
2018-04-04 22:39:38 +00:00
} break ;
case HMML_MARKER_COUNT : break ;
2017-05-13 15:38:10 +00:00
}
2018-04-04 22:39:38 +00:00
InPtr + = StringLength ( Readable ) ;
+ + MarkerIndex ;
2017-05-13 15:38:10 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
while ( RefIndex < Anno - > reference_count & &
InPtr - Anno - > text = = Anno - > references [ RefIndex ] . offset )
2017-06-25 18:05:58 +00:00
{
2017-08-29 20:35:28 +00:00
HMML_Reference * CurrentRef = Anno - > references + RefIndex ;
if ( ! HasReferenceMenu )
{
CopyStringToBuffer ( & ReferenceMenu ,
" <div class= \" menu references \" > \n "
" <span>References ▼</span> \n "
" <div class= \" mouse_catcher \" ></div> \n "
" <div class= \" refs references_container \" > \n " ) ;
2017-06-25 18:22:54 +00:00
2018-04-05 21:25:23 +00:00
if ( BuildReference ( ReferencesArray , RefIdentifier , UniqueRefs , CurrentRef , Anno ) = = RC_INVALID_REFERENCE )
2017-08-29 20:35:28 +00:00
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Reference combination processing failed: %s:%d " , Filename , Anno - > line ) ;
2017-08-29 20:35:28 +00:00
fprintf ( stderr , " %s:%d: Cannot process new combination of reference info \n "
" \n "
" Either tweak your annotation, or contact matt@handmadedev.org \n "
" mentioning the ref node you want to write and how you want it to \n "
" appear in the references menu \n " , Filename , Anno - > line ) ;
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_INVALID_REFERENCE ;
2017-08-29 20:35:28 +00:00
}
+ + ReferencesArray [ RefIdentifier - 1 ] . IdentifierCount ;
+ + UniqueRefs ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
HasReferenceMenu = TRUE ;
}
else
2017-08-08 19:26:54 +00:00
{
2017-08-29 20:35:28 +00:00
for ( int i = 0 ; i < UniqueRefs ; + + i )
2017-08-08 19:26:54 +00:00
{
2017-08-29 20:35:28 +00:00
if ( ReferencesArray [ i ] . IdentifierCount = = REF_MAX_IDENTIFIER )
2017-08-08 19:26:54 +00:00
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_EMERGENCY , " REF_MAX_IDENTIFIER (%d) reached. Contact matt@handmadedev.org " , REF_MAX_IDENTIFIER ) ;
2017-08-29 20:35:28 +00:00
fprintf ( stderr , " %s:%d: Too many timecodes associated with one reference (increase REF_MAX_IDENTIFIER) \n " , Filename , Anno - > line ) ;
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_ERROR_MAX_REFS ;
2017-08-08 19:26:54 +00:00
}
2017-08-29 20:35:28 +00:00
if ( CurrentRef - > isbn )
2017-08-08 19:26:54 +00:00
{
2017-08-29 20:35:28 +00:00
if ( ! StringsDiffer ( CurrentRef - > isbn , ReferencesArray [ i ] . ID ) )
2017-08-08 19:26:54 +00:00
{
2017-08-29 20:35:28 +00:00
CopyString ( ReferencesArray [ i ] . Identifier [ ReferencesArray [ i ] . IdentifierCount ] . Timecode , Anno - > time ) ;
ReferencesArray [ i ] . Identifier [ ReferencesArray [ i ] . IdentifierCount ] . Identifier = RefIdentifier ;
+ + ReferencesArray [ i ] . IdentifierCount ;
goto AppendedIdentifier ;
2017-08-08 19:26:54 +00:00
}
}
2017-08-29 20:35:28 +00:00
else if ( CurrentRef - > url )
2017-08-08 19:26:54 +00:00
{
2017-08-29 20:35:28 +00:00
if ( ! StringsDiffer ( CurrentRef - > url , ReferencesArray [ i ] . ID ) )
2017-08-08 19:26:54 +00:00
{
2017-08-29 20:35:28 +00:00
CopyString ( ReferencesArray [ i ] . Identifier [ ReferencesArray [ i ] . IdentifierCount ] . Timecode , Anno - > time ) ;
ReferencesArray [ i ] . Identifier [ ReferencesArray [ i ] . IdentifierCount ] . Identifier = RefIdentifier ;
+ + ReferencesArray [ i ] . IdentifierCount ;
goto AppendedIdentifier ;
2017-08-08 19:26:54 +00:00
}
}
2017-08-29 20:35:28 +00:00
else
2017-08-08 19:26:54 +00:00
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Reference missing ISBN or URL: %s:%d " , Filename , Anno - > line ) ;
2017-08-29 20:35:28 +00:00
fprintf ( stderr , " %s:%d: Reference must have an ISBN or URL \n " , Filename , Anno - > line ) ;
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_INVALID_REFERENCE ;
2017-08-08 19:26:54 +00:00
}
}
2017-08-29 20:35:28 +00:00
2018-04-05 21:25:23 +00:00
if ( BuildReference ( ReferencesArray , RefIdentifier , UniqueRefs , CurrentRef , Anno ) = = RC_INVALID_REFERENCE )
2017-08-08 19:26:54 +00:00
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Reference combination processing failed: %s:%d " , Filename , Anno - > line ) ;
2017-08-29 20:35:28 +00:00
fprintf ( stderr , " %s:%d: Cannot process new combination of reference info \n "
" \n "
" Either tweak your annotation, or contact matt@handmadedev.org \n "
" mentioning the ref node you want to write and how you want it to \n "
" appear in the references menu \n " , Filename , Anno - > line ) ;
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_INVALID_REFERENCE ;
2017-08-08 19:26:54 +00:00
}
2017-08-29 20:35:28 +00:00
+ + ReferencesArray [ UniqueRefs ] . IdentifierCount ;
+ + UniqueRefs ;
2017-08-08 19:26:54 +00:00
}
2017-08-29 20:35:28 +00:00
AppendedIdentifier :
if ( ! HasReference )
2017-03-25 02:07:55 +00:00
{
2017-08-29 20:35:28 +00:00
if ( CurrentRef - > isbn )
2017-03-25 02:07:55 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & AnnotationData , " data-ref= \" %s " , CurrentRef - > isbn ) ;
2017-03-25 02:07:55 +00:00
}
2017-08-29 20:35:28 +00:00
else if ( CurrentRef - > url )
2017-03-29 03:38:12 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & AnnotationData , " data-ref= \" %s " , CurrentRef - > url ) ;
2017-03-29 03:38:12 +00:00
}
2017-03-30 02:57:04 +00:00
else
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Reference missing ISBN or URL: %s:%d " , Filename , Anno - > line ) ;
2017-08-29 20:35:28 +00:00
fprintf ( stderr , " %s:%d: Reference must have an ISBN or URL \n " , Filename , Anno - > line ) ;
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_INVALID_REFERENCE ;
2017-03-30 02:57:04 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
HasReference = TRUE ;
}
else
{
if ( CurrentRef - > isbn )
2017-03-29 03:38:12 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & AnnotationData , " ,%s " , CurrentRef - > isbn ) ;
2017-03-29 03:38:12 +00:00
}
2017-08-29 20:35:28 +00:00
else if ( CurrentRef - > url )
2017-05-24 21:56:36 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & AnnotationData , " ,%s " , CurrentRef - > url ) ;
2017-05-24 21:56:36 +00:00
}
else
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Reference missing ISBN or URL: %s:%d " , Filename , Anno - > line ) ;
2017-08-29 20:35:28 +00:00
fprintf ( stderr , " %s:%d: Reference must have an ISBN or URL " , Filename , Anno - > line ) ;
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_INVALID_REFERENCE ;
2017-05-24 21:56:36 +00:00
}
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2018-04-04 22:39:38 +00:00
CopyStringToBuffer ( & Text , " <sup style= \" vertical-align: super; \" >%s%d</sup> " ,
Anno - > references [ RefIndex ] . offset = = Anno - > references [ RefIndex - 1 ] . offset ? " , " : " " ,
RefIdentifier ) ;
2017-08-29 20:35:28 +00:00
+ + RefIndex ;
+ + RefIdentifier ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( * InPtr )
{
switch ( * InPtr )
2017-03-25 02:07:55 +00:00
{
2017-08-29 20:35:28 +00:00
case ' < ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( & Text , " < " ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' > ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( & Text , " > " ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' & ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( & Text , " & " ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' \" ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( & Text , " " " ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' \' ' :
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( & Text , " ' " ) ;
break ;
2017-08-29 20:35:28 +00:00
default :
2018-04-04 22:39:38 +00:00
* Text . Ptr + + = * InPtr ;
2017-08-19 21:41:51 +00:00
* Text . Ptr = ' \0 ' ;
2017-06-25 18:05:58 +00:00
break ;
2017-03-25 02:07:55 +00:00
}
2018-04-04 22:39:38 +00:00
+ + InPtr ;
2017-03-25 02:07:55 +00:00
}
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( Anno - > is_quote )
{
if ( ! HasQuoteMenu )
2017-03-29 03:38:12 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & QuoteMenu ,
" <div class= \" menu quotes \" > \n "
" <span>Quotes ▼</span> \n "
" <div class= \" mouse_catcher \" ></div> \n "
" <div class= \" refs quotes_container \" > \n " ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
HasQuoteMenu = TRUE ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( ! HasReference )
{
CopyStringToBuffer ( & AnnotationData , " data-ref= \" &#%d; " , QuoteIdentifier ) ;
}
else
{
CopyStringToBuffer ( & AnnotationData , " ,&#%d; " , QuoteIdentifier ) ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
HasQuote = TRUE ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
char * Speaker = Anno - > quote . author ? Anno - > quote . author : HMML . metadata . stream_username ? HMML . metadata . stream_username : HMML . metadata . member ;
2018-02-28 01:04:06 +00:00
bool ShouldFetchQuotes = FALSE ;
if ( Config . Mode & MODE_NOCACHE | | ( Config . Edition ! = EDITION_SINGLE & & time ( 0 ) - LastQuoteFetch > 60 * 60 ) )
{
ShouldFetchQuotes = TRUE ;
LastQuoteFetch = time ( 0 ) ;
}
2017-08-29 20:35:28 +00:00
if ( BuildQuote ( & QuoteInfo ,
Speaker ,
2018-02-28 01:04:06 +00:00
Anno - > quote . id , ShouldFetchQuotes ) = = RC_UNFOUND )
2017-08-29 20:35:28 +00:00
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Quote #%s %d not found: %s:%d " , Speaker , Anno - > quote . id , Filename , Anno - > line ) ;
2018-01-04 20:55:51 +00:00
Filename [ StringLength ( Filename ) - StringLength ( " .hmml " ) ] = ' \0 ' ;
fprintf ( stderr , " Quote #%s %d not found \n "
" \ e[1;31mSkipping \ e[0m %s - %s \n " ,
2017-08-29 20:35:28 +00:00
Speaker ,
2018-01-04 20:55:51 +00:00
Anno - > quote . id ,
BaseFilename , HMML . metadata . title ) ;
2017-08-29 20:35:28 +00:00
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_ERROR_QUOTE ;
2017-03-29 03:38:12 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & QuoteMenu ,
" <a target= \" _blank \" class= \" ref \" href= \" https://dev.abaines.me.uk/quotes/%s/%d \" > \n "
" <span data-id= \" &#%d; \" > \n "
" <span class= \" ref_content \" > \n "
" <div class= \" source \" >Quote %d</div> \n "
" <div class= \" ref_title \" > " ,
Speaker ,
Anno - > quote . id ,
QuoteIdentifier ,
Anno - > quote . id ) ;
CopyStringToBufferHTMLSafe ( & QuoteMenu , QuoteInfo . Text ) ;
CopyStringToBuffer ( & QuoteMenu , " </div> \n "
" <div class= \" quote_byline \" >—%s, %s</div> \n "
" </span> \n "
" <div class= \" ref_indices \" > \n "
" <span data-timestamp= \" %d \" class= \" timecode \" ><span class= \" ref_index \" >[&#%d;]</span><span class= \" time \" >%s</span></span> \n "
" </div> \n "
" </span> \n "
" </a> \n " ,
Speaker ,
QuoteInfo . Date ,
TimecodeToSeconds ( Anno - > time ) ,
QuoteIdentifier ,
Anno - > time ) ;
if ( ! Anno - > text [ 0 ] )
2017-03-25 02:07:55 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Text , " “ " ) ;
CopyStringToBufferHTMLSafe ( & Text , QuoteInfo . Text ) ;
CopyStringToBuffer ( & Text , " ” " ) ;
2017-05-22 21:37:00 +00:00
}
2017-12-24 17:16:08 +00:00
CopyStringToBuffer ( & Text , " <sup style= \" vertical-align: super; \" >&#%d;</sup> " , QuoteIdentifier ) ;
2017-08-29 20:35:28 +00:00
+ + QuoteIdentifier ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
while ( MarkerIndex < Anno - > marker_count )
{
2018-01-06 00:00:05 +00:00
switch ( GenerateTopicColours ( Anno - > markers [ MarkerIndex ] . marker ) )
2017-05-22 21:37:00 +00:00
{
2018-01-06 00:00:05 +00:00
case RC_SUCCESS :
case RC_NOOP :
break ;
case RC_ERROR_FILE :
case RC_ERROR_MEMORY :
hmml_free ( & HMML ) ;
return RC_ERROR_FATAL ;
2018-01-15 00:03:11 +00:00
}
2018-01-06 00:00:05 +00:00
if ( ! HasFilterMenu )
{
HasFilterMenu = TRUE ;
2017-03-25 02:07:55 +00:00
}
2018-01-06 00:00:05 +00:00
InsertCategory ( & Topics , & LocalTopics , & Media , & LocalMedia , Anno - > markers [ MarkerIndex ] . marker ) ;
2017-08-29 20:35:28 +00:00
+ + MarkerIndex ;
}
2017-06-25 18:22:54 +00:00
2018-01-15 00:03:11 +00:00
if ( LocalTopics . Count = = 0 )
{
switch ( GenerateTopicColours ( " nullTopic " ) )
{
case RC_SUCCESS :
case RC_NOOP :
break ;
case RC_ERROR_FILE :
case RC_ERROR_MEMORY :
hmml_free ( & HMML ) ;
return RC_ERROR_FATAL ;
} ;
InsertCategory ( & Topics , & LocalTopics , & Media , & LocalMedia , " nullTopic " ) ;
}
2017-08-29 20:35:28 +00:00
if ( LocalMedia . Count = = 0 )
{
2018-01-03 22:16:20 +00:00
InsertCategory ( & Topics , & LocalTopics , & Media , & LocalMedia , DefaultMedium ) ;
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2018-01-15 00:03:11 +00:00
BuildCategories ( & AnnotationClass , & CategoryIcons , & LocalTopics , & LocalMedia , & MarkerIndex , DefaultMedium ) ;
2017-08-29 20:35:28 +00:00
CopyBuffer ( & AnnotationHeader , & AnnotationClass ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( HasQuote | | HasReference )
{
CopyStringToBuffer ( & AnnotationData , " \" " ) ;
CopyBuffer ( & AnnotationHeader , & AnnotationData ) ;
}
CopyStringToBuffer ( & AnnotationHeader , " > \n " ) ;
CopyBuffer ( & Annotation , & AnnotationHeader ) ;
CopyStringToBuffer ( & Annotation ,
2017-12-10 17:05:46 +00:00
" <div class= \" cineraContent \" ><span class= \" timecode \" >%s</span> " ,
2017-08-29 20:35:28 +00:00
Anno - > time ) ;
CopyBuffer ( & Annotation , & Text ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( LocalTopics . Count > 0 )
{
2018-01-15 00:03:11 +00:00
CopyBuffer ( & Annotation , & CategoryIcons ) ;
2017-03-22 02:18:31 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Annotation , " </div> \n "
" <div class= \" progress faded \" > \n "
2017-12-10 17:05:46 +00:00
" <div class= \" cineraContent \" ><span class= \" timecode \" >%s</span> " ,
2017-08-29 20:35:28 +00:00
Anno - > time ) ;
CopyBuffer ( & Annotation , & Text ) ;
2017-11-11 00:34:47 +00:00
if ( LocalTopics . Count > 0 )
{
2018-01-15 00:03:11 +00:00
CopyBuffer ( & Annotation , & CategoryIcons ) ;
2017-11-11 00:34:47 +00:00
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Annotation , " </div> \n "
" </div> \n "
" <div class= \" progress main \" > \n "
2017-12-10 17:05:46 +00:00
" <div class= \" cineraContent \" ><span class= \" timecode \" >%s</span> " ,
2017-08-29 20:35:28 +00:00
Anno - > time ) ;
CopyBuffer ( & Annotation , & Text ) ;
2017-11-11 00:34:47 +00:00
if ( LocalTopics . Count > 0 )
{
2018-01-15 00:03:11 +00:00
CopyBuffer ( & Annotation , & CategoryIcons ) ;
2017-11-11 00:34:47 +00:00
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Annotation , " </div> \n "
" </div> \n "
" </div> \n " ) ;
CopyBuffer ( & CollationBuffers - > Player , & Annotation ) ;
// NOTE(matt): Tree structure of "annotation local" buffer dependencies
2018-01-15 00:03:11 +00:00
// CategoryIcons
2017-08-29 20:35:28 +00:00
// Text
// AnnotationData
// AnnotationClass
// AnnotationHeader
// Annotation
2018-01-15 00:03:11 +00:00
DeclaimBuffer ( & CategoryIcons ) ;
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( & Text ) ;
DeclaimBuffer ( & AnnotationData ) ;
DeclaimBuffer ( & AnnotationClass ) ;
DeclaimBuffer ( & AnnotationHeader ) ;
DeclaimBuffer ( & Annotation ) ;
2017-08-29 20:35:28 +00:00
}
2018-01-05 20:03:02 +00:00
2018-04-01 20:58:53 +00:00
if ( Config . Edition ! = EDITION_SINGLE & & ! PrivateVideo )
2018-01-05 20:03:02 +00:00
{
CopyStringToBuffer ( & CollationBuffers - > Search , " --- \n " ) ;
2018-04-01 20:58:53 +00:00
N - > This . Size = CollationBuffers - > Search . Ptr - CollationBuffers - > Search . Location ;
2018-01-05 20:03:02 +00:00
}
2017-08-29 20:35:28 +00:00
2017-05-21 06:35:16 +00:00
# if DEBUG
2017-08-29 20:35:28 +00:00
printf ( " \n \n --- End of Annotations Loop --- \n \n \n \n " ) ;
2017-05-21 06:35:16 +00:00
# endif
2017-08-29 20:35:28 +00:00
if ( HasQuoteMenu )
{
CopyStringToBuffer ( & QuoteMenu ,
" </div> \n "
" </div> \n " ) ;
CopyBuffer ( & CollationBuffers - > Menus , & QuoteMenu ) ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( HasReferenceMenu )
{
for ( int i = 0 ; i < UniqueRefs ; + + i )
2017-03-29 03:38:12 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & ReferenceMenu ,
" <a data-id= \" %s \" href= \" %s \" target= \" _blank \" class= \" ref \" > \n "
" <span class= \" ref_content \" > \n " ,
ReferencesArray [ i ] . ID ,
ReferencesArray [ i ] . URL ) ;
if ( * ReferencesArray [ i ] . Source )
2017-03-30 02:57:04 +00:00
{
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( & ReferenceMenu ,
2017-08-29 20:35:28 +00:00
" <div class= \" source \" > " ) ;
CopyStringToBufferHTMLSafe ( & ReferenceMenu , ReferencesArray [ i ] . Source ) ;
CopyStringToBuffer ( & ReferenceMenu , " </div> \n "
" <div class= \" ref_title \" > " ) ;
CopyStringToBufferHTMLSafe ( & ReferenceMenu , ReferencesArray [ i ] . RefTitle ) ;
CopyStringToBuffer ( & ReferenceMenu , " </div> \n " ) ;
}
else
{
2017-06-25 18:05:58 +00:00
CopyStringToBuffer ( & ReferenceMenu ,
2017-08-29 20:35:28 +00:00
" <div class= \" ref_title \" > " ) ;
CopyStringToBufferHTMLSafe ( & ReferenceMenu , ReferencesArray [ i ] . RefTitle ) ;
CopyStringToBuffer ( & ReferenceMenu , " </div> \n " ) ;
}
CopyStringToBuffer ( & ReferenceMenu ,
" </span> \n " ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
for ( int j = 0 ; j < ReferencesArray [ i ] . IdentifierCount ; )
{
CopyStringToBuffer ( & ReferenceMenu ,
" <div class= \" ref_indices \" > \n " ) ;
for ( int k = 0 ; k < 3 & & j < ReferencesArray [ i ] . IdentifierCount ; + + k , + + j )
2017-06-25 18:05:58 +00:00
{
CopyStringToBuffer ( & ReferenceMenu ,
2017-08-29 20:35:28 +00:00
" <span data-timestamp= \" %d \" class= \" timecode \" ><span class= \" ref_index \" >[%d]</span><span class= \" time \" >%s</span></span> " ,
TimecodeToSeconds ( ReferencesArray [ i ] . Identifier [ j ] . Timecode ) ,
ReferencesArray [ i ] . Identifier [ j ] . Identifier ,
ReferencesArray [ i ] . Identifier [ j ] . Timecode ) ;
2017-06-25 18:05:58 +00:00
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & ReferenceMenu , " \n "
" </div> \n " ) ;
2017-03-30 02:57:04 +00:00
}
2017-06-25 18:22:54 +00:00
2017-03-29 03:38:12 +00:00
CopyStringToBuffer ( & ReferenceMenu ,
2017-08-29 20:35:28 +00:00
" </a> \n " ) ;
}
CopyStringToBuffer ( & ReferenceMenu ,
" </div> \n "
" </div> \n " ) ;
CopyBuffer ( & CollationBuffers - > Menus , & ReferenceMenu ) ;
}
if ( HasFilterMenu )
{
2018-01-15 00:03:11 +00:00
// NOTE(matt): Two loops, one for each JS "object"
2018-02-23 23:36:42 +00:00
CopyStringToBuffer ( & FilterState , " <script> \n "
2017-11-11 00:34:47 +00:00
" var filterInitState = { \n " ) ;
for ( int i = 0 ; i < Topics . Count ; + + i )
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( Topics . Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , Topics . Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
2017-11-11 00:34:47 +00:00
CopyStringToBuffer ( & FilterState , " \" %s \" : \t { \" type \" : \" %s \" , \t \" off \" : false }, \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker , " topic " ) ;
2017-11-11 00:34:47 +00:00
}
for ( int i = 0 ; i < Media . Count ; + + i )
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( Media . Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , Media . Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
2017-11-11 00:34:47 +00:00
if ( ! StringsDiffer ( Media . Category [ i ] . Marker , " afk " ) ) // TODO(matt): Make this configurable?
{
CopyStringToBuffer ( & FilterState , " \" %s \" : \t { \" type \" : \" %s \" , \t \" off \" : true }, \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker , " medium " ) ;
2017-11-11 00:34:47 +00:00
}
else
{
CopyStringToBuffer ( & FilterState , " \" %s \" : \t { \" type \" : \" %s \" , \t \" off \" : false }, \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker , " medium " ) ;
2017-11-11 00:34:47 +00:00
}
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & FilterState ,
2017-11-11 00:34:47 +00:00
" }; \n "
" \n "
2017-08-29 20:35:28 +00:00
" var filterState = { \n " ) ;
for ( int i = 0 ; i < Topics . Count ; + + i )
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( Topics . Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , Topics . Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & FilterState , " \" %s \" : \t { \" type \" : \" %s \" , \t \" off \" : false }, \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker , " topic " ) ;
2017-03-29 03:38:12 +00:00
}
2017-08-29 20:35:28 +00:00
for ( int i = 0 ; i < Media . Count ; + + i )
2017-05-21 06:35:16 +00:00
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( Media . Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , Media . Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
2017-11-11 00:34:47 +00:00
if ( ! StringsDiffer ( Media . Category [ i ] . Marker , " afk " ) ) // TODO(matt): Make this configurable?
{
CopyStringToBuffer ( & FilterState , " \" %s \" : \t { \" type \" : \" %s \" , \t \" off \" : true }, \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker , " medium " ) ;
2017-11-11 00:34:47 +00:00
}
else
{
CopyStringToBuffer ( & FilterState , " \" %s \" : \t { \" type \" : \" %s \" , \t \" off \" : false }, \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker , " medium " ) ;
2017-11-11 00:34:47 +00:00
}
2017-08-29 20:35:28 +00:00
}
2017-11-11 00:34:47 +00:00
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & FilterState ,
2017-11-11 00:34:47 +00:00
" }; \n "
2018-02-23 23:36:42 +00:00
" </script> \n " ) ;
2017-06-25 18:22:54 +00:00
2017-12-07 21:07:36 +00:00
buffer URLPrefix ;
2017-12-12 23:24:10 +00:00
ClaimBuffer ( & URLPrefix , " URLPrefix " , 1024 ) ;
2017-12-18 00:15:03 +00:00
ConstructURLPrefix ( & URLPrefix , INCLUDE_Images , PAGE_PLAYER ) ;
2017-11-11 00:34:47 +00:00
CopyStringToBuffer ( & FilterMenu ,
" <div class= \" menu filter \" > \n "
" <span><img src= \" %scinera_icon_filter.png \" ></span> \n "
" <div class= \" filter_container \" > \n "
2018-01-15 00:03:11 +00:00
" <div class= \" filter_mode exclusive \" >Filter mode: </div> \n "
2017-11-11 00:34:47 +00:00
" <div class= \" filters \" > \n " ,
2017-12-07 21:07:36 +00:00
URLPrefix . Location ) ;
DeclaimBuffer ( & URLPrefix ) ;
2017-11-11 00:34:47 +00:00
2017-08-29 20:35:28 +00:00
if ( Topics . Count > 0 )
{
CopyStringToBuffer ( & FilterMenu ,
" <div class= \" filter_topics \" > \n "
" <div class= \" filter_title \" >Topics</div> \n " ) ;
for ( int i = 0 ; i < Topics . Count ; + + i )
2017-08-19 22:35:07 +00:00
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( Topics . Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , Topics . Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
bool NullTopic = ! StringsDiffer ( Topics . Category [ i ] . Marker , " nullTopic " ) ;
2017-08-19 22:35:07 +00:00
CopyStringToBuffer ( & FilterTopics ,
2018-01-15 00:03:11 +00:00
" <div %sclass= \" filter_content %s \" > \n "
2017-12-29 16:00:44 +00:00
" <span class= \" icon category %s \" ></span><span class= \" cineraText \" >%s</span> \n "
2017-08-29 20:35:28 +00:00
" </div> \n " ,
2018-01-15 00:03:11 +00:00
NullTopic ? " title= \" Annotations that don't fit into the above topic(s) may be filtered using this pseudo-topic \" " : " " ,
SanitisedMarker ,
SanitisedMarker ,
NullTopic ? " (null topic) " : Topics . Category [ i ] . Marker ) ;
2017-08-19 22:35:07 +00:00
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & FilterTopics ,
" </div> \n " ) ;
CopyBuffer ( & FilterMenu , & FilterTopics ) ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( Media . Count > 0 )
{
CopyStringToBuffer ( & FilterMedia ,
" <div class= \" filter_media \" > \n "
" <div class= \" filter_title \" >Media</div> \n " ) ;
for ( int i = 0 ; i < Media . Count ; + + i )
2017-08-19 22:35:07 +00:00
{
2018-01-15 00:03:11 +00:00
char SanitisedMarker [ StringLength ( Media . Category [ i ] . Marker ) ] ;
CopyString ( SanitisedMarker , Media . Category [ i ] . Marker ) ;
SanitisePunctuation ( SanitisedMarker ) ;
2017-08-29 20:35:28 +00:00
int j ;
for ( j = 0 ; j < ArrayCount ( CategoryMedium ) ; + + j )
2017-05-24 21:56:36 +00:00
{
2017-08-29 20:35:28 +00:00
if ( ! StringsDiffer ( Media . Category [ i ] . Marker , CategoryMedium [ j ] . Medium ) )
2017-05-24 21:56:36 +00:00
{
2017-08-29 20:35:28 +00:00
break ;
2017-05-24 21:56:36 +00:00
}
2017-05-21 06:35:16 +00:00
}
2017-08-29 20:35:28 +00:00
2017-11-11 00:34:47 +00:00
if ( ! StringsDiffer ( Media . Category [ i ] . Marker , " afk " ) ) // TODO(matt): Initially hidden config
2018-01-15 00:03:11 +00:00
// When we do this for real, we'll probably need to loop
// over the configured media to see who should be hidden
2017-11-11 00:34:47 +00:00
{
CopyStringToBuffer ( & FilterMedia ,
" <div class= \" filter_content %s off \" > \n "
2017-12-29 16:00:44 +00:00
" <span class= \" icon \" >%s</span><span class= \" cineraText \" >%s</span> \n "
2017-11-11 00:34:47 +00:00
" </div> \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker ,
2017-11-11 00:34:47 +00:00
CategoryMedium [ j ] . Icon ,
2018-01-15 00:03:11 +00:00
CategoryMedium [ j ] . WrittenName ) ;
2017-11-11 00:34:47 +00:00
}
else
{
CopyStringToBuffer ( & FilterMedia ,
" <div class= \" filter_content %s \" > \n "
2018-01-15 00:03:11 +00:00
" <span class= \" icon \" >%s</span><span class= \" cineraText \" >%s%s</span> \n "
2017-11-11 00:34:47 +00:00
" </div> \n " ,
2018-01-15 00:03:11 +00:00
SanitisedMarker ,
2017-11-11 00:34:47 +00:00
CategoryMedium [ j ] . Icon ,
2018-01-15 00:03:11 +00:00
CategoryMedium [ j ] . WrittenName ,
! StringsDiffer ( Media . Category [ i ] . Marker , DefaultMedium ) ? " </span><span class= \" cineraDefaultMediumIndicator \" title= \" Default medium \n "
" Annotations lacking a media icon are in this medium \" >🟉 " : " " ) ;
2017-11-11 00:34:47 +00:00
}
2017-05-21 06:35:16 +00:00
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & FilterMedia ,
" </div> \n " ) ;
CopyBuffer ( & FilterMenu , & FilterMedia ) ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & FilterMenu ,
" </div> \n "
" </div> \n "
" </div> \n " ) ;
2017-08-19 22:35:07 +00:00
2017-08-29 20:35:28 +00:00
CopyBuffer ( & CollationBuffers - > Menus , & FilterMenu ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2018-01-17 20:15:00 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <div class= \" menu views \" > \n "
" <div class= \" view \" data-id= \" theatre \" title= \" Theatre mode \" >🎭</div> \n "
" <div class= \" views_container \" > \n "
" <div class= \" view \" data-id= \" super \" title= \" SUPERtheatre mode \" >🏟</div> \n "
" </div> \n "
" </div> \n " ) ;
2017-08-29 20:35:28 +00:00
if ( HasCreditsMenu )
{
CopyBuffer ( & CollationBuffers - > Menus , & CreditsMenu ) ;
}
2017-06-25 18:22:54 +00:00
2018-01-15 21:52:24 +00:00
// TODO(matt): Maybe figure out a more succinct way to code this Help text
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <div class= \" help \" > \n "
" <span>?</span> \n "
" <div class= \" help_container \" > \n "
" <span class= \" help_key \" >?</span><h1>Keyboard Navigation</h1> \n "
" \n "
" <h2>Global Keys</h2> \n "
2018-02-28 01:04:06 +00:00
" <span class= \" help_key \" >[</span>, <span class= \" help_key \" ><</span> / <span class= \" help_key \" >]</span>, <span class= \" help_key \" >></span> <span class= \" help_text \" >Jump to previous / next episode</span><br> \n "
" <span class= \" help_key \" >W</span>, <span class= \" help_key \" >K</span>, <span class= \" help_key \" >P</span> / <span class= \" help_key \" >S</span>, <span class= \" help_key \" >J</span>, <span class= \" help_key \" >N</span> <span class= \" help_text \" >Jump to previous / next marker</span><br> \n "
2018-01-15 21:52:24 +00:00
" <span class= \" help_key \" >t</span> / <span class= \" help_key \" >T</span> <span class= \" help_text \" >Toggle theatre / SUPERtheatre mode</span><br> \n "
) ;
2017-08-29 20:35:28 +00:00
if ( HasFilterMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key \" >z</span> <span class= \" help_text \" >Toggle filter mode</span> <span class= \" help_key \" >V</span> <span class= \" help_text \" >Revert filter to original state</span> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key unavailable \" >z</span> <span class= \" help_text unavailable \" >Toggle filter mode</span> <span class= \" help_key unavailable \" >V</span> <span class= \" help_text unavailable \" >Revert filter to original state</span> \n " ) ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" \n "
" <h2>Menu toggling</h2> \n " ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( HasQuoteMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key \" >q</span> <span class= \" help_text \" >Quotes</span> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key unavailable \" >q</span> <span class= \" help_text unavailable \" >Quotes</span> \n " ) ;
}
if ( HasReferenceMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key \" >r</span> <span class= \" help_text \" >References</span> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key unavailable \" >r</span> <span class= \" help_text unavailable \" >References</span> \n " ) ;
}
if ( HasFilterMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key \" >f</span> <span class= \" help_text \" >Filter</span> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key unavailable \" >f</span> <span class= \" help_text unavailable \" >Filter</span> \n " ) ;
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( HasCreditsMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key \" >c</span> <span class= \" help_text \" >Credits</span> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key unavailable \" >c</span> <span class= \" help_text unavailable \" >Credits</span> \n " ) ;
}
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" \n "
2018-02-28 01:04:06 +00:00
" <h2>In-Menu Movement</h2> \n "
2017-08-29 20:35:28 +00:00
" <div class= \" help_paragraph \" > \n "
" <div class= \" key_block \" > \n "
" <div class= \" key_column \" style= \" flex-grow: 1 \" > \n "
" <span class= \" help_key \" >a</span> \n "
" </div> \n "
" <div class= \" key_column \" style= \" flex-grow: 2 \" > \n "
" <span class= \" help_key \" >w</span><br> \n "
" <span class= \" help_key \" >s</span> \n "
" </div> \n "
" <div class= \" key_column \" style= \" flex-grow: 1 \" > \n "
" <span class= \" help_key \" >d</span> \n "
" </div> \n "
" </div> \n "
" <div class= \" key_block \" > \n "
" <span class= \" help_key \" >h</span> \n "
" <span class= \" help_key \" >j</span> \n "
" <span class= \" help_key \" >k</span> \n "
" <span class= \" help_key \" >l</span> \n "
" </div> \n "
" <div class= \" key_block \" > \n "
" <div style= \" flex-grow: 1 \" > \n "
" <span class= \" help_key \" >←</span> \n "
" </div> \n "
" <div style= \" flex-grow: 2 \" > \n "
" <span class= \" help_key \" >↑</span><br> \n "
" <span class= \" help_key \" >↓</span> \n "
" </div> \n "
" <div style= \" flex-grow: 1 \" > \n "
" <span class= \" help_key \" >→</span> \n "
" </div> \n "
" </div> \n "
" </div> \n "
" <br> \n " ) ;
if ( HasQuoteMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2>Quotes " ) ;
2017-06-03 01:32:18 +00:00
if ( HasReferenceMenu )
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " and References Menus</h2> \n " ) ;
2017-06-03 01:32:18 +00:00
}
else
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " <span class= \" unavailable \" >and References</span> Menus</h2> \n " ) ;
2017-06-03 01:32:18 +00:00
}
2017-08-29 20:35:28 +00:00
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2><span class= \" unavailable \" >Quotes " ) ;
if ( HasReferenceMenu )
2017-06-03 01:32:18 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " and</span> References Menus</h2> \n " ) ;
2017-06-03 01:32:18 +00:00
}
else
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " and References Menus</span></h2> \n " ) ;
2017-06-03 01:32:18 +00:00
}
2017-08-29 20:35:28 +00:00
}
if ( HasQuoteMenu | | HasReferenceMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span style= \" width: auto \" class= \" help_key \" >Enter</span> <span class= \" help_text \" >Jump to timecode</span><br> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span style= \" width: auto \" class= \" help_key unavailable \" >Enter</span> <span class= \" help_text unavailable \" >Jump to timecode</span><br> \n " ) ;
}
CopyStringToBuffer ( & CollationBuffers - > Menus , " \n " ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( HasQuoteMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2>Quotes " ) ;
if ( HasReferenceMenu )
2017-06-03 01:32:18 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " , References " ) ;
if ( HasCreditsMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus , " and Credits Menus</h2> " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus , " <span class= \" unavailable \" >and Credits</span> Menus</h2> " ) ;
}
2017-06-03 01:32:18 +00:00
}
else
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " <span class= \" unavailable \" >, References " ) ;
if ( HasCreditsMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus , " </span>and Credits Menus</h2> " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus , " and Credits</span> Menus</h2> " ) ;
}
2017-06-03 01:32:18 +00:00
}
2017-08-29 20:35:28 +00:00
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2><span class= \" unavailable \" >Quotes " ) ;
if ( HasReferenceMenu )
2017-06-03 01:32:18 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " , </span>References " ) ;
if ( HasCreditsMenu )
2017-06-03 01:32:18 +00:00
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " and Credits Menus</h2> " ) ;
2017-06-03 01:32:18 +00:00
}
else
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " <span class= \" unavailable \" >and Credits</span> Menus</h2> " ) ;
2017-06-03 01:32:18 +00:00
}
}
else
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " , References " ) ;
if ( HasCreditsMenu )
2017-06-03 01:32:18 +00:00
{
2018-01-15 21:52:24 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " and</span> Credits Menus</h2> " ) ;
2017-06-03 01:32:18 +00:00
}
else
{
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Menus , " and Credits Menus</h2></span> " ) ;
2017-06-03 01:32:18 +00:00
}
}
2017-08-29 20:35:28 +00:00
}
CopyStringToBuffer ( & CollationBuffers - > Menus , " \n " ) ;
if ( HasQuoteMenu | | HasReferenceMenu | | HasCreditsMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key \" >o</span> <span class= \" help_text \" >Open URL (in new tab)</span> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <span class= \" help_key unavailable \" >o</span> <span class= \" help_text unavailable \" >Open URL (in new tab)</span> \n " ) ;
}
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" \n " ) ;
if ( HasFilterMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2>Filter Menu</h2> \n "
" <span class= \" help_key \" >x</span>, <span style= \" width: auto \" class= \" help_key \" >Space</span> <span class= \" help_text \" >Toggle category and focus next</span><br> \n "
" <span class= \" help_key \" >X</span>, <span style= \" width: auto; margin-right: 0px \" class= \" help_key \" >Shift</span><span style= \" width: auto \" class= \" help_key \" >Space</span> <span class= \" help_text \" >Toggle category and focus previous</span><br> \n "
" <span class= \" help_key \" >v</span> <span class= \" help_text \" >Invert topics / media as per focus</span> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2><span class= \" unavailable \" >Filter Menu</span></h2> \n "
" <span class= \" help_key unavailable \" >x</span>, <span style= \" width: auto \" class= \" help_key unavailable \" >Space</span> <span class= \" help_text unavailable \" >Toggle category and focus next</span><br> \n "
" <span class= \" help_key unavailable \" >X</span>, <span style= \" width: auto; margin-right: 0px \" class= \" help_key unavailable \" >Shift</span><span style= \" width: auto \" class= \" help_key unavailable \" >Space</span> <span class= \" help_text unavailable \" >Toggle category and focus previous</span><br> \n "
" <span class= \" help_key unavailable \" >v</span> <span class= \" help_text unavailable \" >Invert topics / media as per focus</span> \n " ) ;
}
CopyStringToBuffer ( & CollationBuffers - > Menus , " \n " ) ;
if ( HasCreditsMenu )
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2>Credits Menu</h2> \n "
" <span style= \" width: auto \" class= \" help_key \" >Enter</span> <span class= \" help_text \" >Open URL (in new tab)</span><br> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" <h2><span class= \" unavailable \" >Credits Menu</span></h2> \n "
" <span style= \" width: auto \" class= \" help_key unavailable \" >Enter</span> <span class= \" help_text unavailable \" >Open URL (in new tab)</span><br> \n " ) ;
}
CopyStringToBuffer ( & CollationBuffers - > Menus ,
" </div> \n "
" </div> \n "
" </div> " ) ;
2018-02-21 21:50:23 +00:00
CopyStringToBuffer ( & CollationBuffers - > Player ,
" </div> \n " ) ;
2018-04-01 20:58:53 +00:00
if ( N )
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
N - > This . LinkOffsets . NextStart = ( CollationBuffers - > Player . Ptr - CollationBuffers - > Player . Location - ( N - > This . LinkOffsets . PrevStart + N - > This . LinkOffsets . PrevEnd ) ) ;
if ( N - > Prev . Size | | N - > Next . Size )
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
if ( N - > Next . Size > 0 )
2018-02-21 21:50:23 +00:00
{
// TODO(matt): Once we have a more rigorous notion of "Day Numbers", perhaps also use them here
buffer NextPlayerURL ;
ClaimBuffer ( & NextPlayerURL , " NextPlayerURL " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH ) ;
2018-04-01 20:58:53 +00:00
ConstructPlayerURL ( & NextPlayerURL , N - > Next . BaseFilename ) ;
2018-02-21 21:50:23 +00:00
CopyStringToBuffer ( & CollationBuffers - > Player ,
" <a class= \" episodeMarker next \" href= \" %s \" ><div>⏬</div><div>Next: '%s'</div><div>⏬</div></a> \n " ,
NextPlayerURL . Location ,
2018-04-01 20:58:53 +00:00
N - > Next . Title ) ;
2018-02-21 21:50:23 +00:00
DeclaimBuffer ( & NextPlayerURL ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Player ,
" <div class= \" episodeMarker last \" ><div>•</div><div>You have arrived at the (current) end of <cite>%s</cite></div><div>•</div></div> \n " , CollationBuffers - > ProjectName ) ;
}
}
2018-04-01 20:58:53 +00:00
N - > This . LinkOffsets . NextEnd = ( CollationBuffers - > Player . Ptr - CollationBuffers - > Player . Location - ( N - > This . LinkOffsets . PrevStart + N - > This . LinkOffsets . PrevEnd + N - > This . LinkOffsets . NextStart ) ) ;
2018-02-21 21:50:23 +00:00
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & CollationBuffers - > Player ,
" </div> \n "
" </div> " ) ;
// TODO(matt): Maybe do something about indentation levels
2017-12-12 23:24:10 +00:00
buffer URLIndex ;
2018-02-21 21:50:23 +00:00
ClaimBuffer ( & URLIndex , " URLIndex " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 ) ;
2017-12-12 23:24:10 +00:00
ConstructIndexURL ( & URLIndex ) ;
CopyString ( CollationBuffers - > URLIndex , URLIndex . Location ) ;
DeclaimBuffer ( & URLIndex ) ;
2017-12-07 21:07:36 +00:00
buffer URLPrefix ;
2017-12-12 23:24:10 +00:00
ClaimBuffer ( & URLPrefix , " URLPrefix " , 1024 ) ;
2017-12-07 21:07:36 +00:00
ConstructURLPrefix ( & URLPrefix , INCLUDE_CSS , PAGE_INDEX ) ;
2017-11-11 00:34:47 +00:00
CopyStringToBuffer ( & CollationBuffers - > IncludesIndex ,
" <link rel= \" stylesheet \" type= \" text/css \" href= \" %scinera.css \" > \n "
" <link rel= \" stylesheet \" type= \" text/css \" href= \" %scinera__%s.css \" > \n "
" <link rel= \" stylesheet \" type= \" text/css \" href= \" %scinera_topics.css \" > \n "
" <style>body { overflow-y: scroll; }</style> \n "
2017-08-29 20:35:28 +00:00
" \n "
" <meta charset= \" UTF-8 \" > \n "
2017-11-11 00:34:47 +00:00
" <meta name= \" generator \" content= \" Cinera %d.%d.%d \" > \n " ,
2017-12-07 21:07:36 +00:00
URLPrefix . Location ,
URLPrefix . Location , StringsDiffer ( Config . Theme , " " ) ? Config . Theme : HMML . metadata . project ,
URLPrefix . Location ,
2017-11-11 00:34:47 +00:00
CINERA_APP_VERSION . Major ,
CINERA_APP_VERSION . Minor ,
CINERA_APP_VERSION . Patch ) ;
2017-12-07 21:07:36 +00:00
ConstructURLPrefix ( & URLPrefix , INCLUDE_CSS , PAGE_PLAYER ) ;
2017-11-11 00:34:47 +00:00
CopyStringToBuffer ( & CollationBuffers - > IncludesPlayer ,
" <link rel= \" stylesheet \" type= \" text/css \" href= \" %scinera.css \" > \n "
" <link rel= \" stylesheet \" type= \" text/css \" href= \" %scinera__%s.css \" > \n "
" <link rel= \" stylesheet \" type= \" text/css \" href= \" %scinera_topics.css \" > \n "
" \n "
" <meta charset= \" UTF-8 \" > \n "
" <meta name= \" generator \" content= \" Cinera %d.%d.%d \" > \n " ,
2017-12-07 21:07:36 +00:00
URLPrefix . Location ,
URLPrefix . Location , StringsDiffer ( Config . Theme , " " ) ? Config . Theme : HMML . metadata . project ,
URLPrefix . Location ,
2017-11-11 00:34:47 +00:00
CINERA_APP_VERSION . Major ,
CINERA_APP_VERSION . Minor ,
CINERA_APP_VERSION . Patch ) ;
2017-08-29 20:35:28 +00:00
if ( Topics . Count | | Media . Count )
{
CopyStringToBuffer ( & CollationBuffers - > IncludesPlayer ,
" <meta name= \" keywords \" content= \" " ) ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( Topics . Count > 0 )
2017-06-03 01:32:18 +00:00
{
2017-08-29 20:35:28 +00:00
for ( int i = 0 ; i < Topics . Count ; + + i )
{
2018-01-15 00:03:11 +00:00
if ( StringsDiffer ( Topics . Category [ i ] . Marker , " nullTopic " ) )
{
CopyStringToBuffer ( & CollationBuffers - > IncludesPlayer , " %s, " , Topics . Category [ i ] . Marker ) ;
}
2017-08-29 20:35:28 +00:00
}
2017-06-03 01:32:18 +00:00
}
2017-08-29 20:35:28 +00:00
if ( Media . Count > 0 )
2017-06-03 01:32:18 +00:00
{
2017-08-29 20:35:28 +00:00
for ( int i = 0 ; i < Media . Count ; + + i )
{
CopyStringToBuffer ( & CollationBuffers - > IncludesPlayer , " %s, " , Media . Category [ i ] . WrittenText ) ;
}
2017-06-03 01:32:18 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
CollationBuffers - > IncludesPlayer . Ptr - = 2 ;
CopyStringToBuffer ( & CollationBuffers - > IncludesPlayer , " \" > \n \n " ) ;
}
2017-12-07 21:07:36 +00:00
ConstructURLPrefix ( & URLPrefix , INCLUDE_JS , PAGE_PLAYER ) ;
2017-11-11 00:34:47 +00:00
CopyStringToBuffer ( & CollationBuffers - > IncludesPlayer ,
2018-01-08 22:59:36 +00:00
" <script type= \" text/javascript \" src= \" %scinera_player_pre.js \" ></script> " ,
2017-12-07 21:07:36 +00:00
URLPrefix . Location ) ;
2017-08-29 20:35:28 +00:00
if ( HasFilterMenu )
{
2017-11-11 00:34:47 +00:00
CopyBuffer ( & CollationBuffers - > ScriptPlayer , & FilterState ) ;
2017-08-29 20:35:28 +00:00
}
2018-02-23 23:36:42 +00:00
CopyStringToBuffer ( & CollationBuffers - > ScriptPlayer ,
" <script type= \" text/javascript \" src= \" %scinera_player_post.js \" ></script> " ,
URLPrefix . Location ) ;
DeclaimBuffer ( & URLPrefix ) ;
2017-08-29 20:35:28 +00:00
// NOTE(matt): Tree structure of "global" buffer dependencies
// FilterState
// CreditsMenu
// FilterMedia
// FilterTopics
// FilterMenu
// ReferenceMenu
// QuoteMenu
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( & FilterState ) ;
2017-08-29 20:35:28 +00:00
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( & CreditsMenu ) ;
DeclaimBuffer ( & FilterMedia ) ;
DeclaimBuffer ( & FilterTopics ) ;
DeclaimBuffer ( & FilterMenu ) ;
DeclaimBuffer ( & ReferenceMenu ) ;
DeclaimBuffer ( & QuoteMenu ) ;
2017-08-29 20:35:28 +00:00
}
else
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " %s:%d: %s " , Filename , HMML . error . line , HMML . error . message ) ;
2018-01-03 22:16:20 +00:00
fprintf ( stderr , " \ e[1;31mSkipping \ e[0m %s:%d: %s \n " , Filename , HMML . error . line , HMML . error . message ) ;
2017-09-07 21:41:08 +00:00
hmml_free ( & HMML ) ;
return RC_ERROR_HMML ;
2017-08-29 20:35:28 +00:00
}
hmml_free ( & HMML ) ;
2017-09-07 21:41:08 +00:00
return RC_SUCCESS ;
2017-08-29 20:35:28 +00:00
}
2017-09-07 21:41:08 +00:00
int
2018-04-01 20:58:53 +00:00
BuffersToHTML ( buffers * CollationBuffers , template * Template , char * OutputPath , int PageType , unsigned int * PlayerOffset )
2017-08-29 20:35:28 +00:00
{
# if DEBUG
2017-09-07 21:41:08 +00:00
printf ( " \n \n --- Buffer Collation --- \n "
2018-01-03 22:16:20 +00:00
" %s \n \n \n " , OutputPath ? OutputPath : Config . OutLocation ) ;
2017-10-18 21:07:29 +00:00
# endif
# if DEBUG_MEM
FILE * MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
2018-02-23 23:36:42 +00:00
fprintf ( MemLog , " \n Entered BuffersToHTML(%s) \n " , OutputPath ? OutputPath : Config . OutLocation ) ;
2017-10-18 21:07:29 +00:00
fclose ( MemLog ) ;
2017-08-29 20:35:28 +00:00
# endif
2018-01-03 22:16:20 +00:00
if ( Template - > Metadata . Filename & & StringsDiffer ( Template - > Metadata . Filename , " " ) )
2017-08-29 20:35:28 +00:00
{
2018-01-03 22:16:20 +00:00
if ( ( Template - > Metadata . Filename & & StringsDiffer ( Template - > Metadata . Filename , " " ) )
2018-04-01 20:58:53 +00:00
& & ( ( Template - > Metadata . Validity & PageType ) | | Config . Mode & MODE_FORCEINTEGRATION ) )
2017-08-29 20:35:28 +00:00
{
2017-09-07 21:41:08 +00:00
buffer Output ;
2017-10-18 21:07:29 +00:00
Output . Size = Template - > Buffer . Size + ( Kilobytes ( 512 ) ) ;
2017-09-07 21:41:08 +00:00
Output . ID = " Output " ;
if ( ! ( Output . Location = malloc ( Output . Size ) ) )
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " BuffersToHTML(): %s " ,
2017-09-07 21:41:08 +00:00
strerror ( errno ) ) ;
return RC_ERROR_MEMORY ;
}
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
2017-11-11 00:34:47 +00:00
MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
fprintf ( MemLog , " Allocated Output (%d) \n " , Output . Size ) ;
fclose ( MemLog ) ;
printf ( " Allocated Output (%d) \n " , Output . Size ) ;
2017-10-18 21:07:29 +00:00
# endif
2017-09-07 21:41:08 +00:00
Output . Ptr = Output . Location ;
2017-08-29 20:35:28 +00:00
2018-02-21 21:50:23 +00:00
bool NeedPlayerOffset = PlayerOffset ? TRUE : FALSE ;
2017-10-18 21:07:29 +00:00
Template - > Buffer . Ptr = Template - > Buffer . Location ;
for ( int i = 0 ; i < Template - > Metadata . TagCount ; + + i )
2017-06-03 01:32:18 +00:00
{
2017-09-07 21:41:08 +00:00
int j = 0 ;
2017-10-18 21:07:29 +00:00
while ( Template - > Metadata . Tag [ i ] . Offset > j )
2017-06-03 01:32:18 +00:00
{
2017-10-18 21:07:29 +00:00
* Output . Ptr + + = * Template - > Buffer . Ptr + + ;
2017-09-07 21:41:08 +00:00
+ + j ;
}
2017-08-29 20:35:28 +00:00
2017-10-18 21:07:29 +00:00
switch ( Template - > Metadata . Tag [ i ] . TagCode )
2017-09-07 21:41:08 +00:00
{
2017-12-12 23:24:10 +00:00
case TAG_PROJECT :
2018-01-04 20:55:51 +00:00
if ( CollationBuffers - > ProjectName [ 0 ] = = ' \0 ' )
{
fprintf ( stderr , " Template contains a <!-- __CINERA_PROJECT --> tag \n "
" Skipping just this tag, because we do not know the project's full name \n " ) ;
}
else
{
2018-02-23 23:36:42 +00:00
CopyStringToBufferNoFormat ( & Output , CollationBuffers - > ProjectName ) ;
2018-01-04 20:55:51 +00:00
}
2017-12-12 23:24:10 +00:00
break ;
2017-09-07 21:41:08 +00:00
case TAG_TITLE :
2018-02-23 23:36:42 +00:00
CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Title ) ;
2017-09-07 21:41:08 +00:00
break ;
2017-12-12 23:24:10 +00:00
case TAG_URL :
2018-02-21 21:50:23 +00:00
if ( PageType = = PAGE_PLAYER )
{
2018-02-23 23:36:42 +00:00
CopyStringToBufferNoFormat ( & Output , CollationBuffers - > URLPlayer ) ;
2018-02-21 21:50:23 +00:00
}
else
{
2018-02-23 23:36:42 +00:00
CopyStringToBufferNoFormat ( & Output , CollationBuffers - > URLIndex ) ;
2018-02-21 21:50:23 +00:00
}
2017-12-12 23:24:10 +00:00
break ;
case TAG_VIDEO_ID :
2018-02-23 23:36:42 +00:00
CopyStringToBufferNoFormat ( & Output , CollationBuffers - > VideoID ) ;
2017-12-07 01:15:13 +00:00
break ;
2017-09-07 21:41:08 +00:00
case TAG_INDEX :
2018-01-04 20:55:51 +00:00
if ( Config . Edition = = EDITION_SINGLE )
{
fprintf ( stderr , " Template contains a <!-- __CINERA_INDEX --> tag \n "
" Skipping just this tag, because an index cannot be generated for inclusion in a \n "
" bespoke template in Single Edition \n " ) ;
}
else
{
CopyBuffer ( & Output , & CollationBuffers - > Index ) ;
}
2017-09-07 21:41:08 +00:00
break ;
case TAG_INCLUDES :
2018-02-21 21:50:23 +00:00
if ( PageType = = PAGE_PLAYER )
{
CopyBuffer ( & Output , & CollationBuffers - > IncludesPlayer ) ;
}
else
{
CopyBuffer ( & Output , & CollationBuffers - > IncludesIndex ) ;
}
2017-08-29 20:35:28 +00:00
break ;
2017-09-07 21:41:08 +00:00
case TAG_MENUS :
2017-10-18 21:07:29 +00:00
CopyBuffer ( & Output , & CollationBuffers - > Menus ) ;
2017-09-07 21:41:08 +00:00
break ;
case TAG_PLAYER :
2018-04-01 20:58:53 +00:00
if ( NeedPlayerOffset ) { * PlayerOffset + = ( Output . Ptr - Output . Location ) ; NeedPlayerOffset = ! NeedPlayerOffset ; }
2017-10-18 21:07:29 +00:00
CopyBuffer ( & Output , & CollationBuffers - > Player ) ;
2017-08-29 20:35:28 +00:00
break ;
2017-09-07 21:41:08 +00:00
case TAG_SCRIPT :
2017-11-11 00:34:47 +00:00
CopyBuffer ( & Output , & CollationBuffers - > ScriptPlayer ) ;
2017-08-29 20:35:28 +00:00
break ;
2018-02-23 23:36:42 +00:00
case TAG_CUSTOM0 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom0 ) ; break ;
case TAG_CUSTOM1 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom1 ) ; break ;
case TAG_CUSTOM2 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom2 ) ; break ;
case TAG_CUSTOM3 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom3 ) ; break ;
case TAG_CUSTOM4 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom4 ) ; break ;
case TAG_CUSTOM5 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom5 ) ; break ;
case TAG_CUSTOM6 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom6 ) ; break ;
case TAG_CUSTOM7 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom7 ) ; break ;
case TAG_CUSTOM8 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom8 ) ; break ;
case TAG_CUSTOM9 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom9 ) ; break ;
case TAG_CUSTOM10 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom10 ) ; break ;
case TAG_CUSTOM11 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom11 ) ; break ;
case TAG_CUSTOM12 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom12 ) ; break ;
case TAG_CUSTOM13 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom13 ) ; break ;
case TAG_CUSTOM14 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom14 ) ; break ;
case TAG_CUSTOM15 : CopyStringToBufferNoFormat ( & Output , CollationBuffers - > Custom15 ) ; break ;
2017-06-03 01:32:18 +00:00
}
2018-02-21 21:50:23 +00:00
2017-10-18 21:07:29 +00:00
DepartComment ( & Template - > Buffer ) ;
2017-06-03 01:32:18 +00:00
}
2017-10-18 21:07:29 +00:00
while ( Template - > Buffer . Ptr - Template - > Buffer . Location < Template - > Buffer . Size )
2017-06-03 01:32:18 +00:00
{
2017-10-18 21:07:29 +00:00
* Output . Ptr + + = * Template - > Buffer . Ptr + + ;
2017-06-03 01:32:18 +00:00
}
2017-08-29 20:35:28 +00:00
2017-09-07 21:41:08 +00:00
FILE * OutFile ;
if ( ! ( OutFile = fopen ( Config . Edition = = EDITION_PROJECT ? OutputPath : Config . OutIntegratedLocation , " w " ) ) )
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Unable to open output file %s: %s " , Config . Edition = = EDITION_PROJECT ? OutputPath : Config . OutIntegratedLocation , strerror ( errno ) ) ;
2017-09-07 21:41:08 +00:00
free ( Output . Location ) ;
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
2017-11-11 00:34:47 +00:00
MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
fprintf ( MemLog , " Freed Output \n " ) ;
fclose ( MemLog ) ;
printf ( " Freed Output \n " ) ;
2017-10-18 21:07:29 +00:00
# endif
2017-09-07 21:41:08 +00:00
return RC_ERROR_FILE ;
}
fwrite ( Output . Location , Output . Ptr - Output . Location , 1 , OutFile ) ;
fclose ( OutFile ) ;
free ( Output . Location ) ;
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
2017-11-11 00:34:47 +00:00
MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
fprintf ( MemLog , " Freed Output \n " ) ;
fclose ( MemLog ) ;
printf ( " Freed Output \n " ) ;
2017-10-18 21:07:29 +00:00
# endif
2017-09-07 21:41:08 +00:00
return RC_SUCCESS ;
}
else
2017-08-29 20:35:28 +00:00
{
2017-09-07 21:41:08 +00:00
return RC_INVALID_TEMPLATE ;
2017-08-29 20:35:28 +00:00
}
}
else
{
2017-09-07 21:41:08 +00:00
buffer Master ;
2017-10-18 21:07:29 +00:00
if ( ClaimBuffer ( & Master , " Master " , Kilobytes ( 512 ) ) = = RC_ARENA_FULL ) { return RC_ARENA_FULL ; } ;
2017-09-07 21:41:08 +00:00
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Master ,
" <html> \n "
2018-01-08 22:59:36 +00:00
" <head> \n "
" " ) ;
2017-08-29 20:35:28 +00:00
2017-10-18 21:07:29 +00:00
CopyBuffer ( & Master , PageType = = PAGE_PLAYER ? & CollationBuffers - > IncludesPlayer : & CollationBuffers - > IncludesIndex ) ;
2017-09-07 21:41:08 +00:00
CopyStringToBuffer ( & Master , " \n " ) ;
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Master ,
" </head> \n "
2018-01-08 22:59:36 +00:00
" <body> \n "
" " ) ;
2017-09-07 21:41:08 +00:00
if ( PageType = = PAGE_PLAYER )
{
2018-01-17 20:15:00 +00:00
CopyStringToBuffer ( & Master , " <div> \n "
2018-01-15 21:52:24 +00:00
" " ) ;
2017-10-18 21:07:29 +00:00
CopyBuffer ( & Master , & CollationBuffers - > Menus ) ;
2018-01-08 22:59:36 +00:00
CopyStringToBuffer ( & Master , " \n "
2018-01-15 21:52:24 +00:00
" " ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
if ( PlayerOffset ) { * PlayerOffset + = Master . Ptr - Master . Location ; }
2018-02-21 21:50:23 +00:00
2017-10-18 21:07:29 +00:00
CopyBuffer ( & Master , & CollationBuffers - > Player ) ;
2018-01-08 22:59:36 +00:00
CopyStringToBuffer ( & Master , " \n "
2018-01-15 21:52:24 +00:00
" " ) ;
CopyStringToBuffer ( & Master , " </div> \n "
2018-01-08 22:59:36 +00:00
" " ) ;
2017-11-11 00:34:47 +00:00
CopyBuffer ( & Master , & CollationBuffers - > ScriptPlayer ) ;
2017-09-07 21:41:08 +00:00
CopyStringToBuffer ( & Master , " \n " ) ;
}
else
{
2017-10-18 21:07:29 +00:00
CopyBuffer ( & Master , & CollationBuffers - > Index ) ;
2017-09-07 21:41:08 +00:00
}
2017-08-29 20:35:28 +00:00
CopyStringToBuffer ( & Master ,
" </body> \n "
" </html> \n " ) ;
FILE * OutFile ;
if ( ! ( OutFile = fopen ( Config . Edition = = EDITION_PROJECT ? OutputPath : Config . OutLocation , " w " ) ) )
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Unable to open output file %s: %s " , Config . Edition = = EDITION_PROJECT ? OutputPath : Config . OutLocation , strerror ( errno ) ) ;
DeclaimBuffer ( & Master ) ;
2017-09-07 21:41:08 +00:00
return RC_ERROR_FILE ;
2017-08-29 20:35:28 +00:00
}
fwrite ( Master . Location , Master . Ptr - Master . Location , 1 , OutFile ) ;
fclose ( OutFile ) ;
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( & Master ) ;
2017-09-07 21:41:08 +00:00
return RC_SUCCESS ;
2017-08-29 20:35:28 +00:00
}
}
2018-02-21 21:50:23 +00:00
int
2018-04-01 20:58:53 +00:00
BinarySearchForMetadataEntry ( index * Index , index_metadata * * Entry , char * SearchTerm )
2018-03-06 20:40:12 +00:00
{
int Lower = 0 ;
index_metadata * LowerEntry = ( index_metadata * ) ( Index - > Metadata . Buffer . Ptr + sizeof ( index_metadata ) * Lower ) ;
2018-04-01 20:58:53 +00:00
if ( StringsDiffer ( SearchTerm , LowerEntry - > BaseFilename ) < 0 ) { return - 1 ; }
2018-03-06 20:40:12 +00:00
int Upper = Index - > Header . EntryCount - 1 ;
int Pivot = Upper - ( ( Upper - Lower ) > > 1 ) ;
index_metadata * UpperEntry ;
index_metadata * PivotEntry ;
do {
LowerEntry = ( index_metadata * ) ( Index - > Metadata . Buffer . Ptr + sizeof ( index_metadata ) * Lower ) ;
PivotEntry = ( index_metadata * ) ( Index - > Metadata . Buffer . Ptr + sizeof ( index_metadata ) * Pivot ) ;
UpperEntry = ( index_metadata * ) ( Index - > Metadata . Buffer . Ptr + sizeof ( index_metadata ) * Upper ) ;
2018-04-01 20:58:53 +00:00
if ( ! StringsDiffer ( SearchTerm , LowerEntry - > BaseFilename ) ) { * Entry = LowerEntry ; return Lower ; }
if ( ! StringsDiffer ( SearchTerm , PivotEntry - > BaseFilename ) ) { * Entry = PivotEntry ; return Pivot ; }
if ( ! StringsDiffer ( SearchTerm , UpperEntry - > BaseFilename ) ) { * Entry = UpperEntry ; return Upper ; }
2018-03-06 20:40:12 +00:00
if ( ( StringsDiffer ( SearchTerm , PivotEntry - > BaseFilename ) < 0 ) ) { Upper = Pivot ; }
else { Lower = Pivot ; }
Pivot = Upper - ( ( Upper - Lower ) > > 1 ) ;
} while ( Upper > Pivot ) ;
return Upper ;
}
int
AccumulateIndexEntryInsertionOffset ( index * Index , int EntryIndex )
{
int Result = 0 ;
index_metadata * AccEntry = { 0 } ;
if ( EntryIndex < Index - > Header . EntryCount > > 1 )
{
for ( ; EntryIndex > 0 ; - - EntryIndex )
{
AccEntry = ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * ( EntryIndex - 1 ) ) ;
Result + = AccEntry - > Size ;
}
Result + = StringLength ( " --- \n " ) ;
}
else
{
for ( ; EntryIndex < Index - > Header . EntryCount ; + + EntryIndex )
{
AccEntry = ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * EntryIndex ) ;
Result + = AccEntry - > Size ;
}
Result = Index - > File . FileSize - Result ;
}
return Result ;
}
2018-04-01 20:58:53 +00:00
void
ClearEntry ( index_metadata * Entry )
2017-11-11 00:34:47 +00:00
{
2018-04-01 20:58:53 +00:00
Entry - > LinkOffsets . PrevStart = 0 ;
Entry - > LinkOffsets . NextStart = 0 ;
Entry - > LinkOffsets . PrevEnd = 0 ;
Entry - > LinkOffsets . NextEnd = 0 ;
Entry - > Size = 0 ;
Clear ( Entry - > BaseFilename , sizeof ( Entry - > BaseFilename ) ) ;
Clear ( Entry - > Title , sizeof ( Entry - > Title ) ) ;
}
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
enum
{
EDIT_DELETION ,
EDIT_ADDITION ,
EDIT_REINSERTION
} index_edits ;
2017-10-18 21:07:29 +00:00
2018-04-01 20:58:53 +00:00
void
GetNeighbourhood ( index * Index , neighbourhood * N , int IndexEditType , bool * ThisIsPrev )
{
index_metadata Entry = { 0 } ;
int EntryIndex ;
int IncomingIndex = N - > ThisIndex ;
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
if ( IndexEditType = = EDIT_DELETION )
2017-11-11 00:34:47 +00:00
{
2018-04-01 20:58:53 +00:00
bool FoundThis = FALSE ;
for ( EntryIndex = IncomingIndex + 1 ; EntryIndex < Index - > Header . EntryCount ; + + EntryIndex )
{
Entry = * ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * EntryIndex ) ;
if ( Entry . Size > 0 )
{
FoundThis = TRUE ;
break ;
}
}
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
if ( ! FoundThis )
{
for ( EntryIndex = IncomingIndex - 1 ; EntryIndex > = 0 ; - - EntryIndex )
{
Entry = * ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * EntryIndex ) ;
if ( Entry . Size > 0 )
{
FoundThis = TRUE ;
* ThisIsPrev = TRUE ;
break ;
}
}
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
if ( FoundThis )
{
N - > ThisIndex = EntryIndex ;
N - > This . Size = Entry . Size ;
N - > This . LinkOffsets . PrevStart = Entry . LinkOffsets . PrevStart ;
N - > This . LinkOffsets . PrevEnd = Entry . LinkOffsets . PrevEnd ;
N - > This . LinkOffsets . NextStart = Entry . LinkOffsets . NextStart ;
N - > This . LinkOffsets . NextEnd = Entry . LinkOffsets . NextEnd ;
CopyString ( N - > This . BaseFilename , Entry . BaseFilename ) ;
CopyString ( N - > This . Title , Entry . Title ) ;
}
else
{
return ; // NOTE(matt): We were evidently the last public entry, until now
}
}
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
N - > PrevIsFirst = TRUE ;
N - > NextIsFinal = TRUE ;
if ( IndexEditType = = EDIT_DELETION & & * ThisIsPrev = = FALSE )
{
EntryIndex = IncomingIndex - 1 ;
}
else
{
EntryIndex = N - > ThisIndex - 1 ;
}
bool FoundPrev = FALSE ;
for ( ; EntryIndex > = 0 ; - - EntryIndex )
{
Entry = * ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * EntryIndex ) ;
if ( Entry . Size > 0 )
2018-03-06 20:40:12 +00:00
{
2018-04-01 20:58:53 +00:00
if ( ! FoundPrev )
2018-03-06 20:40:12 +00:00
{
2018-04-01 20:58:53 +00:00
N - > PrevIndex = EntryIndex ;
N - > Prev . Size = Entry . Size ;
N - > Prev . LinkOffsets . PrevStart = Entry . LinkOffsets . PrevStart ;
N - > Prev . LinkOffsets . PrevEnd = Entry . LinkOffsets . PrevEnd ;
N - > Prev . LinkOffsets . NextStart = Entry . LinkOffsets . NextStart ;
N - > Prev . LinkOffsets . NextEnd = Entry . LinkOffsets . NextEnd ;
CopyString ( N - > Prev . BaseFilename , Entry . BaseFilename ) ;
CopyString ( N - > Prev . Title , Entry . Title ) ;
FoundPrev = TRUE ;
2018-03-06 20:40:12 +00:00
}
2018-04-01 20:58:53 +00:00
else
2018-03-06 20:40:12 +00:00
{
2018-04-01 20:58:53 +00:00
N - > PrevIsFirst = FALSE ;
break ;
2018-03-06 20:40:12 +00:00
}
}
2018-04-01 20:58:53 +00:00
}
switch ( IndexEditType )
{
case EDIT_DELETION :
if ( * ThisIsPrev = = TRUE ) { EntryIndex = Index - > Header . EntryCount ; break ; } // NOTE(matt): No need to enter the loop, else fallthrough
case EDIT_REINSERTION :
EntryIndex = N - > ThisIndex + 1 ;
break ;
case EDIT_ADDITION :
EntryIndex = N - > ThisIndex ;
break ;
}
bool FoundNext = FALSE ;
for ( ; EntryIndex < Index - > Header . EntryCount ;
+ + EntryIndex )
{
Entry = * ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * EntryIndex ) ;
if ( Entry . Size > 0 )
2018-03-06 20:40:12 +00:00
{
2018-04-01 20:58:53 +00:00
if ( ! FoundNext )
2018-03-06 20:40:12 +00:00
{
2018-04-01 20:58:53 +00:00
N - > NextIndex = EntryIndex ;
N - > Next . Size = Entry . Size ;
N - > Next . LinkOffsets . PrevStart = Entry . LinkOffsets . PrevStart ;
N - > Next . LinkOffsets . PrevEnd = Entry . LinkOffsets . PrevEnd ;
N - > Next . LinkOffsets . NextStart = Entry . LinkOffsets . NextStart ;
N - > Next . LinkOffsets . NextEnd = Entry . LinkOffsets . NextEnd ;
CopyString ( N - > Next . BaseFilename , Entry . BaseFilename ) ;
CopyString ( N - > Next . Title , Entry . Title ) ;
FoundNext = TRUE ;
2018-03-06 20:40:12 +00:00
}
else
{
2018-04-01 20:58:53 +00:00
N - > NextIsFinal = FALSE ;
break ;
2018-03-06 20:40:12 +00:00
}
}
2018-04-01 20:58:53 +00:00
}
switch ( IndexEditType )
{
case EDIT_REINSERTION :
break ;
case EDIT_DELETION :
if ( * ThisIsPrev = = FALSE ) { - - N - > ThisIndex ; }
if ( FoundNext ) { - - N - > NextIndex ; }
break ;
case EDIT_ADDITION :
if ( FoundNext ) { + + N - > NextIndex ; }
break ;
}
}
int
InsertIntoIndex ( index * Index , neighbourhood * N , buffers * CollationBuffers , template * * BespokeTemplate , char * BaseFilename , bool RecheckingPrivacy )
{
int IndexMetadataFileReadCode = ReadFileIntoBuffer ( & Index - > Metadata , 0 ) ;
switch ( IndexMetadataFileReadCode )
{
case RC_ERROR_MEMORY :
return RC_ERROR_MEMORY ;
case RC_ERROR_FILE :
case RC_SUCCESS :
break ;
}
int IndexFileReadCode = ReadFileIntoBuffer ( & Index - > File , 0 ) ;
switch ( IndexFileReadCode )
{
case RC_ERROR_MEMORY :
return RC_ERROR_MEMORY ;
case RC_ERROR_FILE :
case RC_SUCCESS :
break ;
}
int IndexEntryInsertionStart = - 1 ;
int IndexEntryInsertionEnd = - 1 ;
Index - > Header . EntryCount = 0 ;
ClearEntry ( & Index - > Entry ) ;
bool Reinserting = FALSE ;
if ( IndexMetadataFileReadCode = = RC_SUCCESS & & IndexFileReadCode = = RC_SUCCESS )
{
// TODO(matt): Index validation?
// Maybe at least if(!StringsDiffer(..., "name: \"");
// and check that we won't crash through the end of the file when skipping to the next entry
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location ;
Index - > Header = * ( index_header * ) Index - > Metadata . Buffer . Ptr ;
Index - > Header . CurrentDBVersion = CINERA_DB_VERSION ;
Index - > Header . CurrentAppVersion = CINERA_APP_VERSION ;
Index - > Header . CurrentHMMLVersion . Major = hmml_version . Major ;
Index - > Header . CurrentHMMLVersion . Minor = hmml_version . Minor ;
Index - > Header . CurrentHMMLVersion . Patch = hmml_version . Patch ;
Index - > Metadata . Buffer . Ptr + = sizeof ( index_header ) ;
Index - > File . Buffer . Ptr + = StringLength ( " --- \n " ) ;
index_metadata * Entry = { 0 } ;
N - > ThisIndex = BinarySearchForMetadataEntry ( Index , & Entry , BaseFilename ) ;
if ( Entry )
2017-09-07 21:41:08 +00:00
{
2018-04-01 20:58:53 +00:00
// Reinsert
Reinserting = TRUE ;
N - > This . Size = Entry - > Size ;
N - > This . LinkOffsets . PrevStart = Entry - > LinkOffsets . PrevStart ;
N - > This . LinkOffsets . PrevEnd = Entry - > LinkOffsets . PrevEnd ;
N - > This . LinkOffsets . NextStart = Entry - > LinkOffsets . NextStart ;
N - > This . LinkOffsets . NextEnd = Entry - > LinkOffsets . NextEnd ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
IndexEntryInsertionStart = AccumulateIndexEntryInsertionOffset ( Index , N - > ThisIndex ) ;
IndexEntryInsertionEnd = IndexEntryInsertionStart + N - > This . Size ;
}
else
{
if ( N - > ThisIndex = = - 1 ) { + + N - > ThisIndex ; } // NOTE(matt): BinarySearchForMetadataEntry returns -1 if search term precedes the set
Entry = ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > ThisIndex ) ;
if ( StringsDiffer ( BaseFilename , Entry - > BaseFilename ) < 0 )
2017-09-07 21:41:08 +00:00
{
2017-11-11 00:34:47 +00:00
// Insert
2018-04-01 20:58:53 +00:00
IndexEntryInsertionStart = AccumulateIndexEntryInsertionOffset ( Index , N - > ThisIndex ) ;
2018-02-21 21:50:23 +00:00
2017-09-07 21:41:08 +00:00
}
2017-10-18 21:07:29 +00:00
else
2017-09-07 21:41:08 +00:00
{
2018-04-01 20:58:53 +00:00
// Append
+ + N - > ThisIndex ;
2017-09-07 21:41:08 +00:00
}
2017-10-18 21:07:29 +00:00
}
2018-04-01 20:58:53 +00:00
GetNeighbourhood ( Index , N , Reinserting ? EDIT_REINSERTION : EDIT_ADDITION , 0 ) ;
2017-10-18 21:07:29 +00:00
}
2017-11-11 00:34:47 +00:00
else
2017-10-18 21:07:29 +00:00
{
2017-11-11 00:34:47 +00:00
// NOTE(matt): Initialising new index_header
2018-02-21 21:50:23 +00:00
Index - > Header . CurrentDBVersion = CINERA_DB_VERSION ;
Index - > Header . CurrentAppVersion = CINERA_APP_VERSION ;
Index - > Header . CurrentHMMLVersion . Major = hmml_version . Major ;
Index - > Header . CurrentHMMLVersion . Minor = hmml_version . Minor ;
Index - > Header . CurrentHMMLVersion . Patch = hmml_version . Patch ;
Index - > Header . InitialDBVersion = CINERA_DB_VERSION ;
Index - > Header . InitialAppVersion = CINERA_APP_VERSION ;
Index - > Header . InitialHMMLVersion . Major = hmml_version . Major ;
Index - > Header . InitialHMMLVersion . Minor = hmml_version . Minor ;
Index - > Header . InitialHMMLVersion . Patch = hmml_version . Patch ;
CopyStringNoFormat ( Index - > Header . ProjectID , Config . ProjectID ) ;
for ( int ProjectIndex = 0 ; ProjectIndex < ArrayCount ( ProjectInfo ) ; + + ProjectIndex )
{
if ( ! StringsDiffer ( ProjectInfo [ ProjectIndex ] . ProjectID , Config . ProjectID ) )
{
CopyStringNoFormat ( Index - > Header . ProjectName , ProjectInfo [ ProjectIndex ] . FullName ) ;
break ;
}
}
CopyStringNoFormat ( Index - > Header . BaseURL , Config . BaseURL ) ;
CopyStringNoFormat ( Index - > Header . IndexLocation , Config . IndexLocation ) ;
CopyStringNoFormat ( Index - > Header . PlayerLocation , Config . PlayerLocation ) ;
CopyStringNoFormat ( Index - > Header . PlayerURLPrefix , Config . PlayerURLPrefix ) ;
2017-12-12 23:24:10 +00:00
2017-11-11 00:34:47 +00:00
DIR * OutputDirectoryHandle ;
if ( ! ( OutputDirectoryHandle = opendir ( Config . BaseDir ) ) )
2017-10-18 21:07:29 +00:00
{
2017-11-11 00:34:47 +00:00
if ( MakeDir ( Config . BaseDir ) = = RC_ERROR_DIRECTORY )
{
LogError ( LOG_ERROR , " Unable to create directory %s: %s " , Config . BaseDir , strerror ( errno ) ) ;
fprintf ( stderr , " Unable to create directory %s: %s \n " , Config . BaseDir , strerror ( errno ) ) ;
return RC_ERROR_DIRECTORY ;
} ;
2017-10-18 21:07:29 +00:00
}
2017-11-11 00:34:47 +00:00
closedir ( OutputDirectoryHandle ) ;
2017-10-18 21:07:29 +00:00
}
2017-09-07 21:41:08 +00:00
2018-02-21 21:50:23 +00:00
char InputFile [ StringLength ( BaseFilename ) + StringLength ( " .hmml " ) ] ;
CopyString ( InputFile , " %s.hmml " , BaseFilename ) ;
2018-04-01 20:58:53 +00:00
bool VideoIsPrivate = FALSE ;
switch ( HMMLToBuffers ( CollationBuffers , BespokeTemplate , InputFile , N ) )
2018-02-21 21:50:23 +00:00
{
// TODO(matt): Actually sort out the fatality of these cases, once we are always-on
case RC_ERROR_FILE :
case RC_ERROR_FATAL :
return RC_ERROR_FATAL ;
case RC_ERROR_HMML :
case RC_ERROR_MAX_REFS :
case RC_ERROR_QUOTE :
case RC_INVALID_REFERENCE :
return RC_ERROR_HMML ;
2018-04-01 20:58:53 +00:00
case RC_PRIVATE_VIDEO :
VideoIsPrivate = TRUE ;
2018-02-21 21:50:23 +00:00
case RC_SUCCESS :
break ;
} ;
2017-09-07 21:41:08 +00:00
2018-04-01 20:58:53 +00:00
ClearCopyStringNoFormat ( N - > This . BaseFilename , sizeof ( N - > This . BaseFilename ) , BaseFilename ) ;
if ( N - > This . Size > 0 )
{
ClearCopyStringNoFormat ( N - > This . Title , sizeof ( N - > This . Title ) , CollationBuffers - > Title ) ;
}
2017-09-07 21:41:08 +00:00
2018-02-21 21:50:23 +00:00
if ( ! ( Index - > Metadata . Handle = fopen ( Index - > Metadata . Path , " w " ) ) ) { FreeBuffer ( & Index - > Metadata . Buffer ) ; return RC_ERROR_FILE ; }
if ( ! ( Index - > File . Handle = fopen ( Index - > File . Path , " w " ) ) ) { FreeBuffer ( & Index - > File . Buffer ) ; return RC_ERROR_FILE ; }
2018-04-01 20:58:53 +00:00
if ( ! Reinserting ) { + + Index - > Header . EntryCount ; }
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
fwrite ( & Index - > Header , sizeof ( index_header ) , 1 , Index - > Metadata . Handle ) ;
2017-11-11 00:34:47 +00:00
if ( IndexMetadataFileReadCode = = RC_SUCCESS )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) ;
2017-10-18 21:07:29 +00:00
}
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
if ( Reinserting )
2017-10-18 21:07:29 +00:00
{
2017-11-11 00:34:47 +00:00
// NOTE(matt): We hit this during the start-up sync and when copying in a .hmml file over an already existing one, but
// would need to fool about with the inotify event processing to get to this branch in the case that saving
// a file triggers an IN_DELETE followed by an IN_CLOSE_WRITE event
2018-04-01 20:58:53 +00:00
//
// We will almost definitely need to handle our inotify events differently once we are outputting the player
// pages of private videos to "secret locations", because we will need to preserve the randomly generated
// location stored in the .metadata
2017-11-11 00:34:47 +00:00
// Reinsert
2018-04-01 20:58:53 +00:00
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > ThisIndex ;
* ( index_metadata * ) Index - > Metadata . Buffer . Ptr = N - > This ;
fwrite ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) , Index - > Metadata . FileSize - sizeof ( index_header ) , 1 , Index - > Metadata . Handle ) ;
2017-11-11 00:34:47 +00:00
2018-02-21 21:50:23 +00:00
fwrite ( Index - > File . Buffer . Location , IndexEntryInsertionStart , 1 , Index - > File . Handle ) ;
2018-04-01 20:58:53 +00:00
fwrite ( CollationBuffers - > Search . Location , N - > This . Size , 1 , Index - > File . Handle ) ;
2018-02-21 21:50:23 +00:00
fwrite ( Index - > File . Buffer . Location + IndexEntryInsertionEnd , Index - > File . FileSize - IndexEntryInsertionEnd , 1 , Index - > File . Handle ) ;
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
if ( VideoIsPrivate )
{
if ( ! RecheckingPrivacy )
{
LogError ( LOG_NOTICE , " Privately Reinserted %s " , BaseFilename ) ;
fprintf ( stderr , " \ e[0;34mPrivately Reinserted \ e[0m %s \n " , BaseFilename ) ;
}
}
else
{
LogError ( LOG_NOTICE , " Reinserted %s - %s " , BaseFilename , CollationBuffers - > Title ) ;
fprintf ( stderr , " \ e[1;33mReinserted \ e[0m %s - %s \n " , BaseFilename , CollationBuffers - > Title ) ;
}
2017-11-11 00:34:47 +00:00
}
2018-04-01 20:58:53 +00:00
else if ( IndexEntryInsertionStart > = 0 )
2017-11-11 00:34:47 +00:00
{
// Insert new
2018-04-01 20:58:53 +00:00
fwrite ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) , sizeof ( index_metadata ) * N - > ThisIndex , 1 , Index - > Metadata . Handle ) ;
fwrite ( & N - > This , sizeof ( index_metadata ) , 1 , Index - > Metadata . Handle ) ;
fwrite ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > ThisIndex ,
Index - > Metadata . FileSize - sizeof ( index_header ) - sizeof ( index_metadata ) * N - > ThisIndex ,
1 , Index - > Metadata . Handle ) ;
2017-11-11 00:34:47 +00:00
2018-02-21 21:50:23 +00:00
fwrite ( Index - > File . Buffer . Location , IndexEntryInsertionStart , 1 , Index - > File . Handle ) ;
2018-04-01 20:58:53 +00:00
fwrite ( CollationBuffers - > Search . Location , N - > This . Size , 1 , Index - > File . Handle ) ;
2018-02-21 21:50:23 +00:00
fwrite ( Index - > File . Buffer . Location + IndexEntryInsertionStart , Index - > File . FileSize - IndexEntryInsertionStart , 1 , Index - > File . Handle ) ;
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
if ( VideoIsPrivate )
{
if ( ! RecheckingPrivacy )
{
LogError ( LOG_NOTICE , " Privately Inserted %s " , BaseFilename ) ;
fprintf ( stderr , " \ e[0;34mPrivately Inserted \ e[0m %s \n " , BaseFilename ) ;
}
}
else
{
LogError ( LOG_NOTICE , " Inserted %s - %s " , BaseFilename , CollationBuffers - > Title ) ;
fprintf ( stderr , " \ e[1;32mInserted \ e[0m %s - %s \n " , BaseFilename , CollationBuffers - > Title ) ;
}
2017-10-18 21:07:29 +00:00
}
else
{
2017-11-11 00:34:47 +00:00
// Append new
if ( IndexMetadataFileReadCode = = RC_SUCCESS )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
fwrite ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) , Index - > Metadata . FileSize - sizeof ( index_header ) , 1 , Index - > Metadata . Handle ) ;
2018-02-21 21:50:23 +00:00
fwrite ( Index - > File . Buffer . Location , Index - > File . FileSize , 1 , Index - > File . Handle ) ;
2017-09-07 21:41:08 +00:00
}
2017-11-11 00:34:47 +00:00
else
{
2018-02-21 21:50:23 +00:00
fprintf ( Index - > File . Handle , " --- \n " ) ;
2017-11-11 00:34:47 +00:00
}
2018-04-01 20:58:53 +00:00
fwrite ( & N - > This , sizeof ( index_metadata ) , 1 , Index - > Metadata . Handle ) ;
fwrite ( CollationBuffers - > Search . Location , N - > This . Size , 1 , Index - > File . Handle ) ;
if ( VideoIsPrivate )
{
if ( ! RecheckingPrivacy )
{
LogError ( LOG_NOTICE , " Privately Appended %s " , BaseFilename ) ;
fprintf ( stderr , " \ e[0;34mPrivately Appended \ e[0m %s \n " , BaseFilename ) ;
}
}
else
{
LogError ( LOG_NOTICE , " Appended %s - %s " , BaseFilename , CollationBuffers - > Title ) ;
fprintf ( stderr , " \ e[1;32mAppended \ e[0m %s - %s \n " , BaseFilename , CollationBuffers - > Title ) ;
}
2017-10-18 21:07:29 +00:00
}
2018-02-21 21:50:23 +00:00
fclose ( Index - > Metadata . Handle ) ;
fclose ( Index - > File . Handle ) ;
2018-04-01 20:58:53 +00:00
2018-02-21 21:50:23 +00:00
FreeBuffer ( & Index - > Metadata . Buffer ) ;
FreeBuffer ( & Index - > File . Buffer ) ;
2018-04-01 20:58:53 +00:00
// TODO(matt): Remove VideoIsPrivate in favour of generating a player page in a random location
return VideoIsPrivate ? RC_PRIVATE_VIDEO : Reinserting ? RC_SUCCESS : RC_UNFOUND ;
2017-10-18 21:07:29 +00:00
}
2017-12-12 23:24:10 +00:00
void
ConstructDirectoryPath ( buffer * DirectoryPath , int PageType , char * PageLocation , char * BaseFilename )
{
RewindBuffer ( DirectoryPath ) ;
CopyStringToBuffer ( DirectoryPath , Config . BaseDir ) ;
switch ( PageType )
{
case PAGE_INDEX :
if ( StringsDiffer ( PageLocation , " " ) )
{
CopyStringToBuffer ( DirectoryPath , " /%s " , PageLocation ) ;
}
break ;
case PAGE_PLAYER :
if ( StringsDiffer ( PageLocation , " " ) )
{
CopyStringToBuffer ( DirectoryPath , " /%s " , PageLocation ) ;
}
2018-04-01 20:58:53 +00:00
if ( BaseFilename )
2017-12-12 23:24:10 +00:00
{
2018-04-01 20:58:53 +00:00
if ( StringsDiffer ( Config . PlayerURLPrefix , " " ) )
{
char * Ptr = BaseFilename + StringLength ( Config . ProjectID ) ;
CopyStringToBuffer ( DirectoryPath , " /%s%s " , Config . PlayerURLPrefix , Ptr ) ;
}
else
{
CopyStringToBuffer ( DirectoryPath , " /%s " , BaseFilename ) ;
}
2017-12-12 23:24:10 +00:00
}
break ;
}
}
2018-02-21 21:50:23 +00:00
enum
{
LINK_INCLUDE ,
LINK_EXCLUDE
} link_types ;
enum
{
LINK_PREV ,
LINK_NEXT
} link_directions ;
int InsertNeighbourLink ( file_buffer * FromFile , index_metadata * From , index_metadata * To , int LinkDirection , char * ProjectName , bool FromHasOneNeighbour )
{
if ( ReadFileIntoBuffer ( FromFile , 0 ) = = RC_SUCCESS )
{
if ( ! ( FromFile - > Handle = fopen ( FromFile - > Path , " w " ) ) ) { FreeBuffer ( & FromFile - > Buffer ) ; return RC_ERROR_FILE ; } ;
buffer Link ;
ClaimBuffer ( & Link , " Link " , 4096 ) ;
buffer ToPlayerURL ;
if ( To )
{
ClaimBuffer ( & ToPlayerURL , " ToPlayerURL " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH ) ;
ConstructPlayerURL ( & ToPlayerURL , To - > BaseFilename ) ;
}
switch ( LinkDirection )
{
case LINK_PREV :
{
int NewPrevEnd = 0 ;
int NewNextEnd = 0 ;
fwrite ( FromFile - > Buffer . Location , From - > LinkOffsets . PrevStart , 1 , FromFile - > Handle ) ;
if ( To )
{
CopyStringToBuffer ( & Link ,
" <a class= \" episodeMarker prev \" href= \" %s \" ><div>⏫</div><div>Previous: '%s'</div><div>⏫</div></a> \n " ,
ToPlayerURL . Location ,
To - > Title ) ;
}
else
{
CopyStringToBuffer ( & Link ,
" <div class= \" episodeMarker first \" ><div>•</div><div>Welcome to <cite>%s</cite></div><div>•</div></div> \n " , ProjectName ) ;
}
NewPrevEnd = Link . Ptr - Link . Location ;
fwrite ( Link . Location , ( Link . Ptr - Link . Location ) , 1 , FromFile - > Handle ) ;
if ( FromHasOneNeighbour )
{
fwrite ( FromFile - > Buffer . Location + From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd , From - > LinkOffsets . NextStart , 1 , FromFile - > Handle ) ;
RewindBuffer ( & Link ) ;
CopyStringToBuffer ( & Link ,
" <div class= \" episodeMarker last \" ><div>•</div><div>You have arrived at the (current) end of <cite>%s</cite></div><div>•</div></div> \n " , ProjectName ) ;
NewNextEnd = Link . Ptr - Link . Location ;
fwrite ( Link . Location , NewNextEnd , 1 , FromFile - > Handle ) ;
fwrite ( FromFile - > Buffer . Location + From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd + From - > LinkOffsets . NextStart + From - > LinkOffsets . NextEnd ,
FromFile - > FileSize - ( From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd + From - > LinkOffsets . NextStart + From - > LinkOffsets . NextEnd ) ,
1 ,
FromFile - > Handle ) ;
}
else
{
fwrite ( FromFile - > Buffer . Location + From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd ,
FromFile - > FileSize - ( From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd ) ,
1 ,
FromFile - > Handle ) ;
}
From - > LinkOffsets . PrevEnd = NewPrevEnd ;
if ( FromHasOneNeighbour ) { From - > LinkOffsets . NextEnd = NewNextEnd ; }
} break ;
case LINK_NEXT :
{
int NewPrevEnd = 0 ;
int NewNextEnd = 0 ;
if ( FromHasOneNeighbour )
{
fwrite ( FromFile - > Buffer . Location , From - > LinkOffsets . PrevStart , 1 , FromFile - > Handle ) ;
CopyStringToBuffer ( & Link ,
" <div class= \" episodeMarker first \" ><div>•</div><div>Welcome to <cite>%s</cite></div><div>•</div></div> \n " , ProjectName ) ;
NewPrevEnd = Link . Ptr - Link . Location ;
fwrite ( Link . Location , NewPrevEnd , 1 , FromFile - > Handle ) ;
RewindBuffer ( & Link ) ;
fwrite ( FromFile - > Buffer . Location + From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd ,
From - > LinkOffsets . NextStart , 1 , FromFile - > Handle ) ;
}
else
{
fwrite ( FromFile - > Buffer . Location , From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd + From - > LinkOffsets . NextStart , 1 , FromFile - > Handle ) ;
}
if ( To )
{
CopyStringToBuffer ( & Link ,
" <a class= \" episodeMarker next \" href= \" %s \" ><div>⏬</div><div>Next: '%s'</div><div>⏬</div></a> \n " ,
ToPlayerURL . Location ,
To - > Title ) ;
}
else
{
CopyStringToBuffer ( & Link ,
" <div class= \" episodeMarker last \" ><div>•</div><div>You have arrived at the (current) end of <cite>%s</cite></div><div>•</div></div> \n " , ProjectName ) ;
}
NewNextEnd = Link . Ptr - Link . Location ;
fwrite ( Link . Location , ( Link . Ptr - Link . Location ) , 1 , FromFile - > Handle ) ;
fwrite ( FromFile - > Buffer . Location + From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd + From - > LinkOffsets . NextStart + From - > LinkOffsets . NextEnd ,
FromFile - > FileSize - ( From - > LinkOffsets . PrevStart + From - > LinkOffsets . PrevEnd + From - > LinkOffsets . NextStart + From - > LinkOffsets . NextEnd ) ,
1 ,
FromFile - > Handle ) ;
if ( FromHasOneNeighbour ) { From - > LinkOffsets . PrevEnd = NewPrevEnd ; }
From - > LinkOffsets . NextEnd = NewNextEnd ;
} break ;
}
if ( To ) { DeclaimBuffer ( & ToPlayerURL ) ; }
DeclaimBuffer ( & Link ) ;
fclose ( FromFile - > Handle ) ;
FreeBuffer ( & FromFile - > Buffer ) ;
return RC_SUCCESS ;
}
else
{
return RC_ERROR_FILE ;
}
}
int DeleteNeighbourLinks ( file_buffer * File , index_metadata * Metadata )
{
if ( ReadFileIntoBuffer ( File , 0 ) = = RC_SUCCESS )
{
if ( ! ( File - > Handle = fopen ( File - > Path , " w " ) ) ) { FreeBuffer ( & File - > Buffer ) ; return RC_ERROR_FILE ; } ;
fwrite ( File - > Buffer . Location , Metadata - > LinkOffsets . PrevStart , 1 , File - > Handle ) ;
fwrite ( File - > Buffer . Location + Metadata - > LinkOffsets . PrevStart + Metadata - > LinkOffsets . PrevEnd , Metadata - > LinkOffsets . NextStart , 1 , File - > Handle ) ;
fwrite ( File - > Buffer . Location + Metadata - > LinkOffsets . PrevStart + Metadata - > LinkOffsets . PrevEnd + Metadata - > LinkOffsets . NextStart + Metadata - > LinkOffsets . NextEnd ,
File - > FileSize - ( Metadata - > LinkOffsets . PrevStart + Metadata - > LinkOffsets . PrevEnd + Metadata - > LinkOffsets . NextStart + Metadata - > LinkOffsets . NextEnd ) ,
1 ,
File - > Handle ) ;
fclose ( File - > Handle ) ;
Metadata - > LinkOffsets . PrevEnd = 0 ;
Metadata - > LinkOffsets . NextEnd = 0 ;
FreeBuffer ( & File - > Buffer ) ;
return RC_SUCCESS ;
}
2018-04-01 20:58:53 +00:00
else { return RC_ERROR_FILE ; }
2018-02-21 21:50:23 +00:00
}
2018-04-01 20:58:53 +00:00
int LinkNeighbours ( index * Index , neighbourhood * N , char * BaseFilename , int LinkType )
2018-02-21 21:50:23 +00:00
{
switch ( ReadFileIntoBuffer ( & Index - > Metadata , 0 ) )
{
case RC_ERROR_FILE :
return RC_ERROR_FILE ;
case RC_ERROR_MEMORY :
2018-04-01 20:58:53 +00:00
LogError ( LOG_ERROR , " LinkNeighbours(): %s " , strerror ( errno ) ) ;
2018-02-21 21:50:23 +00:00
return RC_ERROR_MEMORY ;
case RC_SUCCESS :
break ;
}
2018-02-28 01:04:06 +00:00
Index - > Header = * ( index_header * ) Index - > Metadata . Buffer . Ptr ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
if ( N - > PrevIndex = = - 1 & & N - > NextIndex = = - 1 )
2018-03-06 20:40:12 +00:00
{
2018-04-01 20:58:53 +00:00
buffer LonePlayerPagePath ;
ClaimBuffer ( & LonePlayerPagePath , " LonePlayerPagePath " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10 ) ;
ConstructDirectoryPath ( & LonePlayerPagePath , PAGE_PLAYER , Config . PlayerLocation , N - > This . BaseFilename ) ;
CopyStringToBuffer ( & LonePlayerPagePath , " /index.html " ) ;
file_buffer LonePlayerPage ;
CopyStringNoFormat ( LonePlayerPage . Path , LonePlayerPagePath . Location ) ;
DeleteNeighbourLinks ( & LonePlayerPage , & N - > This ) ;
DeclaimBuffer ( & LonePlayerPagePath ) ;
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > ThisIndex ;
* ( index_metadata * ) Index - > Metadata . Buffer . Ptr = N - > This ;
2018-03-06 20:40:12 +00:00
}
2018-04-01 20:58:53 +00:00
else
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
if ( N - > PrevIndex > = 0 )
{
buffer PreviousPlayerPagePath ;
ClaimBuffer ( & PreviousPlayerPagePath , " PreviousPlayerPagePath " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10 ) ;
ConstructDirectoryPath ( & PreviousPlayerPagePath , PAGE_PLAYER , Config . PlayerLocation , N - > Prev . BaseFilename ) ;
CopyStringToBuffer ( & PreviousPlayerPagePath , " /index.html " ) ;
file_buffer PreviousPlayerPage ;
CopyStringNoFormat ( PreviousPlayerPage . Path , PreviousPlayerPagePath . Location ) ;
switch ( LinkType )
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
case LINK_EXCLUDE :
2018-02-21 21:50:23 +00:00
{
2018-04-01 20:58:53 +00:00
buffer ThisPlayerPagePath ;
ClaimBuffer ( & ThisPlayerPagePath , " ThisPlayerPagePath " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10 ) ;
ConstructDirectoryPath ( & ThisPlayerPagePath , PAGE_PLAYER , Config . PlayerLocation , N - > This . BaseFilename ) ;
CopyStringToBuffer ( & ThisPlayerPagePath , " /index.html " ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
file_buffer ThisPlayerPage ;
CopyStringNoFormat ( ThisPlayerPage . Path , ThisPlayerPagePath . Location ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
InsertNeighbourLink ( & ThisPlayerPage , & N - > This , & N - > Prev , LINK_PREV , Index - > Header . ProjectName , N - > NextIndex = = - 1 ? TRUE : FALSE ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
DeclaimBuffer ( & ThisPlayerPagePath ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > ThisIndex ;
* ( index_metadata * ) Index - > Metadata . Buffer . Ptr = N - > This ;
}
case LINK_INCLUDE :
{
InsertNeighbourLink ( & PreviousPlayerPage , & N - > Prev , & N - > This , LINK_NEXT , Index - > Header . ProjectName , N - > PrevIsFirst ) ;
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > PrevIndex ;
* ( index_metadata * ) Index - > Metadata . Buffer . Ptr = N - > Prev ;
}
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
DeclaimBuffer ( & PreviousPlayerPagePath ) ;
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
if ( N - > NextIndex > = 0 )
{
buffer NextPlayerPagePath ;
ClaimBuffer ( & NextPlayerPagePath , " NextPlayerPagePath " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10 ) ;
ConstructDirectoryPath ( & NextPlayerPagePath , PAGE_PLAYER , Config . PlayerLocation , N - > Next . BaseFilename ) ;
CopyStringToBuffer ( & NextPlayerPagePath , " /index.html " ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
file_buffer NextPlayerPage ;
CopyStringNoFormat ( NextPlayerPage . Path , NextPlayerPagePath . Location ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
switch ( LinkType )
{
case LINK_EXCLUDE :
{
buffer ThisPlayerPagePath ;
ClaimBuffer ( & ThisPlayerPagePath , " ThisPlayerPagePath " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH + 1 + 10 ) ;
ConstructDirectoryPath ( & ThisPlayerPagePath , PAGE_PLAYER , Config . PlayerLocation , N - > This . BaseFilename ) ;
CopyStringToBuffer ( & ThisPlayerPagePath , " /index.html " ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
file_buffer ThisPlayerPage ;
CopyStringNoFormat ( ThisPlayerPage . Path , ThisPlayerPagePath . Location ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
InsertNeighbourLink ( & ThisPlayerPage , & N - > This , & N - > Next , LINK_NEXT , Index - > Header . ProjectName , N - > PrevIndex = = - 1 ? TRUE : FALSE ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
DeclaimBuffer ( & ThisPlayerPagePath ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > ThisIndex ;
* ( index_metadata * ) Index - > Metadata . Buffer . Ptr = N - > This ;
}
case LINK_INCLUDE :
{
InsertNeighbourLink ( & NextPlayerPage , & N - > Next , & N - > This , LINK_PREV , Index - > Header . ProjectName , N - > NextIsFinal ) ;
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > NextIndex ;
* ( index_metadata * ) Index - > Metadata . Buffer . Ptr = N - > Next ;
}
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
DeclaimBuffer ( & NextPlayerPagePath ) ;
2018-02-21 21:50:23 +00:00
}
2018-04-01 20:58:53 +00:00
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
Index - > Metadata . Handle = fopen ( Index - > Metadata . Path , " w " ) ;
fwrite ( Index - > Metadata . Buffer . Location , Index - > Metadata . FileSize , 1 , Index - > Metadata . Handle ) ;
fclose ( Index - > Metadata . Handle ) ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
FreeBuffer ( & Index - > Metadata . Buffer ) ;
return RC_SUCCESS ;
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
void
DeleteIndexPageFromFilesystem ( ) // NOTE(matt): Do we need to handle relocating, like the PlayerPage function?
{
buffer IndexDirectory ;
ClaimBuffer ( & IndexDirectory , " IndexDirectory " , 1024 ) ;
ConstructDirectoryPath ( & IndexDirectory , PAGE_INDEX , Config . IndexLocation , " " ) ;
char IndexPagePath [ 1024 ] ;
CopyString ( IndexPagePath , " %s/index.html " , IndexDirectory . Location ) ;
remove ( IndexPagePath ) ;
remove ( IndexDirectory . Location ) ;
DeclaimBuffer ( & IndexDirectory ) ;
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
int
DeletePlayerPageFromFilesystem ( char * BaseFilename , char * PlayerLocation , bool Relocating )
{
// NOTE(matt): Once we have the notion of an output filename format, we'll need to use that here
buffer OutputDirectoryPath ;
ClaimBuffer ( & OutputDirectoryPath , " OutputDirectoryPath " , 1024 ) ;
ConstructDirectoryPath ( & OutputDirectoryPath , PAGE_PLAYER , PlayerLocation , BaseFilename ) ;
DIR * PlayerDir ;
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
if ( ( PlayerDir = opendir ( OutputDirectoryPath . Location ) ) ) // There is a directory for the Player, which there probably should be if not for manual intervention
{
char PlayerPagePath [ 256 ] ;
CopyString ( PlayerPagePath , " %s/index.html " , OutputDirectoryPath . Location ) ;
FILE * PlayerPage ;
if ( ( PlayerPage = fopen ( PlayerPagePath , " r " ) ) )
{
fclose ( PlayerPage ) ;
remove ( PlayerPagePath ) ;
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
closedir ( PlayerDir ) ;
if ( ( remove ( OutputDirectoryPath . Location ) = = - 1 ) )
{
LogError ( LOG_NOTICE , " Mostly deleted %s. Unable to remove directory %s: %s " , BaseFilename , OutputDirectoryPath . Location , strerror ( errno ) ) ;
fprintf ( stderr , " \ e[1;30mMostly deleted \ e[0m %s. \ e[1;31mUnable to remove directory \ e[0m %s: %s " , BaseFilename , OutputDirectoryPath . Location , strerror ( errno ) ) ;
}
else
{
if ( ! Relocating )
{
LogError ( LOG_INFORMATIONAL , " Deleted %s " , BaseFilename ) ;
fprintf ( stderr , " \ e[1;30mDeleted \ e[0m %s \n " , BaseFilename ) ;
}
2018-02-21 21:50:23 +00:00
2018-04-01 20:58:53 +00:00
}
2018-02-21 21:50:23 +00:00
}
2018-04-01 20:58:53 +00:00
DeclaimBuffer ( & OutputDirectoryPath ) ;
2018-02-21 21:50:23 +00:00
return RC_SUCCESS ;
}
2017-10-18 21:07:29 +00:00
int
2018-04-01 20:58:53 +00:00
DeleteFromIndex ( index * Index , neighbourhood * N , char * BaseFilename )
2017-10-18 21:07:29 +00:00
{
// TODO(matt): LogError()
2018-02-21 21:50:23 +00:00
switch ( ReadFileIntoBuffer ( & Index - > Metadata , 0 ) )
2017-10-18 21:07:29 +00:00
{
case RC_ERROR_FILE :
return RC_ERROR_FILE ;
case RC_ERROR_MEMORY :
LogError ( LOG_ERROR , " DeleteFromIndex(): %s " , strerror ( errno ) ) ;
return RC_ERROR_MEMORY ;
2017-11-11 00:34:47 +00:00
case RC_SUCCESS :
2017-10-18 21:07:29 +00:00
break ;
2017-11-11 00:34:47 +00:00
}
2018-02-21 21:50:23 +00:00
switch ( ReadFileIntoBuffer ( & Index - > File , 0 ) )
2017-11-11 00:34:47 +00:00
{
case RC_ERROR_FILE :
return RC_ERROR_FILE ;
case RC_ERROR_MEMORY :
LogError ( LOG_ERROR , " DeleteFromIndex(): %s " , strerror ( errno ) ) ;
return RC_ERROR_MEMORY ;
2017-10-18 21:07:29 +00:00
case RC_SUCCESS :
break ;
}
2018-02-21 21:50:23 +00:00
Index - > Header = * ( index_header * ) Index - > Metadata . Buffer . Ptr ;
Index - > Metadata . Buffer . Ptr + = sizeof ( Index - > Header ) ;
2017-10-18 21:07:29 +00:00
2018-04-01 20:58:53 +00:00
index_metadata * Entry = { 0 } ;
int EntryIndex = BinarySearchForMetadataEntry ( Index , & Entry , BaseFilename ) ;
if ( Entry )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
int DeleteFileFrom = AccumulateIndexEntryInsertionOffset ( Index , EntryIndex ) ;
int DeleteFileTo = DeleteFileFrom + Entry - > Size ;
bool ThisIsPrev = FALSE ;
N - > ThisIndex = EntryIndex ;
GetNeighbourhood ( Index , N , EDIT_DELETION , & ThisIsPrev ) ;
- - Index - > Header . EntryCount ;
2017-08-29 20:35:28 +00:00
2018-02-21 21:50:23 +00:00
if ( Index - > Header . EntryCount = = 0 )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
DeleteIndexPageFromFilesystem ( ) ;
2018-02-21 21:50:23 +00:00
remove ( Index - > Metadata . Path ) ;
remove ( Index - > File . Path ) ;
2017-10-18 21:07:29 +00:00
}
else
2017-09-07 21:41:08 +00:00
{
2018-02-21 21:50:23 +00:00
if ( ! ( Index - > Metadata . Handle = fopen ( Index - > Metadata . Path , " w " ) ) ) { FreeBuffer ( & Index - > Metadata . Buffer ) ; return RC_ERROR_FILE ; }
if ( ! ( Index - > File . Handle = fopen ( Index - > File . Path , " w " ) ) ) { FreeBuffer ( & Index - > File . Buffer ) ; return RC_ERROR_FILE ; }
2017-11-11 00:34:47 +00:00
2018-02-21 21:50:23 +00:00
fwrite ( & Index - > Header , sizeof ( Index - > Header ) , 1 , Index - > Metadata . Handle ) ;
2018-04-01 20:58:53 +00:00
fwrite ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) , sizeof ( index_metadata ) * EntryIndex , 1 , Index - > Metadata . Handle ) ;
fwrite ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * ( EntryIndex + 1 ) ,
Index - > Metadata . FileSize - sizeof ( index_header ) - sizeof ( index_metadata ) * ( EntryIndex + 1 ) ,
1 , Index - > Metadata . Handle ) ;
2018-02-21 21:50:23 +00:00
fclose ( Index - > Metadata . Handle ) ;
2017-11-11 00:34:47 +00:00
2018-02-21 21:50:23 +00:00
fwrite ( Index - > File . Buffer . Location , DeleteFileFrom , 1 , Index - > File . Handle ) ;
fwrite ( Index - > File . Buffer . Location + DeleteFileTo , Index - > File . FileSize - DeleteFileTo , 1 , Index - > File . Handle ) ;
fclose ( Index - > File . Handle ) ;
2017-09-07 21:41:08 +00:00
}
2017-10-18 21:07:29 +00:00
}
2018-02-21 21:50:23 +00:00
FreeBuffer ( & Index - > Metadata . Buffer ) ;
FreeBuffer ( & Index - > File . Buffer ) ;
2018-04-01 20:58:53 +00:00
return Entry ? RC_SUCCESS : RC_NOOP ;
2017-10-18 21:07:29 +00:00
}
int
2018-02-21 21:50:23 +00:00
IndexToBuffer ( index * Index , buffers * CollationBuffers ) // NOTE(matt): This guy malloc's CollationBuffers->Index
2017-10-18 21:07:29 +00:00
{
2018-03-06 20:40:12 +00:00
if ( ReadFileIntoBuffer ( & Index - > Metadata , 0 ) = = RC_SUCCESS )
2017-10-18 21:07:29 +00:00
{
2018-02-21 21:50:23 +00:00
Index - > Header = * ( index_header * ) Index - > Metadata . Buffer . Ptr ;
Index - > Metadata . Buffer . Ptr + = sizeof ( Index - > Header ) ;
2017-10-18 21:07:29 +00:00
2017-12-07 01:15:13 +00:00
bool ProjectFound = FALSE ;
int ProjectIndex ;
for ( ProjectIndex = 0 ; ProjectIndex < ArrayCount ( ProjectInfo ) ; + + ProjectIndex )
{
if ( ! StringsDiffer ( ProjectInfo [ ProjectIndex ] . ProjectID , Config . ProjectID ) )
{
ProjectFound = TRUE ;
break ;
}
}
if ( ! ProjectFound )
{
fprintf ( stderr , " Missing Project Info for %s \n " , Config . ProjectID ) ;
2018-02-21 21:50:23 +00:00
FreeBuffer ( & Index - > Metadata . Buffer ) ;
2017-12-07 01:15:13 +00:00
return RC_ERROR_PROJECT ;
}
2018-03-06 20:40:12 +00:00
char * Theme = StringsDiffer ( Config . Theme , " " ) ? Config . Theme : Config . ProjectID ;
int ThemeStringAllowance = StringLength ( Theme ) * 2 ;
char queryContainer [ 680 + ThemeStringAllowance ] ;
2017-12-07 01:15:13 +00:00
CopyString ( queryContainer ,
2017-12-10 00:17:19 +00:00
" <div class= \" cineraQueryContainer %s \" > \n "
2017-11-11 00:34:47 +00:00
" <label for= \" query \" >Query:</label> \n "
" <div class= \" inputContainer \" > \n "
" <input type= \" text \" id= \" query \" autofocus= \" \" > \n "
" <div class= \" spinner \" > \n "
" Downloading data... \n "
" </div> \n "
" </div> \n "
" </div> \n "
2017-12-10 00:17:19 +00:00
" <div id= \" cineraResultsSummary \" >Found: 0 episodes, 0 markers, 0h 0m 0s total.</div> \n "
" <div id= \" cineraResults \" ></div> \n "
2017-11-11 00:34:47 +00:00
" \n "
2018-01-21 18:59:09 +00:00
" <div id= \" cineraIndex \" class= \" %s \" > \n "
2018-01-21 19:30:54 +00:00
" <div id= \" cineraIndexSort \" >Sort: Old to New ⏶</div> \n "
2018-01-21 18:59:09 +00:00
" <div id= \" cineraIndexEntries \" > \n " ,
2018-03-06 20:40:12 +00:00
Theme ,
Theme ) ;
2017-10-18 21:07:29 +00:00
2017-12-12 23:24:10 +00:00
buffer PlayerURL ;
2018-02-21 21:50:23 +00:00
ClaimBuffer ( & PlayerURL , " PlayerURL " , MAX_BASE_URL_LENGTH + 1 + MAX_RELATIVE_PAGE_LOCATION_LENGTH + 1 + MAX_PLAYER_URL_PREFIX_LENGTH + MAX_BASE_FILENAME_LENGTH ) ;
2017-12-12 23:24:10 +00:00
ConstructPlayerURL ( & PlayerURL , " " ) ;
2018-04-01 20:58:53 +00:00
buffer URLPrefix ;
ClaimBuffer ( & URLPrefix , " URLPrefix " , 1024 ) ;
ConstructURLPrefix ( & URLPrefix , INCLUDE_JS , PAGE_INDEX ) ;
2018-01-21 18:59:09 +00:00
char Script [ 532 + StringLength ( URLPrefix . Location ) + ( StringLength ( Config . ProjectID ) * 2 ) ] ;
CopyString ( Script ,
" </div> \n "
" </div> \n "
2017-12-07 01:15:13 +00:00
" <script type= \" text/javascript \" > \n "
" var projectID = \" %s \" ; \n "
2017-12-07 21:07:36 +00:00
" var theme = \" %s \" ; \n "
2017-12-12 23:24:10 +00:00
" var baseURL = \" %s \" ; \n "
" var playerLocation = \" %s \" ; \n "
2017-12-07 21:07:36 +00:00
" var outputURLPrefix = \" %s \" ; \n "
2017-12-12 23:24:10 +00:00
// TODO(matt): PlayerURL
2017-12-07 01:15:13 +00:00
" </script> \n "
" <script type= \" text/javascript \" src= \" %scinera_search.js \" ></script> \n " ,
Config . ProjectID ,
2018-03-06 20:40:12 +00:00
Theme ,
2017-12-12 23:24:10 +00:00
Config . BaseURL ,
Config . PlayerLocation ,
StringsDiffer ( Config . PlayerURLPrefix , " " ) ? Config . PlayerURLPrefix : Config . ProjectID ,
2017-12-07 21:07:36 +00:00
URLPrefix . Location ) ;
DeclaimBuffer ( & URLPrefix ) ;
2017-12-07 01:15:13 +00:00
int EntryLength = 32 + StringLength ( ProjectInfo [ ProjectIndex ] . Unit ) + 16 + 256 ;
2018-02-21 21:50:23 +00:00
CollationBuffers - > Index . Size = StringLength ( queryContainer ) + ( Index - > Header . EntryCount * EntryLength ) + StringLength ( Script ) ;
2018-01-03 22:16:20 +00:00
2017-12-07 01:15:13 +00:00
if ( ! ( CollationBuffers - > Index . Location = malloc ( CollationBuffers - > Index . Size ) ) )
2017-11-11 00:34:47 +00:00
{
2018-02-21 21:50:23 +00:00
FreeBuffer ( & Index - > Metadata . Buffer ) ;
2017-12-07 01:15:13 +00:00
return ( RC_ERROR_MEMORY ) ;
2017-11-11 00:34:47 +00:00
}
2018-04-01 20:58:53 +00:00
CollationBuffers - > Index . ID = " Index " ;
2017-12-07 01:15:13 +00:00
CollationBuffers - > Index . Ptr = CollationBuffers - > Index . Location ;
CopyStringToBuffer ( & CollationBuffers - > Index , queryContainer ) ;
2017-11-11 00:34:47 +00:00
2018-03-06 20:40:12 +00:00
char * ProjectUnit = 0 ;
if ( StringsDiffer ( ProjectInfo [ ProjectIndex ] . Unit , " " ) )
2017-11-11 00:34:47 +00:00
{
2018-03-06 20:40:12 +00:00
ProjectUnit = ProjectInfo [ ProjectIndex ] . Unit ;
}
int ProjectIDLength = StringLength ( Config . ProjectID ) ;
char Number [ 16 ] ;
char Text [ 1024 ] ; // NOTE(matt): Surely this will be big enough
index_metadata * This ;
2018-04-01 20:58:53 +00:00
bool IndexRequired = FALSE ;
2018-03-06 20:40:12 +00:00
for ( int EntryIndex = 0 ; EntryIndex < Index - > Header . EntryCount ; + + EntryIndex , Index - > Metadata . Buffer . Ptr + = sizeof ( index_metadata ) )
{
This = ( index_metadata * ) Index - > Metadata . Buffer . Ptr ;
2018-04-01 20:58:53 +00:00
if ( This - > Size > 0 )
2017-11-11 00:34:47 +00:00
{
2018-04-01 20:58:53 +00:00
IndexRequired = TRUE ;
CopyString ( Number , This - > BaseFilename + ProjectIDLength ) ;
if ( ProjectInfo [ ProjectIndex ] . NumberingScheme = = NS_LINEAR )
2017-11-11 00:34:47 +00:00
{
2018-04-01 20:58:53 +00:00
for ( int i = 0 ; Number [ i ] ; + + i )
2017-11-11 00:34:47 +00:00
{
2018-04-01 20:58:53 +00:00
if ( Number [ i ] = = ' _ ' )
{
Number [ i ] = ' . ' ;
}
2017-11-11 00:34:47 +00:00
}
}
2018-04-01 20:58:53 +00:00
ConstructPlayerURL ( & PlayerURL , This - > BaseFilename ) ;
2017-11-11 00:34:47 +00:00
2018-04-01 20:58:53 +00:00
if ( ProjectUnit )
{
CopyStringToBuffer ( & CollationBuffers - > Index ,
" <div> \n "
" <a href= \" %s \" > " , PlayerURL . Location ) ;
2017-12-07 21:07:36 +00:00
2018-04-01 20:58:53 +00:00
CopyString ( Text , " %s %s: %s " ,
ProjectUnit , // TODO(matt): Do we need to special-case the various numbering schemes?
Number ,
This - > Title ) ;
2017-12-07 21:07:36 +00:00
2018-04-01 20:58:53 +00:00
CopyStringToBufferHTMLSafe ( & CollationBuffers - > Index , Text ) ;
2017-12-07 21:07:36 +00:00
2018-04-01 20:58:53 +00:00
CopyStringToBuffer ( & CollationBuffers - > Index ,
" </a> \n "
" </div> \n " ) ;
}
else
{
CopyStringToBuffer ( & CollationBuffers - > Index ,
" <div> \n "
" <a href= \" %s \" > " , PlayerURL . Location ) ;
CopyStringToBufferHTMLSafe ( & CollationBuffers - > Index , This - > Title ) ;
CopyStringToBuffer ( & CollationBuffers - > Index ,
" </a> \n "
" </div> \n " ) ;
}
2018-03-06 20:40:12 +00:00
}
2017-11-11 00:34:47 +00:00
}
2017-12-12 23:24:10 +00:00
DeclaimBuffer ( & PlayerURL ) ;
2017-12-07 01:15:13 +00:00
CopyStringToBuffer ( & CollationBuffers - > Index , Script ) ;
2017-11-11 00:34:47 +00:00
2018-02-21 21:50:23 +00:00
FreeBuffer ( & Index - > Metadata . Buffer ) ;
2018-04-01 20:58:53 +00:00
if ( ! IndexRequired ) { return RC_NOOP ; }
else { return RC_SUCCESS ; }
2017-11-11 00:34:47 +00:00
}
else
{
return RC_ERROR_FILE ;
}
2017-10-18 21:07:29 +00:00
}
2017-09-07 21:41:08 +00:00
2017-12-12 23:24:10 +00:00
char *
StripTrailingSlash ( char * String ) // NOTE(matt): For absolute paths
2017-10-18 21:07:29 +00:00
{
2017-12-12 23:24:10 +00:00
int Length = StringLength ( String ) ;
while ( Length > 0 & & String [ Length - 1 ] = = ' / ' )
2017-12-07 21:07:36 +00:00
{
2017-12-12 23:24:10 +00:00
String [ Length - 1 ] = ' \0 ' ;
- - Length ;
2017-12-07 21:07:36 +00:00
}
2017-12-12 23:24:10 +00:00
return String ;
}
char *
StripSurroundingSlashes ( char * String ) // NOTE(matt): For relative paths
{
int Length = StringLength ( String ) ;
if ( Length > 0 )
2017-12-07 21:07:36 +00:00
{
2017-12-12 23:24:10 +00:00
while ( ( String [ 0 ] ) = = ' / ' )
{
+ + String ;
- - Length ;
}
while ( String [ Length - 1 ] = = ' / ' )
{
String [ Length - 1 ] = ' \0 ' ;
- - Length ;
}
2017-12-07 21:07:36 +00:00
}
2017-12-12 23:24:10 +00:00
return String ;
}
int
2018-04-01 20:58:53 +00:00
GeneratePlayerPage ( index * Index , neighbourhood * N , buffers * CollationBuffers , template * PlayerTemplate , char * BaseFilename )
2017-12-12 23:24:10 +00:00
{
buffer OutputDirectoryPath ;
ClaimBuffer ( & OutputDirectoryPath , " OutputDirectoryPath " , 1024 ) ;
ConstructDirectoryPath ( & OutputDirectoryPath , PAGE_PLAYER , Config . PlayerLocation , BaseFilename ) ;
2017-09-15 01:11:39 +00:00
2017-10-18 21:07:29 +00:00
DIR * OutputDirectoryHandle ;
2017-12-12 23:24:10 +00:00
if ( ! ( OutputDirectoryHandle = opendir ( OutputDirectoryPath . Location ) ) ) // TODO(matt): open()
2017-10-18 21:07:29 +00:00
{
2017-12-12 23:24:10 +00:00
if ( MakeDir ( OutputDirectoryPath . Location ) = = RC_ERROR_DIRECTORY )
2017-09-07 21:41:08 +00:00
{
2017-12-12 23:24:10 +00:00
LogError ( LOG_ERROR , " Unable to create directory %s: %s " , OutputDirectoryPath . Location , strerror ( errno ) ) ;
fprintf ( stderr , " Unable to create directory %s: %s \n " , OutputDirectoryPath . Location , strerror ( errno ) ) ;
2017-10-18 21:07:29 +00:00
return RC_ERROR_DIRECTORY ;
} ;
}
closedir ( OutputDirectoryHandle ) ;
2017-12-12 23:24:10 +00:00
char PlayerPagePath [ 1024 ] ;
CopyString ( PlayerPagePath , " %s/index.html " , OutputDirectoryPath . Location ) ;
DeclaimBuffer ( & OutputDirectoryPath ) ;
2018-01-03 22:16:20 +00:00
bool IndexInTemplate = FALSE ;
for ( int TagIndex = 0 ; TagIndex < PlayerTemplate - > Metadata . TagCount ; + + TagIndex )
{
if ( PlayerTemplate - > Metadata . Tag [ TagIndex ] . TagCode = = TAG_INDEX )
{
IndexInTemplate = TRUE ;
2018-02-21 21:50:23 +00:00
IndexToBuffer ( Index , CollationBuffers ) ;
2018-01-03 22:16:20 +00:00
break ;
}
}
2018-04-01 20:58:53 +00:00
BuffersToHTML ( CollationBuffers , PlayerTemplate , PlayerPagePath , PAGE_PLAYER , & N - > This . LinkOffsets . PrevStart ) ;
2018-02-21 21:50:23 +00:00
ReadFileIntoBuffer ( & Index - > Metadata , 0 ) ;
if ( ! ( Index - > Metadata . Handle = fopen ( Index - > Metadata . Path , " w " ) ) ) { FreeBuffer ( & Index - > Metadata . Buffer ) ; return RC_ERROR_FILE ; }
2018-04-01 20:58:53 +00:00
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * N - > ThisIndex ;
* ( index_metadata * ) Index - > Metadata . Buffer . Ptr = N - > This ;
fwrite ( Index - > Metadata . Buffer . Location , Index - > Metadata . FileSize , 1 , Index - > Metadata . Handle ) ;
2018-02-21 21:50:23 +00:00
fclose ( Index - > Metadata . Handle ) ;
FreeBuffer ( & Index - > Metadata . Buffer ) ;
2018-01-03 22:16:20 +00:00
if ( IndexInTemplate )
{
FreeBuffer ( & CollationBuffers - > Index ) ;
}
2017-10-18 21:07:29 +00:00
return RC_SUCCESS ;
}
int
2018-02-21 21:50:23 +00:00
GenerateIndexPage ( index * Index , buffers * CollationBuffers , template * IndexTemplate )
2017-10-18 21:07:29 +00:00
{
2017-12-12 23:24:10 +00:00
buffer OutputDirectoryPath ;
ClaimBuffer ( & OutputDirectoryPath , " OutputDirectoryPath " , 1024 ) ;
ConstructDirectoryPath ( & OutputDirectoryPath , PAGE_INDEX , Config . IndexLocation , 0 ) ;
DIR * OutputDirectoryHandle ;
if ( ! ( OutputDirectoryHandle = opendir ( OutputDirectoryPath . Location ) ) ) // TODO(matt): open()
{
if ( MakeDir ( OutputDirectoryPath . Location ) = = RC_ERROR_DIRECTORY )
{
LogError ( LOG_ERROR , " Unable to create directory %s: %s " , OutputDirectoryPath . Location , strerror ( errno ) ) ;
fprintf ( stderr , " Unable to create directory %s: %s \n " , OutputDirectoryPath . Location , strerror ( errno ) ) ;
return RC_ERROR_DIRECTORY ;
} ;
}
closedir ( OutputDirectoryHandle ) ;
char IndexPagePath [ 1024 ] ;
CopyString ( IndexPagePath , " %s/index.html " , OutputDirectoryPath . Location ) ;
DeclaimBuffer ( & OutputDirectoryPath ) ;
2018-04-01 20:58:53 +00:00
switch ( IndexToBuffer ( Index , CollationBuffers ) )
{
case RC_SUCCESS :
{
BuffersToHTML ( CollationBuffers , IndexTemplate , IndexPagePath , PAGE_INDEX , 0 ) ;
break ;
}
case RC_NOOP :
{
DeleteIndexPageFromFilesystem ( ) ;
break ;
}
}
2017-12-07 01:15:13 +00:00
FreeBuffer ( & CollationBuffers - > Index ) ;
2017-10-18 21:07:29 +00:00
return RC_SUCCESS ;
}
int
2018-04-01 20:58:53 +00:00
DeleteEntry ( index * Index , neighbourhood * Neighbourhood , char * BaseFilename )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
if ( DeleteFromIndex ( Index , Neighbourhood , BaseFilename ) = = RC_SUCCESS )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
if ( Neighbourhood - > This . Size > 0 )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
LinkNeighbours ( Index , Neighbourhood , BaseFilename , LINK_EXCLUDE ) ;
2017-10-18 21:07:29 +00:00
}
2018-04-01 20:58:53 +00:00
DeletePlayerPageFromFilesystem ( BaseFilename , Config . PlayerLocation , FALSE ) ;
return RC_SUCCESS ;
}
return RC_NOOP ;
}
2017-10-18 21:07:29 +00:00
2018-04-01 20:58:53 +00:00
void
InsertEntry ( index * Index , neighbourhood * Neighbourhood , buffers * CollationBuffers , template * PlayerTemplate , template * BespokeTemplate , char * BaseFilename , bool * Inserted , bool RecheckingPrivacy )
{
switch ( InsertIntoIndex ( Index , Neighbourhood , CollationBuffers , & BespokeTemplate , BaseFilename , RecheckingPrivacy ) )
{
case RC_UNFOUND :
LinkNeighbours ( Index , Neighbourhood , BaseFilename , LINK_INCLUDE ) ;
* Inserted = TRUE ;
case RC_SUCCESS :
2017-12-12 23:24:10 +00:00
{
2018-04-01 20:58:53 +00:00
if ( BespokeTemplate - > Metadata . Filename & & StringsDiffer ( BespokeTemplate - > Metadata . Filename , " " ) )
{
GeneratePlayerPage ( Index , Neighbourhood , CollationBuffers , BespokeTemplate , BaseFilename ) ;
DeclaimTemplate ( BespokeTemplate ) ;
}
else
{
GeneratePlayerPage ( Index , Neighbourhood , CollationBuffers , PlayerTemplate , BaseFilename ) ;
}
* Inserted = TRUE ;
} break ;
2017-10-18 21:07:29 +00:00
}
}
2017-08-29 20:35:28 +00:00
2018-03-06 20:40:12 +00:00
int
2018-04-01 20:58:53 +00:00
RecheckPrivacy ( index * Index , buffers * CollationBuffers , template * IndexTemplate , template * PlayerTemplate , template * BespokeTemplate )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
if ( ReadFileIntoBuffer ( & Index - > Metadata , 0 ) = = RC_SUCCESS )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
Index - > Header = * ( index_header * ) Index - > Metadata . Buffer . Ptr ;
index_metadata Entry = { 0 } ;
int PrivateEntryIndex = 0 ;
index_metadata PrivateEntries [ Index - > Header . EntryCount ] ;
bool Inserted = FALSE ;
for ( int IndexEntry = 0 ; IndexEntry < Index - > Header . EntryCount ; + + IndexEntry )
{
Entry = * ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * IndexEntry ) ;
if ( Entry . Size = = 0 )
{
PrivateEntries [ PrivateEntryIndex ] = Entry ;
+ + PrivateEntryIndex ;
}
}
FreeBuffer ( & Index - > Metadata . Buffer ) ;
for ( int i = 0 ; i < PrivateEntryIndex ; + + i )
{
neighbourhood Neighbourhood = { 0 } ;
Neighbourhood . PrevIndex = - 1 ;
Neighbourhood . ThisIndex = - 1 ;
Neighbourhood . NextIndex = - 1 ;
InsertEntry ( Index , & Neighbourhood , CollationBuffers , PlayerTemplate , BespokeTemplate , PrivateEntries [ i ] . BaseFilename , & Inserted , TRUE ) ;
}
if ( Inserted )
{
GenerateIndexPage ( Index , CollationBuffers , IndexTemplate ) ;
}
LastPrivacyCheck = time ( 0 ) ;
2017-10-18 21:07:29 +00:00
}
2018-04-01 20:58:53 +00:00
return RC_SUCCESS ;
2017-10-18 21:07:29 +00:00
}
2017-08-29 20:35:28 +00:00
2017-10-18 21:07:29 +00:00
int
2018-02-21 21:50:23 +00:00
MonitorDirectory ( index * Index , buffers * CollationBuffers , template * IndexTemplate , template * PlayerTemplate , template * BespokeTemplate , int inotifyInstance , int WatchDescriptor )
2017-10-18 21:07:29 +00:00
{
# if DEBUG_MEM
FILE * MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
fprintf ( MemLog , " \n Called MonitorDirectory() \n " ) ;
fclose ( MemLog ) ;
# endif
buffer Events ;
2018-04-01 20:58:53 +00:00
if ( ClaimBuffer ( & Events , " inotify Events " , Kilobytes ( 4 ) ) = = RC_ARENA_FULL ) { return RC_ARENA_FULL ; } ;
2017-10-18 21:07:29 +00:00
struct inotify_event * Event ;
2018-04-01 20:58:53 +00:00
int BytesRead = read ( inotifyInstance , Events . Location , Events . Size ) ; // TODO(matt): Handle error EINVAL
2018-02-21 21:50:23 +00:00
if ( inotifyInstance < 0 ) { perror ( " MonitorDirectory() " ) ; }
2017-10-18 21:07:29 +00:00
2018-04-01 20:58:53 +00:00
bool Deleted = FALSE ;
bool Inserted = FALSE ;
2018-02-21 21:50:23 +00:00
for ( Events . Ptr = Events . Location ;
Events . Ptr < Events . Location + BytesRead & & Events . Ptr - Events . Location < Events . Size ;
Events . Ptr + = sizeof ( struct inotify_event ) + Event - > len )
2017-10-18 21:07:29 +00:00
{
2018-02-21 21:50:23 +00:00
Event = ( struct inotify_event * ) Events . Ptr ;
2018-04-01 20:58:53 +00:00
char * Ptr = Event - > name ;
2018-02-21 21:50:23 +00:00
Ptr + = ( StringLength ( Event - > name ) - StringLength ( " .hmml " ) ) ;
if ( ! ( StringsDiffer ( Ptr , " .hmml " ) ) )
2017-10-18 21:07:29 +00:00
{
2018-02-21 21:50:23 +00:00
* Ptr = ' \0 ' ;
2018-04-01 20:58:53 +00:00
neighbourhood Neighbourhood = { 0 } ;
Neighbourhood . PrevIndex = - 1 ;
Neighbourhood . ThisIndex = - 1 ;
Neighbourhood . NextIndex = - 1 ;
2017-11-11 00:34:47 +00:00
2018-02-21 21:50:23 +00:00
// TODO(matt): Maybe handle IN_ALL_EVENTS
if ( Event - > mask & IN_DELETE | | Event - > mask & IN_MOVED_FROM )
{
2018-04-01 20:58:53 +00:00
if ( DeleteEntry ( Index , & Neighbourhood , Event - > name ) = = RC_SUCCESS )
2018-03-06 20:40:12 +00:00
{
2018-04-01 20:58:53 +00:00
Deleted = TRUE ;
2018-03-06 20:40:12 +00:00
}
2018-02-21 21:50:23 +00:00
}
else
{
2018-04-01 20:58:53 +00:00
InsertEntry ( Index , & Neighbourhood , CollationBuffers , PlayerTemplate , BespokeTemplate , Event - > name , & Inserted , 0 ) ;
2017-10-18 21:07:29 +00:00
}
2017-09-07 21:41:08 +00:00
}
}
2017-08-19 21:41:51 +00:00
2018-04-01 20:58:53 +00:00
if ( Deleted | | Inserted )
{
GenerateIndexPage ( Index , CollationBuffers , IndexTemplate ) ;
}
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( & Events ) ;
2018-04-01 20:58:53 +00:00
return RC_SUCCESS ;
2017-09-07 21:41:08 +00:00
}
2017-06-25 18:22:54 +00:00
2017-12-12 23:24:10 +00:00
int
RemoveDirectory ( char * Path )
{
if ( ( remove ( Path ) = = - 1 ) )
{
LogError ( LOG_NOTICE , " Unable to remove directory %s: %s " , Path , strerror ( errno ) ) ;
2018-01-03 22:16:20 +00:00
fprintf ( stderr , " \ e[1;30mUnable to remove directory \ e[0m %s: %s \n " , Path , strerror ( errno ) ) ;
2017-12-12 23:24:10 +00:00
return RC_ERROR_DIRECTORY ;
}
else
{
LogError ( LOG_INFORMATIONAL , " Deleted %s " , Path ) ;
//fprintf(stderr, "\e[1;30mDeleted\e[0m %s\n", Path);
return RC_SUCCESS ;
}
}
int
RemoveDirectoryRecursively ( char * Path )
{
if ( RemoveDirectory ( Path ) = = RC_ERROR_DIRECTORY ) { return RC_ERROR_DIRECTORY ; }
char * Ptr = Path + StringLength ( Path ) - 1 ;
while ( Ptr > Path )
{
if ( * Ptr = = ' / ' )
{
* Ptr = ' \0 ' ;
if ( RemoveDirectory ( Path ) = = RC_ERROR_DIRECTORY ) { return RC_ERROR_DIRECTORY ; }
}
- - Ptr ;
}
return RC_SUCCESS ;
}
int
UpgradeDB ( index * Index )
{
2018-02-21 21:50:23 +00:00
// DBVersion 1
typedef struct { unsigned int DBVersion ; version AppVersion ; version HMMLVersion ; unsigned int EntryCount ; } index_header1 ;
typedef struct { int Size ; char BaseFilename [ 32 ] ; } index_metadata1 ;
typedef struct { file_buffer File ; file_buffer Metadata ; index_header1 Header ; index_metadata1 Entry ; } index1 ;
//
// DBVersion 2
typedef struct { unsigned int DBVersion ; version AppVersion ; version HMMLVersion ; unsigned int EntryCount ; char IndexLocation [ 32 ] ; char PlayerLocation [ 32 ] ; } index_header2 ;
typedef struct { int Size ; char BaseFilename [ 32 ] ; } index_metadata2 ;
typedef struct { file_buffer File ; file_buffer Metadata ; index_header2 Header ; index_metadata2 Entry ; } index2 ;
//
// NOTE(matt): For each new DB version, we must declare and initialise one instance of each preceding version, only cast the
// incoming Index to the type of the OriginalDBVersion, and move into the final case all operations on that incoming Index
bool OnlyHeaderChanged = TRUE ;
int OriginalHeaderSize = 0 ;
index1 Index1 = { 0 } ;
index2 Index2 = { 0 } ;
int OriginalDBVersion = Index - > Header . CurrentDBVersion ;
switch ( OriginalDBVersion )
2017-12-12 23:24:10 +00:00
{
case 1 :
{
2018-02-21 21:50:23 +00:00
OriginalHeaderSize = sizeof ( index_header1 ) ;
Index1 . Header = * ( index_header1 * ) Index - > Metadata . Buffer . Ptr ;
Index2 . Header . DBVersion = CINERA_DB_VERSION ;
Index2 . Header . AppVersion = CINERA_APP_VERSION ;
Index2 . Header . HMMLVersion . Major = hmml_version . Major ;
Index2 . Header . HMMLVersion . Minor = hmml_version . Minor ;
Index2 . Header . HMMLVersion . Patch = hmml_version . Patch ;
Index2 . Header . EntryCount = Index1 . Header . EntryCount ;
Clear ( Index2 . Header . IndexLocation , sizeof ( Index2 . Header . IndexLocation ) ) ;
Clear ( Index2 . Header . PlayerLocation , sizeof ( Index2 . Header . PlayerLocation ) ) ;
}
case 2 :
{
if ( OriginalDBVersion = = 2 )
2017-12-12 23:24:10 +00:00
{
2018-02-21 21:50:23 +00:00
OriginalHeaderSize = sizeof ( index_header2 ) ;
Index2 . Header = * ( index_header2 * ) Index - > Metadata . Buffer . Ptr ;
}
2017-12-12 23:24:10 +00:00
2018-02-21 21:50:23 +00:00
Index - > Header . InitialDBVersion = Index2 . Header . DBVersion ;
Index - > Header . InitialAppVersion = Index2 . Header . AppVersion ;
Index - > Header . InitialHMMLVersion . Major = Index2 . Header . HMMLVersion . Major ;
Index - > Header . InitialHMMLVersion . Minor = Index2 . Header . HMMLVersion . Minor ;
Index - > Header . InitialHMMLVersion . Patch = Index2 . Header . HMMLVersion . Patch ;
Index - > Header . CurrentDBVersion = CINERA_DB_VERSION ;
Index - > Header . CurrentAppVersion = CINERA_APP_VERSION ;
Index - > Header . CurrentHMMLVersion . Major = hmml_version . Major ;
Index - > Header . CurrentHMMLVersion . Minor = hmml_version . Minor ;
Index - > Header . CurrentHMMLVersion . Patch = hmml_version . Patch ;
Index - > Header . EntryCount = Index2 . Header . EntryCount ;
ClearCopyStringNoFormat ( Index - > Header . ProjectID , sizeof ( Index - > Header . ProjectID ) , Config . ProjectID ) ;
2017-12-12 23:24:10 +00:00
2018-02-21 21:50:23 +00:00
Clear ( Index - > Header . ProjectName , sizeof ( Index - > Header . ProjectName ) ) ;
for ( int ProjectIndex = 0 ; ProjectIndex < ArrayCount ( ProjectInfo ) ; + + ProjectIndex )
2017-12-12 23:24:10 +00:00
{
2018-02-21 21:50:23 +00:00
if ( ! StringsDiffer ( ProjectInfo [ ProjectIndex ] . ProjectID , Config . ProjectID ) )
{
CopyString ( Index - > Header . ProjectName , ProjectInfo [ ProjectIndex ] . FullName ) ;
break ;
}
}
ClearCopyStringNoFormat ( Index - > Header . BaseURL , sizeof ( Index - > Header . BaseURL ) , Config . BaseURL ) ;
ClearCopyStringNoFormat ( Index - > Header . IndexLocation , sizeof ( Index - > Header . IndexLocation ) , Index2 . Header . IndexLocation ) ;
ClearCopyStringNoFormat ( Index - > Header . PlayerLocation , sizeof ( Index - > Header . PlayerLocation ) , Index2 . Header . PlayerLocation ) ;
ClearCopyStringNoFormat ( Index - > Header . PlayerURLPrefix , sizeof ( Index - > Header . PlayerURLPrefix ) , Config . PlayerURLPrefix ) ;
OnlyHeaderChanged = FALSE ;
2017-12-12 23:24:10 +00:00
if ( ! ( Index - > Metadata . Handle = fopen ( Index - > Metadata . Path , " w " ) ) ) { FreeBuffer ( & Index - > Metadata . Buffer ) ; return RC_ERROR_FILE ; }
fwrite ( & Index - > Header , sizeof ( Index - > Header ) , 1 , Index - > Metadata . Handle ) ;
2018-02-21 21:50:23 +00:00
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + OriginalHeaderSize ;
if ( ReadFileIntoBuffer ( & Index - > File , 0 ) = = RC_ERROR_FILE )
{
fprintf ( stderr , " \ e[1;31mUnable to open index file \ e[0m %s: %s \n "
" Removing %s and starting afresh \n " , Index - > File . Path , strerror ( errno ) ,
Index - > Metadata . Path ) ;
fclose ( Index - > Metadata . Handle ) ;
FreeBuffer ( & Index - > Metadata . Buffer ) ;
remove ( Index - > Metadata . Path ) ;
return RC_ERROR_FILE ;
}
Index - > File . Buffer . Ptr + = StringLength ( " --- \n " ) ;
for ( int EntryIndex = 0 ; EntryIndex < Index - > Header . EntryCount ; + + EntryIndex )
{
// NOTE(matt): We can use either index_metadata1 or 2 here because they are the same
index_metadata2 This = * ( index_metadata2 * ) Index - > Metadata . Buffer . Ptr ;
Index - > Entry . LinkOffsets . PrevStart = 0 ;
Index - > Entry . LinkOffsets . NextStart = 0 ;
Index - > Entry . LinkOffsets . PrevEnd = 0 ;
Index - > Entry . LinkOffsets . NextEnd = 0 ;
Index - > Entry . Size = This . Size ;
ClearCopyStringNoFormat ( Index - > Entry . BaseFilename , sizeof ( Index - > Entry . BaseFilename ) , This . BaseFilename ) ;
char * IndexEntryStart = Index - > File . Buffer . Ptr ;
SeekBufferForString ( & Index - > File . Buffer , " title: \" " , C_SEEK_FORWARDS , C_SEEK_AFTER ) ;
Clear ( Index - > Entry . Title , sizeof ( Index - > Entry . Title ) ) ;
CopyStringNoFormatT ( Index - > Entry . Title , Index - > File . Buffer . Ptr , ' \n ' ) ;
Index - > Entry . Title [ StringLength ( Index - > Entry . Title ) - 1 ] = ' \0 ' ;
fwrite ( & Index - > Entry , sizeof ( Index - > Entry ) , 1 , Index - > Metadata . Handle ) ;
Index - > Metadata . Buffer . Ptr + = sizeof ( This ) ;
IndexEntryStart + = This . Size ;
Index - > File . Buffer . Ptr = IndexEntryStart ;
}
FreeBuffer ( & Index - > File . Buffer ) ;
2017-12-12 23:24:10 +00:00
fclose ( Index - > Metadata . Handle ) ;
FreeBuffer ( & Index - > Metadata . Buffer ) ;
if ( ReadFileIntoBuffer ( & Index - > Metadata , 0 ) = = RC_ERROR_FILE )
{
return RC_ERROR_FILE ;
}
}
}
2018-02-21 21:50:23 +00:00
if ( OnlyHeaderChanged )
{
if ( ! ( Index - > Metadata . Handle = fopen ( Index - > Metadata . Path , " w " ) ) ) { FreeBuffer ( & Index - > Metadata . Buffer ) ; return RC_ERROR_FILE ; }
fwrite ( & Index - > Header , sizeof ( Index - > Header ) , 1 , Index - > Metadata . Handle ) ;
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location + OriginalHeaderSize ;
fwrite ( Index - > Metadata . Buffer . Ptr , Index - > Metadata . FileSize - OriginalHeaderSize , 1 , Index - > Metadata . Handle ) ;
Index - > Metadata . Buffer . Ptr = Index - > Metadata . Buffer . Location ;
fclose ( Index - > Metadata . Handle ) ;
FreeBuffer ( & Index - > Metadata . Buffer ) ;
if ( ReadFileIntoBuffer ( & Index - > Metadata , 0 ) = = RC_ERROR_FILE )
{
return RC_ERROR_FILE ;
}
}
fprintf ( stderr , " \n \ e[1;32mUpgraded Cinera DB from %d to %d! \ e[0m \n \n " , OriginalDBVersion , Index - > Header . CurrentDBVersion ) ;
2017-12-12 23:24:10 +00:00
return RC_SUCCESS ;
}
2017-10-18 21:07:29 +00:00
typedef struct
{
bool Present ;
char ID [ 32 ] ;
2017-11-11 00:34:47 +00:00
} index_entry ; // Metadata, unless we actually want to bolster this?
2017-10-18 21:07:29 +00:00
2018-04-01 20:58:53 +00:00
void
RemoveChildDirectories ( buffer FullPath , char * ParentDirectory )
{
char * Ptr = FullPath . Location + StringLength ( ParentDirectory ) ;
RemoveDirectory ( FullPath . Location ) ;
while ( FullPath . Ptr > Ptr )
{
if ( * FullPath . Ptr = = ' / ' )
{
* FullPath . Ptr = ' \0 ' ;
RemoveDirectory ( FullPath . Location ) ;
}
- - FullPath . Ptr ;
}
}
2017-09-07 21:41:08 +00:00
int
2018-02-21 21:50:23 +00:00
DeleteDeadIndexEntries ( index * Index )
2017-09-07 21:41:08 +00:00
{
2017-11-11 00:34:47 +00:00
// TODO(matt): More rigorously figure out who we should delete
// Maybe compare the output directory and the input HMML names
2018-02-21 21:50:23 +00:00
if ( ReadFileIntoBuffer ( & Index - > Metadata , 0 ) = = RC_ERROR_FILE )
2017-10-18 21:07:29 +00:00
{
return RC_ERROR_FILE ;
}
2018-02-21 21:50:23 +00:00
Index - > Header = * ( index_header * ) Index - > Metadata . Buffer . Ptr ;
if ( Index - > Header . CurrentDBVersion < CINERA_DB_VERSION )
2017-11-11 00:34:47 +00:00
{
2018-02-21 21:50:23 +00:00
if ( CINERA_DB_VERSION = = 4 )
2017-12-12 23:24:10 +00:00
{
2018-02-21 21:50:23 +00:00
fprintf ( stderr , " \n \ e[1;31mHandle conversion from CINERA_DB_VERSION %d to %d! \ e[0m \n \n " , Index - > Header . CurrentDBVersion , CINERA_DB_VERSION ) ;
2017-12-12 23:24:10 +00:00
exit ( RC_ERROR_FATAL ) ;
}
2018-02-21 21:50:23 +00:00
if ( UpgradeDB ( Index ) = = RC_ERROR_FILE ) { return RC_NOOP ;
}
}
else if ( Index - > Header . CurrentDBVersion > CINERA_DB_VERSION )
{
fprintf ( stderr , " \ e[1;31mUnsupported DB Version (%d). Please upgrade Cinera \ e[0m \n " , Index - > Header . CurrentDBVersion ) ;
exit ( RC_ERROR_FATAL ) ;
2017-11-11 00:34:47 +00:00
}
2017-10-18 21:07:29 +00:00
2018-04-01 20:58:53 +00:00
bool NewPlayerLocation = FALSE ;
bool NewIndexLocation = FALSE ;
2018-02-21 21:50:23 +00:00
if ( StringsDiffer ( Index - > Header . PlayerLocation , Config . PlayerLocation ) )
2017-12-12 23:24:10 +00:00
{
2018-04-01 20:58:53 +00:00
buffer OldPlayerDirectory ;
ClaimBuffer ( & OldPlayerDirectory , " OldPlayerDirectory " , 1024 ) ;
ConstructDirectoryPath ( & OldPlayerDirectory , PAGE_PLAYER , Index - > Header . PlayerLocation , 0 ) ;
buffer NewPlayerDirectory ;
ClaimBuffer ( & NewPlayerDirectory , " NewPlayerDirectory " , 1024 ) ;
ConstructDirectoryPath ( & NewPlayerDirectory , PAGE_PLAYER , Config . PlayerLocation , 0 ) ;
2017-12-12 23:24:10 +00:00
printf ( " \ e[1;33mRelocating Player Page%s from %s to %s \ e[0m \n " ,
2018-02-21 21:50:23 +00:00
Index - > Header . EntryCount > 1 ? " s " : " " ,
2018-04-01 20:58:53 +00:00
OldPlayerDirectory . Location ,
NewPlayerDirectory . Location ) ;
DeclaimBuffer ( & NewPlayerDirectory ) ;
2018-02-21 21:50:23 +00:00
for ( int EntryIndex = 0 ; EntryIndex < Index - > Header . EntryCount ; + + EntryIndex )
2017-12-12 23:24:10 +00:00
{
2018-04-01 20:58:53 +00:00
index_metadata This = * ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * EntryIndex ) ;
ConstructDirectoryPath ( & OldPlayerDirectory , PAGE_PLAYER , Index - > Header . PlayerLocation , This . BaseFilename ) ;
2018-02-21 21:50:23 +00:00
DeletePlayerPageFromFilesystem ( This . BaseFilename , Index - > Header . PlayerLocation , TRUE ) ;
2017-12-12 23:24:10 +00:00
}
2018-04-01 20:58:53 +00:00
ConstructDirectoryPath ( & OldPlayerDirectory , PAGE_PLAYER , Index - > Header . PlayerLocation , 0 ) ;
if ( StringLength ( Index - > Header . PlayerLocation ) > 0 )
{
RemoveChildDirectories ( OldPlayerDirectory , Config . BaseDir ) ;
}
DeclaimBuffer ( & OldPlayerDirectory ) ;
2018-02-21 21:50:23 +00:00
ClearCopyStringNoFormat ( Index - > Header . PlayerLocation , sizeof ( Index - > Header . PlayerLocation ) , Config . PlayerLocation ) ;
2018-04-01 20:58:53 +00:00
NewPlayerLocation = TRUE ;
2018-02-21 21:50:23 +00:00
}
if ( StringsDiffer ( Index - > Header . IndexLocation , Config . IndexLocation ) )
2017-12-12 23:24:10 +00:00
{
2018-04-01 20:58:53 +00:00
buffer OldIndexDirectory ;
ClaimBuffer ( & OldIndexDirectory , " OldIndexDirectory " , 1024 ) ;
ConstructDirectoryPath ( & OldIndexDirectory , PAGE_INDEX , Index - > Header . IndexLocation , 0 ) ;
buffer NewIndexDirectory ;
ClaimBuffer ( & NewIndexDirectory , " NewIndexDirectory " , 1024 ) ;
ConstructDirectoryPath ( & NewIndexDirectory , PAGE_INDEX , Config . IndexLocation , 0 ) ;
2017-12-12 23:24:10 +00:00
printf ( " \ e[1;33mRelocating Index Page from %s to %s \ e[0m \n " ,
2018-04-01 20:58:53 +00:00
OldIndexDirectory . Location ,
NewIndexDirectory . Location ) ;
DeclaimBuffer ( & NewIndexDirectory ) ;
2017-12-12 23:24:10 +00:00
char IndexPagePath [ 2048 ] = { 0 } ;
2018-04-01 20:58:53 +00:00
CopyString ( IndexPagePath , " %s/index.html " , OldIndexDirectory . Location ) ;
2017-12-12 23:24:10 +00:00
remove ( IndexPagePath ) ;
2018-04-01 20:58:53 +00:00
if ( StringLength ( Index - > Header . IndexLocation ) > 0 )
{
RemoveChildDirectories ( OldIndexDirectory , Config . BaseDir ) ;
}
DeclaimBuffer ( & OldIndexDirectory ) ;
2017-12-12 23:24:10 +00:00
2018-02-21 21:50:23 +00:00
ClearCopyStringNoFormat ( Index - > Header . IndexLocation , sizeof ( Index - > Header . IndexLocation ) , Config . IndexLocation ) ;
2018-04-01 20:58:53 +00:00
NewIndexLocation = TRUE ;
}
if ( NewPlayerLocation | | NewIndexLocation )
{
2018-02-21 21:50:23 +00:00
if ( ! ( Index - > Metadata . Handle = fopen ( Index - > Metadata . Path , " w " ) ) ) { FreeBuffer ( & Index - > Metadata . Buffer ) ; return RC_ERROR_FILE ; }
2018-04-01 20:58:53 +00:00
* ( index_header * ) Index - > Metadata . Buffer . Location = Index - > Header ;
fwrite ( Index - > Metadata . Buffer . Location , Index - > Metadata . FileSize , 1 , Index - > Metadata . Handle ) ;
2018-02-21 21:50:23 +00:00
fclose ( Index - > Metadata . Handle ) ;
2017-12-12 23:24:10 +00:00
}
2018-02-21 21:50:23 +00:00
index_entry Entries [ Index - > Header . EntryCount ] ;
2017-11-11 00:34:47 +00:00
2018-02-21 21:50:23 +00:00
for ( int EntryIndex = 0 ; EntryIndex < Index - > Header . EntryCount ; + + EntryIndex )
2017-10-18 21:07:29 +00:00
{
2018-04-01 20:58:53 +00:00
index_metadata This = * ( index_metadata * ) ( Index - > Metadata . Buffer . Location + sizeof ( index_header ) + sizeof ( index_metadata ) * EntryIndex ) ;
2017-11-11 00:34:47 +00:00
CopyStringNoFormat ( Entries [ EntryIndex ] . ID , This . BaseFilename ) ;
Entries [ EntryIndex ] . Present = FALSE ;
2017-10-18 21:07:29 +00:00
}
2017-09-07 21:41:08 +00:00
DIR * ProjectDirHandle ;
if ( ! ( ProjectDirHandle = opendir ( Config . ProjectDir ) ) )
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_ERROR , " Unable to scan project directory %s: %s " , Config . ProjectDir , strerror ( errno ) ) ;
2017-09-07 21:41:08 +00:00
fprintf ( stderr , " Unable to scan project directory %s: %s \n " , Config . ProjectDir , strerror ( errno ) ) ;
return RC_ERROR_DIRECTORY ;
}
2017-08-29 20:35:28 +00:00
2017-09-07 21:41:08 +00:00
struct dirent * ProjectFiles ;
2017-08-29 20:35:28 +00:00
2017-09-07 21:41:08 +00:00
while ( ( ProjectFiles = readdir ( ProjectDirHandle ) ) )
{
char * Ptr ;
Ptr = ProjectFiles - > d_name ;
Ptr + = ( StringLength ( ProjectFiles - > d_name ) - StringLength ( " .hmml " ) ) ;
if ( ! ( StringsDiffer ( Ptr , " .hmml " ) ) )
{
* Ptr = ' \0 ' ;
2018-02-21 21:50:23 +00:00
for ( int i = 0 ; i < Index - > Header . EntryCount ; + + i )
2017-08-19 21:41:51 +00:00
{
2017-10-18 21:07:29 +00:00
if ( ! StringsDiffer ( Entries [ i ] . ID , ProjectFiles - > d_name ) )
{
Entries [ i ] . Present = TRUE ;
2017-09-07 21:41:08 +00:00
break ;
2017-10-18 21:07:29 +00:00
}
2017-08-29 20:35:28 +00:00
}
}
}
2017-09-07 21:41:08 +00:00
closedir ( ProjectDirHandle ) ;
2017-10-18 21:07:29 +00:00
bool Deleted = FALSE ;
2018-02-21 21:50:23 +00:00
for ( int i = 0 ; i < Index - > Header . EntryCount ; + + i )
2017-10-18 21:07:29 +00:00
{
if ( Entries [ i ] . Present = = FALSE )
{
Deleted = TRUE ;
2018-04-01 20:58:53 +00:00
neighbourhood Neighbourhood = { 0 } ;
Neighbourhood . PrevIndex = - 1 ;
Neighbourhood . ThisIndex = - 1 ;
Neighbourhood . NextIndex = - 1 ;
DeleteEntry ( Index , & Neighbourhood , Entries [ i ] . ID ) ;
2017-10-18 21:07:29 +00:00
}
}
2018-02-21 21:50:23 +00:00
FreeBuffer ( & Index - > Metadata . Buffer ) ;
2017-10-18 21:07:29 +00:00
return Deleted ? RC_SUCCESS : RC_NOOP ;
2017-08-29 20:35:28 +00:00
}
2017-08-19 21:41:51 +00:00
2017-09-15 01:11:39 +00:00
int
2018-02-21 21:50:23 +00:00
SyncIndexWithInput ( index * Index , buffers * CollationBuffers , template * IndexTemplate , template * PlayerTemplate , template * BespokeTemplate )
2017-09-15 01:11:39 +00:00
{
2017-11-11 00:34:47 +00:00
bool Deleted = FALSE ;
2018-02-21 21:50:23 +00:00
if ( DeleteDeadIndexEntries ( Index ) = = RC_SUCCESS )
2017-11-11 00:34:47 +00:00
{
Deleted = TRUE ;
}
2017-09-15 01:11:39 +00:00
2017-10-18 21:07:29 +00:00
DIR * ProjectDirHandle ;
if ( ! ( ProjectDirHandle = opendir ( Config . ProjectDir ) ) )
{
LogError ( LOG_ERROR , " Unable to scan project directory %s: %s " , Config . ProjectDir , strerror ( errno ) ) ;
fprintf ( stderr , " Unable to scan project directory %s: %s \n " , Config . ProjectDir , strerror ( errno ) ) ;
return RC_ERROR_DIRECTORY ;
}
2017-09-15 01:11:39 +00:00
2017-10-18 21:07:29 +00:00
struct dirent * ProjectFiles ;
bool Inserted = FALSE ;
while ( ( ProjectFiles = readdir ( ProjectDirHandle ) ) )
2017-09-15 01:11:39 +00:00
{
2017-10-18 21:07:29 +00:00
char * Ptr = ProjectFiles - > d_name ;
Ptr + = ( StringLength ( ProjectFiles - > d_name ) - StringLength ( " .hmml " ) ) ;
if ( ! ( StringsDiffer ( Ptr , " .hmml " ) ) )
2017-09-15 01:11:39 +00:00
{
2017-10-18 21:07:29 +00:00
* Ptr = ' \0 ' ;
2018-04-01 20:58:53 +00:00
neighbourhood Neighbourhood = { 0 } ;
Neighbourhood . PrevIndex = - 1 ;
Neighbourhood . NextIndex = - 1 ;
InsertEntry ( Index , & Neighbourhood , CollationBuffers , PlayerTemplate , BespokeTemplate , ProjectFiles - > d_name , & Inserted , 0 ) ;
2017-09-15 01:11:39 +00:00
}
}
2017-10-18 21:07:29 +00:00
closedir ( ProjectDirHandle ) ;
2017-09-15 01:11:39 +00:00
2017-11-11 00:34:47 +00:00
if ( Deleted | | Inserted )
2017-10-18 21:07:29 +00:00
{
2018-02-21 21:50:23 +00:00
GenerateIndexPage ( Index , CollationBuffers , IndexTemplate ) ;
2017-10-18 21:07:29 +00:00
}
2017-09-15 01:11:39 +00:00
return RC_SUCCESS ;
}
2017-11-18 00:27:33 +00:00
void
PrintVersions ( )
{
curl_version_info_data * CurlVersion = curl_version_info ( CURLVERSION_NOW ) ;
printf ( " Cinera: %d.%d.%d \n "
" Cinera DB: %d \n "
" hmmlib: %d.%d.%d \n "
" libcurl: %s \n " ,
CINERA_APP_VERSION . Major , CINERA_APP_VERSION . Minor , CINERA_APP_VERSION . Patch ,
CINERA_DB_VERSION ,
hmml_version . Major , hmml_version . Minor , hmml_version . Patch ,
CurlVersion - > version ) ;
}
2017-08-29 20:35:28 +00:00
int
main ( int ArgC , char * * Args )
{
// TODO(matt): Read all defaults from the config
config DefaultConfig = {
2017-12-12 23:24:10 +00:00
. RootDir = " . " ,
. RootURL = " " ,
2017-11-11 00:34:47 +00:00
. CSSDir = " " ,
. ImagesDir = " " ,
. JSDir = " " ,
2018-01-03 22:16:20 +00:00
. TemplatesDir = " . " ,
. TemplateIndexLocation = " " ,
. TemplatePlayerLocation = " " ,
2017-12-12 23:24:10 +00:00
. BaseDir = " . " ,
. BaseURL = " " ,
. IndexLocation = " " ,
. PlayerLocation = " " , // Should default to the ProjectID
. Edition = EDITION_SINGLE ,
2017-10-18 21:07:29 +00:00
. LogLevel = LOG_EMERGENCY ,
2017-08-29 20:35:28 +00:00
. DefaultMedium = " programming " ,
2018-01-03 22:16:20 +00:00
. Mode = 0 ,
2017-08-29 20:35:28 +00:00
. OutLocation = " out.html " ,
. OutIntegratedLocation = " out_integrated.html " ,
. ProjectDir = " . " ,
2017-10-18 21:07:29 +00:00
. ProjectID = " " ,
2017-12-07 01:15:13 +00:00
. Theme = " " ,
2017-12-07 21:07:36 +00:00
. UpdateInterval = 4 ,
2017-12-12 23:24:10 +00:00
. PlayerURLPrefix = " "
2017-08-29 20:35:28 +00:00
} ;
2017-08-19 21:41:51 +00:00
2017-08-29 20:35:28 +00:00
if ( getenv ( " XDG_CACHE_HOME " ) )
{
CopyString ( DefaultConfig . CacheDir , " %s/cinera " , getenv ( " XDG_CACHE_HOME " ) ) ;
}
else
{
CopyString ( DefaultConfig . CacheDir , " %s/.cache/cinera " , getenv ( " HOME " ) ) ;
}
2017-08-19 21:41:51 +00:00
2017-10-18 21:07:29 +00:00
Config = DefaultConfig ;
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( ArgC < 2 )
{
2017-10-18 21:07:29 +00:00
PrintUsage ( Args [ 0 ] , & DefaultConfig ) ;
2017-09-07 21:41:08 +00:00
return RC_RIP ;
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
char CommandLineArg ;
2018-04-01 20:58:53 +00:00
while ( ( CommandLineArg = getopt ( ArgC , Args , " a:b:B:c:d:efghi:j:l:m:n:o:p:qr:R:s:t:u:vwx:y: " ) ) ! = - 1 )
2017-08-29 20:35:28 +00:00
{
switch ( CommandLineArg )
{
2017-12-12 23:24:10 +00:00
case ' a ' :
Config . PlayerLocation = StripSurroundingSlashes ( optarg ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' b ' :
2017-11-11 00:34:47 +00:00
Config . BaseDir = StripTrailingSlash ( optarg ) ;
2017-08-29 20:35:28 +00:00
break ;
2017-12-12 23:24:10 +00:00
case ' B ' :
Config . BaseURL = StripTrailingSlash ( optarg ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' c ' :
2017-12-12 23:24:10 +00:00
Config . CSSDir = StripSurroundingSlashes ( optarg ) ;
2017-11-11 00:34:47 +00:00
break ;
case ' d ' :
Config . ProjectDir = StripTrailingSlash ( optarg ) ;
2017-08-29 20:35:28 +00:00
break ;
2018-02-21 21:50:23 +00:00
case ' e ' :
Config . Mode | = MODE_EXAMINE ;
break ;
2017-08-29 20:35:28 +00:00
case ' f ' :
2018-04-01 20:58:53 +00:00
Config . Mode | = MODE_FORCEINTEGRATION ;
break ;
case ' g ' :
Config . Mode | = MODE_NOPRIVACY ;
2017-08-29 20:35:28 +00:00
break ;
case ' i ' :
2017-12-12 23:24:10 +00:00
Config . ImagesDir = StripSurroundingSlashes ( optarg ) ;
2017-10-18 21:07:29 +00:00
break ;
2017-08-29 20:35:28 +00:00
case ' j ' :
2017-12-12 23:24:10 +00:00
Config . JSDir = StripSurroundingSlashes ( optarg ) ;
2017-08-29 20:35:28 +00:00
break ;
2017-09-07 21:41:08 +00:00
case ' l ' :
// TODO(matt): Make this actually take a string, rather than requiring the LogLevel number
Config . LogLevel = StringToInt ( optarg ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' m ' :
Config . DefaultMedium = optarg ;
break ;
2017-12-12 23:24:10 +00:00
case ' n ' :
Config . IndexLocation = StripSurroundingSlashes ( optarg ) ;
break ;
2017-08-29 20:35:28 +00:00
case ' o ' :
Config . OutLocation = optarg ;
Config . OutIntegratedLocation = optarg ;
break ;
case ' p ' :
2017-11-11 00:34:47 +00:00
Config . ProjectID = optarg ;
2017-08-29 20:35:28 +00:00
break ;
2017-12-18 14:31:05 +00:00
case ' q ' :
2017-12-18 15:10:02 +00:00
Config . Mode | = MODE_ONESHOT ;
2017-12-18 14:31:05 +00:00
break ;
2017-10-18 21:07:29 +00:00
case ' r ' :
2017-11-11 00:34:47 +00:00
Config . RootDir = StripTrailingSlash ( optarg ) ;
2017-10-18 21:07:29 +00:00
break ;
2017-12-12 23:24:10 +00:00
case ' R ' :
Config . RootURL = StripTrailingSlash ( optarg ) ;
break ;
2017-12-07 01:15:13 +00:00
case ' s ' :
Config . Theme = optarg ;
break ;
2017-08-29 20:35:28 +00:00
case ' t ' :
2018-01-03 22:16:20 +00:00
Config . TemplatesDir = StripTrailingSlash ( optarg ) ;
2017-09-07 21:41:08 +00:00
break ;
2018-02-21 21:50:23 +00:00
case ' u ' :
2017-10-18 21:07:29 +00:00
Config . UpdateInterval = StringToInt ( optarg ) ;
break ;
2017-11-18 00:27:33 +00:00
case ' v ' :
PrintVersions ( ) ;
return RC_SUCCESS ;
2018-02-28 01:04:06 +00:00
case ' w ' :
Config . Mode | = MODE_NOCACHE ;
break ;
2017-09-07 21:41:08 +00:00
case ' x ' :
Config . TemplateIndexLocation = optarg ;
2018-01-03 22:16:20 +00:00
break ;
case ' y ' :
Config . TemplatePlayerLocation = optarg ;
2017-08-29 20:35:28 +00:00
break ;
case ' h ' :
default :
2017-10-18 21:07:29 +00:00
PrintUsage ( Args [ 0 ] , & DefaultConfig ) ;
2017-11-18 00:27:33 +00:00
return RC_SUCCESS ;
2017-08-29 20:35:28 +00:00
}
}
2018-02-21 21:50:23 +00:00
if ( Config . Mode & MODE_EXAMINE )
{
index Index = { 0 } ;
Index . Metadata . Buffer . ID = " IndexMetadata " ;
// TODO(matt): Allow optionally passing a .metadata file as an argument?
CopyString ( Index . Metadata . Path , " %s/%s.metadata " , Config . BaseDir , Config . ProjectID ) ;
ExamineIndex ( & Index ) ;
exit ( RC_SUCCESS ) ;
}
bool HaveConfigErrors = FALSE ;
2017-11-11 00:34:47 +00:00
if ( StringsDiffer ( Config . ProjectID , " " ) )
2017-08-29 20:35:28 +00:00
{
2018-02-21 21:50:23 +00:00
if ( StringLength ( Config . ProjectID ) > MAX_PROJECT_ID_LENGTH )
{
fprintf ( stderr , " \ e[1;31mProjectID \" %s \" is too long (%d/%d characters) \ e[0m \n " , Config . ProjectID , StringLength ( Config . ProjectID ) , MAX_PROJECT_ID_LENGTH ) ;
HaveConfigErrors = TRUE ;
}
2017-11-11 00:34:47 +00:00
Config . Edition = EDITION_PROJECT ;
2017-12-07 21:07:36 +00:00
for ( int ProjectInfoIndex = 0 ; ProjectInfoIndex < ArrayCount ( ProjectInfo ) ; + + ProjectInfoIndex )
{
if ( ! StringsDiffer ( Config . ProjectID , ProjectInfo [ ProjectInfoIndex ] . ProjectID ) )
{
2018-02-21 21:50:23 +00:00
2017-12-07 21:07:36 +00:00
if ( StringsDiffer ( ProjectInfo [ ProjectInfoIndex ] . Medium , " " ) )
{
Config . DefaultMedium = ProjectInfo [ ProjectInfoIndex ] . Medium ;
}
2018-02-21 21:50:23 +00:00
2017-12-07 21:07:36 +00:00
if ( StringsDiffer ( ProjectInfo [ ProjectInfoIndex ] . AltURLPrefix , " " ) )
{
2018-02-21 21:50:23 +00:00
if ( StringLength ( ProjectInfo [ ProjectInfoIndex ] . AltURLPrefix ) > MAX_PLAYER_URL_PREFIX_LENGTH )
{
fprintf ( stderr , " \ e[1;31mPlayer URL Prefix \" %s \" is too long (%d/%d characters) \ e[0m \n " , ProjectInfo [ ProjectInfoIndex ] . AltURLPrefix , StringLength ( ProjectInfo [ ProjectInfoIndex ] . AltURLPrefix ) , MAX_PLAYER_URL_PREFIX_LENGTH ) ;
HaveConfigErrors = TRUE ;
}
else
{
Config . PlayerURLPrefix = ProjectInfo [ ProjectInfoIndex ] . AltURLPrefix ;
}
}
if ( StringLength ( ProjectInfo [ ProjectInfoIndex ] . FullName ) > MAX_PROJECT_NAME_LENGTH )
{
fprintf ( stderr , " \ e[1;31mProject Name \" %s \" is too long (%d/%d characters) \ e[0m \n " , ProjectInfo [ ProjectInfoIndex ] . FullName , StringLength ( ProjectInfo [ ProjectInfoIndex ] . FullName ) , MAX_PROJECT_NAME_LENGTH ) ;
HaveConfigErrors = TRUE ;
2017-12-07 21:07:36 +00:00
}
2018-02-21 21:50:23 +00:00
2017-12-07 21:07:36 +00:00
break ;
}
}
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2018-02-21 21:50:23 +00:00
if ( StringsDiffer ( Config . BaseURL , " " ) & & StringLength ( Config . BaseURL ) > MAX_BASE_URL_LENGTH )
{
fprintf ( stderr , " \ e[1;31mBase URL \" %s \" is too long (%d/%d characters) \ e[0m \n " , Config . BaseURL , StringLength ( Config . BaseURL ) , MAX_BASE_URL_LENGTH ) ;
HaveConfigErrors = TRUE ;
}
if ( StringsDiffer ( Config . IndexLocation , " " ) & & StringLength ( Config . IndexLocation ) > MAX_RELATIVE_PAGE_LOCATION_LENGTH )
{
fprintf ( stderr , " \ e[1;31mRelative Index Page Location \" %s \" is too long (%d/%d characters) \ e[0m \n " , Config . IndexLocation , StringLength ( Config . IndexLocation ) , MAX_RELATIVE_PAGE_LOCATION_LENGTH ) ;
HaveConfigErrors = TRUE ;
}
if ( StringsDiffer ( Config . PlayerLocation , " " ) & & StringLength ( Config . PlayerLocation ) > MAX_RELATIVE_PAGE_LOCATION_LENGTH )
{
fprintf ( stderr , " \ e[1;31mRelative Player Page Location \" %s \" is too long (%d/%d characters) \ e[0m \n " , Config . PlayerLocation , StringLength ( Config . PlayerLocation ) , MAX_RELATIVE_PAGE_LOCATION_LENGTH ) ;
HaveConfigErrors = TRUE ;
}
2018-01-03 22:16:20 +00:00
if ( ! MediumExists ( Config . DefaultMedium ) )
2017-08-29 20:35:28 +00:00
{
2018-01-03 22:16:20 +00:00
// TODO(matt): We'll want to stick around when we have multiple projects configured
2018-02-21 21:50:23 +00:00
HaveConfigErrors = TRUE ;
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2018-02-21 21:50:23 +00:00
if ( HaveConfigErrors ) { exit ( RC_RIP ) ; }
2017-10-18 21:07:29 +00:00
// NOTE(matt): Init MemoryArena (it is global)
2017-08-29 20:35:28 +00:00
MemoryArena . Size = Megabytes ( 4 ) ;
if ( ! ( MemoryArena . Location = calloc ( MemoryArena . Size , 1 ) ) )
{
2017-10-18 21:07:29 +00:00
LogError ( LOG_EMERGENCY , " %s: %s " , Args [ 0 ] , strerror ( errno ) ) ;
2017-09-07 21:41:08 +00:00
return RC_RIP ;
2017-08-29 20:35:28 +00:00
}
MemoryArena . Ptr = MemoryArena . Location ;
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
FILE * MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
fprintf ( MemLog , " Allocated MemoryArena (%d) \n " , MemoryArena . Size ) ;
fclose ( MemLog ) ;
printf ( " Allocated MemoryArena (%d) \n " , MemoryArena . Size ) ;
# endif
2017-09-07 21:41:08 +00:00
2017-10-18 21:07:29 +00:00
# if DEBUG
printf ( " Allocated MemoryArena: %d \n \n " , MemoryArena . Size ) ;
# endif
2017-08-29 20:35:28 +00:00
// NOTE(matt): Tree structure of buffer dependencies
// IncludesPlayer
// Menus
// Player
2017-11-11 00:34:47 +00:00
// ScriptPlayer
2017-10-18 21:07:29 +00:00
//
// IncludesIndex
// Index
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
buffers CollationBuffers ;
2017-10-18 21:07:29 +00:00
if ( ClaimBuffer ( & CollationBuffers . IncludesPlayer , " IncludesPlayer " , Kilobytes ( 1 ) ) = = RC_ARENA_FULL ) { goto RIP ; } ;
2017-11-18 00:27:33 +00:00
if ( ClaimBuffer ( & CollationBuffers . Menus , " Menus " , Kilobytes ( 32 ) ) = = RC_ARENA_FULL ) { goto RIP ; } ;
2017-10-18 21:07:29 +00:00
if ( ClaimBuffer ( & CollationBuffers . Player , " Player " , Kilobytes ( 256 ) ) = = RC_ARENA_FULL ) { goto RIP ; } ;
2017-11-11 00:34:47 +00:00
if ( ClaimBuffer ( & CollationBuffers . ScriptPlayer , " ScriptPlayer " , Kilobytes ( 8 ) ) = = RC_ARENA_FULL ) { goto RIP ; } ;
2017-06-25 18:22:54 +00:00
2017-10-18 21:07:29 +00:00
if ( ClaimBuffer ( & CollationBuffers . IncludesIndex , " IncludesIndex " , Kilobytes ( 1 ) ) = = RC_ARENA_FULL ) { goto RIP ; } ;
2017-11-11 00:34:47 +00:00
if ( ClaimBuffer ( & CollationBuffers . Search , " Search " , Kilobytes ( 32 ) ) = = RC_ARENA_FULL ) { goto RIP ; } ;
2017-11-18 00:27:33 +00:00
// NOTE(matt): Templating
//
// Config will contain paths of multiple templates
// App is running all the time, and picking up changes to the config as we go
2018-01-03 22:16:20 +00:00
// If we find a new template, we first of all Init and Validate it
// In our case here, we just want to straight up Init our three possible templates
// and Validate the Index and Player templates if their locations are set
2017-11-18 00:27:33 +00:00
2018-01-03 22:16:20 +00:00
template * IndexTemplate ; InitTemplate ( & IndexTemplate ) ;
template * PlayerTemplate ; InitTemplate ( & PlayerTemplate ) ;
template * BespokeTemplate ; InitTemplate ( & BespokeTemplate ) ;
2017-06-25 18:22:54 +00:00
2018-01-03 22:16:20 +00:00
if ( StringsDiffer ( Config . TemplatePlayerLocation , " " ) )
2017-08-29 20:35:28 +00:00
{
2018-01-03 22:16:20 +00:00
switch ( ValidateTemplate ( & PlayerTemplate , Config . TemplatePlayerLocation , TEMPLATE_PLAYER ) )
2017-09-07 21:41:08 +00:00
{
case RC_INVALID_TEMPLATE : // Invalid template
case RC_ERROR_FILE : // Could not load template
case RC_ERROR_MEMORY : // Could not allocate memory for template
goto RIP ;
case RC_SUCCESS :
break ;
}
2018-01-03 22:16:20 +00:00
}
2017-09-07 21:41:08 +00:00
2018-01-03 22:16:20 +00:00
if ( Config . Edition = = EDITION_PROJECT & & StringsDiffer ( Config . TemplateIndexLocation , " " ) )
{
switch ( ValidateTemplate ( & IndexTemplate , Config . TemplateIndexLocation , TEMPLATE_INDEX ) )
2017-09-07 21:41:08 +00:00
{
2018-01-03 22:16:20 +00:00
case RC_INVALID_TEMPLATE : // Invalid template
case RC_ERROR_MEMORY : // Could not allocate memory for template
case RC_ERROR_FILE : // Could not load template
goto RIP ;
case RC_SUCCESS :
break ;
2017-09-07 21:41:08 +00:00
}
2017-08-29 20:35:28 +00:00
}
2017-06-25 18:22:54 +00:00
2017-08-29 20:35:28 +00:00
if ( Config . Edition = = EDITION_PROJECT )
{
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
FILE * MemLog = fopen ( " /home/matt/cinera_mem " , " w+ " ) ;
fprintf ( MemLog , " Entered Project Edition \n " ) ;
fclose ( MemLog ) ;
# endif
2017-11-11 00:34:47 +00:00
// TODO(matt): Also log these startup messages?
2017-11-18 00:27:33 +00:00
PrintVersions ( ) ;
printf ( " \n "
2018-01-03 22:16:20 +00:00
" Universal \n "
2017-12-12 23:24:10 +00:00
" Cache Directory: \ e[1;30m(XDG_CACHE_HOME) \ e[0m \t %s \n "
2017-10-18 21:07:29 +00:00
" \n "
2017-11-11 00:34:47 +00:00
" Root \n "
2017-12-12 23:24:10 +00:00
" Directory: \ e[1;30m(-r) \ e[0m \t \t \t %s \n "
" URL: \ e[1;30m(-R) \ e[0m \t \t \t %s \n "
2017-11-11 00:34:47 +00:00
" Paths relative to root \n "
2017-12-12 23:24:10 +00:00
" CSS: \ e[1;30m(-c) \ e[0m \t \t \t %s \n "
" Images: \ e[1;30m(-i) \ e[0m \t \t \t %s \n "
2018-01-03 22:16:20 +00:00
" JS: \ e[1;30m(-j) \ e[0m \t \t \t %s \n "
" \n "
2017-11-11 00:34:47 +00:00
" Project \n "
2018-01-03 22:16:20 +00:00
" ID: \ e[1;30m(-p) \ e[0m \t \t \t \t %s \n "
" Default Medium: \ e[1;30m(-m) \ e[0m \t \t %s \n "
" Style / Theme: \ e[1;30m(-s) \ e[0m \t \t \t %s \n "
" \n "
" Input Paths \n "
" Annotations Directory: \ e[1;30m(-d) \ e[0m \t \t %s \n "
" Templates Directory: \ e[1;30m(-t) \ e[0m \t \t %s \n "
" Index Template: \ e[1;30m(-x) \ e[0m \t \t %s \n "
" Player Template: \ e[1;30m(-y) \ e[0m \t \t %s \n "
" \n "
" Output Paths \n "
2017-12-12 23:24:10 +00:00
" Base \n "
" Directory: \ e[1;30m(-b) \ e[0m \t \t \t %s \n "
" URL: \ e[1;30m(-B) \ e[0m \t \t \t %s \n "
" Paths relative to base \n "
" Index Page: \ e[1;30m(-n) \ e[0m \t \t \t %s \n "
2018-01-03 22:16:20 +00:00
/* NOTE(matt): Here, I think, is where we'll split into sub-projects (...really?...) */
2017-12-12 23:24:10 +00:00
" Player Page(s): \ e[1;30m(-a) \ e[0m \t \t %s \n "
2018-01-03 22:16:20 +00:00
" Player Page Prefix: \ e[1;30m(hardcoded) \ e[0m \t %s \n "
2018-04-01 20:58:53 +00:00
" \n "
" Modes \n "
" Force template integration: \ e[1;30m(-f) \ e[0m \t %s \n "
" Ignore video privacy status: \ e[1;30m(-g) \ e[0m \t %s \n "
" Quit after sync: \ e[1;30m(-q) \ e[0m \t \t %s \n "
" Force quote cache rebuild: \ e[1;30m(-w) \ e[0m \t %s \n "
2017-12-12 23:24:10 +00:00
" \n " ,
2017-11-11 00:34:47 +00:00
2018-01-03 22:16:20 +00:00
Config . CacheDir ,
Config . RootDir ,
StringsDiffer ( Config . RootURL , " " ) ? Config . RootURL : " [empty] " ,
StringsDiffer ( Config . CSSDir , " " ) ? Config . CSSDir : " (same as root) " ,
StringsDiffer ( Config . ImagesDir , " " ) ? Config . ImagesDir : " (same as root) " ,
StringsDiffer ( Config . JSDir , " " ) ? Config . JSDir : " (same as root) " ,
Config . ProjectID ,
Config . DefaultMedium ,
StringsDiffer ( Config . Theme , " " ) ? Config . Theme : Config . ProjectID ,
Config . ProjectDir ,
Config . TemplatesDir ,
StringsDiffer ( Config . TemplateIndexLocation , " " ) ? Config . TemplateIndexLocation : " [none set] " ,
StringsDiffer ( Config . TemplatePlayerLocation , " " ) ? Config . TemplatePlayerLocation : " [none set] " ,
2017-12-12 23:24:10 +00:00
Config . BaseDir ,
StringsDiffer ( Config . BaseURL , " " ) ? Config . BaseURL : " [empty] " ,
StringsDiffer ( Config . IndexLocation , " " ) ? Config . IndexLocation : " (same as base) " ,
StringsDiffer ( Config . PlayerLocation , " " ) ? Config . PlayerLocation : " (directly descended from base) " ,
2018-04-01 20:58:53 +00:00
StringsDiffer ( Config . PlayerURLPrefix , " " ) ? Config . PlayerURLPrefix : Config . ProjectID ,
Config . Mode & MODE_FORCEINTEGRATION ? " on " : " off " ,
Config . Mode & MODE_NOPRIVACY ? " on " : " off " ,
Config . Mode & MODE_ONESHOT ? " on " : " off " ,
Config . Mode & MODE_NOCACHE ? " on " : " off " ) ;
2017-10-18 21:07:29 +00:00
2017-12-12 23:24:10 +00:00
if ( ( StringsDiffer ( Config . IndexLocation , " " ) | | StringsDiffer ( Config . PlayerLocation , " " ) )
& & StringLength ( Config . BaseURL ) = = 0 )
{
printf ( " \ e[1;33mPlease set a Project Base URL (-B) so we can output the Index / Player pages to \n "
" locations other than the defaults \ e[0m \n " ) ;
return ( RC_SUCCESS ) ;
}
2018-02-21 21:50:23 +00:00
index Index = { 0 } ;
Index . Metadata . Buffer . ID = " IndexMetadata " ;
CopyString ( Index . Metadata . Path , " %s/%s.metadata " , Config . BaseDir , Config . ProjectID ) ;
Index . File . Buffer . ID = " IndexFile " ;
CopyString ( Index . File . Path , " %s/%s.index " , Config . BaseDir , Config . ProjectID ) ;
2017-12-12 23:24:10 +00:00
printf ( " ┌╼ Synchronising with annotation files in Project Input Directory ╾┐ \n " ) ;
2018-02-21 21:50:23 +00:00
SyncIndexWithInput ( & Index , & CollationBuffers , IndexTemplate , PlayerTemplate , BespokeTemplate ) ;
2017-12-18 15:10:02 +00:00
if ( Config . Mode & MODE_ONESHOT )
2017-12-18 14:31:05 +00:00
{
goto RIP ;
}
2017-10-18 21:07:29 +00:00
2017-11-11 00:34:47 +00:00
printf ( " \n ┌╼ Monitoring Project Directory for \ e[1;32mnew \ e[0m, \ e[1;33medited \ e[0m and \ e[1;30mdeleted \ e[0m .hmml files ╾┐ \n " ) ;
2017-09-15 01:11:39 +00:00
int inotifyInstance = inotify_init1 ( IN_NONBLOCK ) ;
2017-10-18 21:07:29 +00:00
// NOTE(matt): Do we want to also watch IN_DELETE_SELF events?
int WatchDescriptor = inotify_add_watch ( inotifyInstance , Config . ProjectDir , IN_CLOSE_WRITE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO ) ;
2017-09-15 01:11:39 +00:00
2018-04-01 20:58:53 +00:00
while ( MonitorDirectory ( & Index , & CollationBuffers , IndexTemplate , PlayerTemplate , BespokeTemplate , inotifyInstance , WatchDescriptor ) ! = RC_ARENA_FULL )
2017-09-15 01:11:39 +00:00
{
2018-02-28 01:04:06 +00:00
// TODO(matt): Refetch the quotes and rebuild player pages if needed
//
// Every sixty mins, redownload the quotes and, I suppose, SyncIndexWithInput(). But here we still don't even know
// who the speaker is. To know, we'll probably have to store all quoted speakers in the project's .metadata. Maybe
// postpone this for now, but we will certainly need this to happen
//
// The most ideal solution is possibly that we store quote numbers in the Metadata->Entry, listen for and handle a
// REST PUT request from insobot when a quote changes (unless we're supposed to poll insobot for them?), and rebuild
// the player page(s) accordingly.
2018-04-01 20:58:53 +00:00
//
if ( ! ( Config . Mode & MODE_NOPRIVACY ) & & time ( 0 ) - LastPrivacyCheck > 60 * 60 * 4 )
{
RecheckPrivacy ( & Index , & CollationBuffers , IndexTemplate , PlayerTemplate , BespokeTemplate ) ;
}
2017-10-18 21:07:29 +00:00
sleep ( Config . UpdateInterval ) ;
2017-09-15 01:11:39 +00:00
}
2017-09-07 21:41:08 +00:00
}
else
{
if ( optind = = ArgC )
2017-08-29 20:35:28 +00:00
{
fprintf ( stderr , " %s: requires at least one input .hmml file \n " , Args [ 0 ] ) ;
2017-10-18 21:07:29 +00:00
PrintUsage ( Args [ 0 ] , & DefaultConfig ) ;
2017-09-07 21:41:08 +00:00
goto RIP ;
2017-03-22 02:18:31 +00:00
}
2017-09-07 21:41:08 +00:00
NextFile :
2017-12-24 01:57:11 +00:00
// TODO(matt): Just change the default output location so all these guys won't overwrite each other
2017-08-29 20:35:28 +00:00
for ( int FileIndex = optind ; FileIndex < ArgC ; + + FileIndex )
2017-03-22 02:18:31 +00:00
{
2018-01-04 20:55:51 +00:00
bool HasBespokeTemplate = FALSE ;
char * Ptr = Args [ FileIndex ] ;
Ptr + = ( StringLength ( Args [ FileIndex ] ) - StringLength ( " .hmml " ) ) ;
if ( ! ( StringsDiffer ( Ptr , " .hmml " ) ) )
2017-09-07 21:41:08 +00:00
{
2018-01-04 20:55:51 +00:00
CopyString ( Config . SingleHMMLFilePath , Args [ FileIndex ] ) ;
2018-04-01 20:58:53 +00:00
switch ( HMMLToBuffers ( & CollationBuffers , & BespokeTemplate , Args [ FileIndex ] , 0 ) )
2018-01-04 20:55:51 +00:00
{
// TODO(matt): Actually sort out the fatality of these cases, once we are always-on
case RC_ERROR_FILE :
case RC_ERROR_FATAL :
goto RIP ;
case RC_ERROR_HMML :
case RC_ERROR_MAX_REFS :
case RC_ERROR_QUOTE :
case RC_INVALID_REFERENCE :
if ( FileIndex < ( ArgC - 1 ) ) { goto NextFile ; }
else { goto RIP ; }
case RC_SUCCESS :
break ;
} ;
HasBespokeTemplate = StringsDiffer ( BespokeTemplate - > Metadata . Filename , " " ) ;
switch ( BuffersToHTML ( & CollationBuffers ,
HasBespokeTemplate ? BespokeTemplate : PlayerTemplate ,
0 ,
2018-02-21 21:50:23 +00:00
PAGE_PLAYER , 0 ) )
2018-01-04 20:55:51 +00:00
{
// TODO(matt): Actually sort out the fatality of these cases, once we are always-on
case RC_INVALID_TEMPLATE :
if ( HasBespokeTemplate ) { DeclaimTemplate ( BespokeTemplate ) ; }
if ( FileIndex < ( ArgC - 1 ) ) { goto NextFile ; }
case RC_ERROR_MEMORY :
case RC_ERROR_FILE :
case RC_ARENA_FULL :
goto RIP ;
case RC_SUCCESS :
#if 0
fprintf ( stdout , " \ e[1;32mWritten \ e[0m %s \n " , HasBespokeTemplate ? Config . OutIntegratedLocation : Config . OutLocation ) ;
# endif
if ( HasBespokeTemplate ) { DeclaimTemplate ( BespokeTemplate ) ; }
break ;
} ;
}
2017-03-22 02:18:31 +00:00
}
}
2017-08-29 20:35:28 +00:00
2018-01-03 22:16:20 +00:00
if ( StringsDiffer ( PlayerTemplate - > Metadata . Filename , " " ) )
2017-08-29 20:35:28 +00:00
{
2017-10-18 21:07:29 +00:00
DeclaimTemplate ( PlayerTemplate ) ;
2018-01-03 22:16:20 +00:00
}
if ( Config . Edition = = EDITION_PROJECT & & StringsDiffer ( IndexTemplate - > Metadata . Filename , " " ) )
{
DeclaimTemplate ( IndexTemplate ) ;
2017-08-29 20:35:28 +00:00
}
2017-11-11 00:34:47 +00:00
DeclaimBuffer ( & CollationBuffers . Search ) ;
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( & CollationBuffers . IncludesIndex ) ;
2017-11-11 00:34:47 +00:00
DeclaimBuffer ( & CollationBuffers . ScriptPlayer ) ;
2017-10-18 21:07:29 +00:00
DeclaimBuffer ( & CollationBuffers . Player ) ;
DeclaimBuffer ( & CollationBuffers . Menus ) ;
DeclaimBuffer ( & CollationBuffers . IncludesPlayer ) ;
2017-09-07 21:41:08 +00:00
RIP :
2017-07-29 00:36:14 +00:00
free ( MemoryArena . Location ) ;
2017-10-18 21:07:29 +00:00
# if DEBUG_MEM
MemLog = fopen ( " /home/matt/cinera_mem " , " a+ " ) ;
fprintf ( MemLog , " Freed MemoryArena \n " ) ;
fclose ( MemLog ) ;
printf ( " Freed MemoryArena \n " ) ;
# endif
2017-03-10 14:19:25 +00:00
}