Prevent file clashes

Config parsing now errors-out when it detects clashes between:
    base_dir and search_location, and global_search_dir
    base_dir and player_location

This prevents us from generating search / project pages of multiple
projects (incl. global_search_dir) at the same location, which would
screw up the asset landmarks.
This commit is contained in:
Matt Mascarenhas 2020-05-17 22:50:28 +01:00
parent 877dbab5bd
commit ce9a0e7635
2 changed files with 141 additions and 24 deletions

View File

@ -23,7 +23,7 @@ typedef struct
version CINERA_APP_VERSION = { version CINERA_APP_VERSION = {
.Major = 0, .Major = 0,
.Minor = 7, .Minor = 7,
.Patch = 9 .Patch = 10
}; };
#include <stdarg.h> // NOTE(matt): varargs #include <stdarg.h> // NOTE(matt): varargs
@ -907,6 +907,18 @@ StringsMatch(string A, string B)
return FALSE; return FALSE;
} }
bool
StringsMatchSized(string A, int Size, string B)
{
int i = 0;
while(i < A.Length && i < B.Length && A.Base[i] == B.Base[i]) { ++i; }
if(i == Size)
{
return TRUE;
}
return FALSE;
}
bool bool
StringsMatchCaseInsensitive(string A, string B) StringsMatchCaseInsensitive(string A, string B)
{ {
@ -2723,6 +2735,15 @@ GetBaseFilename(string Filepath,
return Result; return Result;
} }
string
TrimString(string S, uint32_t CharsFromStart, uint32_t CharsFromEnd)
{
string Result = S;
Result.Length -= (CharsFromStart + CharsFromEnd);
Result.Base += CharsFromStart;
return Result;
}
char *WatchTypeStrings[] = char *WatchTypeStrings[] =
{ {
"WT_HMML", "WT_HMML",
@ -12351,16 +12372,6 @@ DeleteFromDB(neighbourhood *N, string BaseFilename)
return Entry ? RC_SUCCESS : RC_NOOP; return Entry ? RC_SUCCESS : RC_NOOP;
} }
string
TrimString(string S, uint32_t CharsFromStart, uint32_t CharsFromEnd)
{
string Result = S;
Result.Length -= (CharsFromStart + CharsFromEnd);
Result.Base += CharsFromStart;
return Result;
}
void void
GenerateFilterOfProjectAndChildren(buffer *Filter, db_header_project *StoredP, project *P, bool *SearchRequired, bool TopLevel, bool *RequiresCineraJS) GenerateFilterOfProjectAndChildren(buffer *Filter, db_header_project *StoredP, project *P, bool *SearchRequired, bool TopLevel, bool *RequiresCineraJS)
{ {

View File

@ -2958,7 +2958,8 @@ PushVariantUniquely(resolution_errors *E, variant_strings *VS, string Path, uint
typedef struct typedef struct
{ {
string String; string String0;
string String1;
project **Projects; project **Projects;
uint64_t ProjectCount; uint64_t ProjectCount;
} config_string_association; } config_string_association;
@ -2966,7 +2967,8 @@ typedef struct
typedef struct typedef struct
{ {
config_string_association *Associations; config_string_association *Associations;
config_identifier_id ID; config_identifier_id ID0;
config_identifier_id ID1;
uint64_t Count; uint64_t Count;
} config_string_associations; } config_string_associations;
@ -2974,6 +2976,8 @@ typedef struct
{ {
variant_strings VariantStrings; variant_strings VariantStrings;
config_string_associations HMMLDirs; config_string_associations HMMLDirs;
config_string_associations BaseDirAndSearchLocation;
config_string_associations BaseDirAndPlayerLocation;
} config_verifiers; } config_verifiers;
void void
@ -3197,14 +3201,60 @@ AddProjectToAssociation(config_string_association *A, project *P)
} }
void void
PushHMMLDirAssociation(config_string_associations *HMMLDirs, string *S, project *P) PushAssociation(config_string_associations *HMMLDirs, string *S0, string *S1, project *P)
{ {
HMMLDirs->Associations = Fit(HMMLDirs->Associations, sizeof(*HMMLDirs->Associations), HMMLDirs->Count, 8, TRUE); HMMLDirs->Associations = Fit(HMMLDirs->Associations, sizeof(*HMMLDirs->Associations), HMMLDirs->Count, 8, TRUE);
HMMLDirs->Associations[HMMLDirs->Count].String = *S; if(S0) { HMMLDirs->Associations[HMMLDirs->Count].String0 = *S0; }
if(S1) { HMMLDirs->Associations[HMMLDirs->Count].String1 = *S1; }
AddProjectToAssociation(&HMMLDirs->Associations[HMMLDirs->Count], P); AddProjectToAssociation(&HMMLDirs->Associations[HMMLDirs->Count], P);
++HMMLDirs->Count; ++HMMLDirs->Count;
} }
config_string_association *
GetAssociation(config_string_associations *Set, string String0, string String1)
{
config_string_association *Result = 0;
for(int i = 0; i < Set->Count; ++i)
{
config_string_association *This = Set->Associations + i;
if(StringsMatch(String0, This->String0) && StringsMatch(String1, This->String1))
{
Result = This;
break;
}
}
return Result;
}
config_string_association *
GetGlobalPathAssociation(config_string_associations *Set, string Path)
{
config_string_association *Result = 0;
for(int i = 0; i < Set->Count; ++i)
{
config_string_association *This = Set->Associations + i;
if(StringsMatchSized(Path, This->String0.Length, This->String0))
{
Path = TrimString(Path, This->String0.Length, 0);
if(Path.Length == 0 && This->String1.Length == 0)
{
Result = This;
break;
}
else if(StringsMatchSized(Path, 1, Wrap0("/")))
{
Path = TrimString(Path, 1, 0);
if(Path.Length > 0 && StringsMatchSized(Path, This->String1.Length, This->String1))
{
Result = This;
break;
}
}
}
}
return Result;
}
void void
SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HMMLDirs, project *P, scope_tree *ProjectTree, config_pair *HMMLDir) SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HMMLDirs, project *P, scope_tree *ProjectTree, config_pair *HMMLDir)
{ {
@ -3212,7 +3262,7 @@ SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HM
bool Clashed = FALSE; bool Clashed = FALSE;
for(int i = 0; i < HMMLDirs->Count; ++i) for(int i = 0; i < HMMLDirs->Count; ++i)
{ {
if(StringsMatch(HMMLDirs->Associations[i].String, Result)) if(StringsMatch(HMMLDirs->Associations[i].String0, Result))
{ {
AddProjectToAssociation(&HMMLDirs->Associations[i], P); AddProjectToAssociation(&HMMLDirs->Associations[i], P);
PushError(E, S_ERROR, &HMMLDir->Position, IDENT_HMML_DIR); PushError(E, S_ERROR, &HMMLDir->Position, IDENT_HMML_DIR);
@ -3223,7 +3273,7 @@ SetUniqueHMMLDir(config *C, resolution_errors *E, config_string_associations *HM
if(!Clashed) if(!Clashed)
{ {
PushHMMLDirAssociation(HMMLDirs, &Result, P); PushAssociation(HMMLDirs, &Result, 0, P);
} }
P->HMMLDir = Result; P->HMMLDir = Result;
} }
@ -3399,6 +3449,28 @@ PushProject(config *C, resolution_errors *E, config_verifiers *V, project *P, sc
} }
} }
config_string_association *BaseDirAndSearchLocation = GetAssociation(&V->BaseDirAndSearchLocation, P->BaseDir, P->SearchLocation);
if(BaseDirAndSearchLocation)
{
PushError(E, S_ERROR, 0, IDENT_SEARCH_LOCATION);
AddProjectToAssociation(BaseDirAndSearchLocation, P);
}
else
{
PushAssociation(&V->BaseDirAndSearchLocation, &P->BaseDir, &P->SearchLocation, P);
}
config_string_association *BaseDirAndPlayerLocation = GetAssociation(&V->BaseDirAndPlayerLocation, P->BaseDir, P->PlayerLocation);
if(BaseDirAndPlayerLocation)
{
PushError(E, S_ERROR, 0, IDENT_PLAYER_LOCATION);
AddProjectToAssociation(BaseDirAndPlayerLocation, P);
}
else
{
PushAssociation(&V->BaseDirAndPlayerLocation, &P->BaseDir, &P->PlayerLocation, P);
}
if(!P->DefaultMedium) if(!P->DefaultMedium)
{ {
ResetPen(&C->ResolvedVariables); ResetPen(&C->ResolvedVariables);
@ -3511,16 +3583,35 @@ PrintAssociationClashes(config_string_associations *A)
config_string_association *Association = A->Associations + i; config_string_association *Association = A->Associations + i;
if(Association->ProjectCount > 1) if(Association->ProjectCount > 1)
{ {
string ID = Wrap0(ConfigIdentifiers[A->ID].String); ConfigError(0, 0, S_ERROR, "Multiple projects share the same:", 0);
ConfigError(0, 0, S_ERROR, "Multiple projects share the same ", &ID);
fprintf(stderr, " "); config_pair Assoc0 = { .Key = A->ID0, .Value = Association->String0 };
PrintStringC(CS_MAGENTA_BOLD, Association->String); int IndentLevel = 2;
PrintPair(&Assoc0, ": ", FALSE, IndentLevel, FALSE, FALSE);
if(A->ID1 != IDENT_NULL)
{
config_pair Assoc1 = { .Key = A->ID1, .Value = Association->String1 };
PrintPair(&Assoc1, ": ", FALSE, IndentLevel, TRUE, FALSE);
}
fprintf(stderr, "\n"); fprintf(stderr, "\n");
for(int j = 0; j < Association->ProjectCount; ++j) for(int j = 0; j < Association->ProjectCount; ++j)
{ {
project *P = Association->Projects[j]; project *P = Association->Projects[j];
fprintf(stderr, " "); fprintf(stderr, " ");
PrintStringC(CS_CYAN, P->Lineage); if(P)
{
PrintStringC(CS_CYAN, P->Lineage);
}
else
{
PrintStringC(CS_RED, Wrap0("[global scope: "));
// TODO(matt): Maybe generalise this, next time we're in this situation
if(A->ID0 == IDENT_BASE_DIR && A->ID1 == IDENT_SEARCH_LOCATION)
{
PrintC(CS_YELLOW_BOLD, ConfigIdentifiers[IDENT_GLOBAL_SEARCH_DIR].String);
}
PrintStringC(CS_RED, Wrap0("]"));
}
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
} }
@ -3609,6 +3700,8 @@ FreeVariants(variant_strings *V)
void void
FreeVerifiers(config_verifiers *Verifiers) FreeVerifiers(config_verifiers *Verifiers)
{ {
FreeAssociations(&Verifiers->BaseDirAndSearchLocation);
FreeAssociations(&Verifiers->BaseDirAndPlayerLocation);
FreeAssociations(&Verifiers->HMMLDirs); FreeAssociations(&Verifiers->HMMLDirs);
FreeVariants(&Verifiers->VariantStrings); FreeVariants(&Verifiers->VariantStrings);
} }
@ -3623,7 +3716,11 @@ ResolveVariables(scope_tree *S)
InitBook(&Result->Project, sizeof(project), 8, MBT_PROJECT); InitBook(&Result->Project, sizeof(project), 8, MBT_PROJECT);
resolution_errors Errors = {}; resolution_errors Errors = {};
config_verifiers Verifiers = {}; config_verifiers Verifiers = {};
Verifiers.HMMLDirs.ID = IDENT_HMML_DIR; Verifiers.HMMLDirs.ID0 = IDENT_HMML_DIR;
Verifiers.BaseDirAndSearchLocation.ID0 = IDENT_BASE_DIR;
Verifiers.BaseDirAndSearchLocation.ID1 = IDENT_SEARCH_LOCATION;
Verifiers.BaseDirAndPlayerLocation.ID0 = IDENT_BASE_DIR;
Verifiers.BaseDirAndPlayerLocation.ID1 = IDENT_PLAYER_LOCATION;
for(int i = 0; i < S->PairCount; ++i) for(int i = 0; i < S->PairCount; ++i)
{ {
@ -3752,6 +3849,13 @@ ResolveVariables(scope_tree *S)
} }
} }
config_string_association *BaseDirAndSearchLocation = GetGlobalPathAssociation(&Verifiers.BaseDirAndSearchLocation, Result->GlobalSearchDir);
if(BaseDirAndSearchLocation)
{
PushError(&Errors, S_ERROR, 0, IDENT_GLOBAL_SEARCH_DIR);
AddProjectToAssociation(BaseDirAndSearchLocation, 0);
}
// TODO(matt): Mandate that, if a GlobalSearchDir or GlobalSearchURL is set, then both must be set // TODO(matt): Mandate that, if a GlobalSearchDir or GlobalSearchURL is set, then both must be set
if(!Result->GlobalTheme.Length) if(!Result->GlobalTheme.Length)
{ {
@ -3762,6 +3866,8 @@ ResolveVariables(scope_tree *S)
if(Errors.ErrorCount > 0) if(Errors.ErrorCount > 0)
{ {
PrintAssociationClashes(&Verifiers.HMMLDirs); PrintAssociationClashes(&Verifiers.HMMLDirs);
PrintAssociationClashes(&Verifiers.BaseDirAndSearchLocation);
PrintAssociationClashes(&Verifiers.BaseDirAndPlayerLocation);
PrintVariantInconsistencies(&Verifiers.VariantStrings); PrintVariantInconsistencies(&Verifiers.VariantStrings);
Free(Result); Free(Result);
} }