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 = {
.Major = 0,
.Minor = 7,
.Patch = 9
.Patch = 10
};
#include <stdarg.h> // NOTE(matt): varargs
@ -907,6 +907,18 @@ StringsMatch(string A, string B)
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
StringsMatchCaseInsensitive(string A, string B)
{
@ -2723,6 +2735,15 @@ GetBaseFilename(string Filepath,
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[] =
{
"WT_HMML",
@ -12351,16 +12372,6 @@ DeleteFromDB(neighbourhood *N, string BaseFilename)
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
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
{
string String;
string String0;
string String1;
project **Projects;
uint64_t ProjectCount;
} config_string_association;
@ -2966,7 +2967,8 @@ typedef struct
typedef struct
{
config_string_association *Associations;
config_identifier_id ID;
config_identifier_id ID0;
config_identifier_id ID1;
uint64_t Count;
} config_string_associations;
@ -2974,6 +2976,8 @@ typedef struct
{
variant_strings VariantStrings;
config_string_associations HMMLDirs;
config_string_associations BaseDirAndSearchLocation;
config_string_associations BaseDirAndPlayerLocation;
} config_verifiers;
void
@ -3197,14 +3201,60 @@ AddProjectToAssociation(config_string_association *A, project *P)
}
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[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);
++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
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;
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);
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)
{
PushHMMLDirAssociation(HMMLDirs, &Result, P);
PushAssociation(HMMLDirs, &Result, 0, P);
}
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)
{
ResetPen(&C->ResolvedVariables);
@ -3511,16 +3583,35 @@ PrintAssociationClashes(config_string_associations *A)
config_string_association *Association = A->Associations + i;
if(Association->ProjectCount > 1)
{
string ID = Wrap0(ConfigIdentifiers[A->ID].String);
ConfigError(0, 0, S_ERROR, "Multiple projects share the same ", &ID);
fprintf(stderr, " ");
PrintStringC(CS_MAGENTA_BOLD, Association->String);
ConfigError(0, 0, S_ERROR, "Multiple projects share the same:", 0);
config_pair Assoc0 = { .Key = A->ID0, .Value = Association->String0 };
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");
for(int j = 0; j < Association->ProjectCount; ++j)
{
project *P = Association->Projects[j];
fprintf(stderr, " ");
PrintStringC(CS_CYAN, P->Lineage);
fprintf(stderr, " ");
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");
}
}
@ -3609,6 +3700,8 @@ FreeVariants(variant_strings *V)
void
FreeVerifiers(config_verifiers *Verifiers)
{
FreeAssociations(&Verifiers->BaseDirAndSearchLocation);
FreeAssociations(&Verifiers->BaseDirAndPlayerLocation);
FreeAssociations(&Verifiers->HMMLDirs);
FreeVariants(&Verifiers->VariantStrings);
}
@ -3623,7 +3716,11 @@ ResolveVariables(scope_tree *S)
InitBook(&Result->Project, sizeof(project), 8, MBT_PROJECT);
resolution_errors Errors = {};
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)
{
@ -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
if(!Result->GlobalTheme.Length)
{
@ -3762,6 +3866,8 @@ ResolveVariables(scope_tree *S)
if(Errors.ErrorCount > 0)
{
PrintAssociationClashes(&Verifiers.HMMLDirs);
PrintAssociationClashes(&Verifiers.BaseDirAndSearchLocation);
PrintAssociationClashes(&Verifiers.BaseDirAndPlayerLocation);
PrintVariantInconsistencies(&Verifiers.VariantStrings);
Free(Result);
}