Skip to content

Commit

Permalink
Merge #3064 Multi-match 'find', allow 'as' for Ships and GameData
Browse files Browse the repository at this point in the history
  • Loading branch information
HebaruSan committed Jun 11, 2020
2 parents 72cf8e2 + fb0ba99 commit cfb5881
Show file tree
Hide file tree
Showing 8 changed files with 352 additions and 391 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file.
- [GUI] Added Chinese translation of multiple search category fields. (#3053 by: hyx5020; reviewed: HebaruSan)
- [Build] Create a system menu entry for ConsoleUI (#3052 by: HebaruSan; reviewed: DasSkelett)
- [Multiple] Non-parallel GitHub downloads, one progress bar per download (#3054 by: HebaruSan; reviewed: DasSkelett)
- [Core] Multi-match 'find', allow 'as' for Ships and GameData (#3064 by: HebaruSan; reviewed: DasSkelett)

### Bugfixes

Expand Down
217 changes: 4 additions & 213 deletions Core/ModuleInstaller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,216 +510,6 @@ private bool IsReservedDirectory(string path)
|| path == ksp.ShipsThumbsSPH() || path == ksp.Missions();
}

/// <summary>
/// Given a stanza and an open zipfile, returns all files that would be installed
/// for this stanza.
///
/// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null.
///
/// Throws a BadInstallLocationKraken if the install stanza targets an
/// unknown install location (eg: not GameData, Ships, etc)
///
/// Throws a BadMetadataKraken if the stanza resulted in no files being returned.
/// </summary>
/// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception>
internal static List<InstallableFile> FindInstallableFiles(ModuleInstallDescriptor stanza, ZipFile zipfile, KSP ksp)
{
string installDir;
bool makeDirs;
var files = new List<InstallableFile>();

// Normalize the path before doing everything else
string install_to = KSPPathUtils.NormalizePath(stanza.install_to);

// Convert our stanza to a standard `file` type. This is a no-op if it's
// already the basic type.
stanza = stanza.ConvertFindToFile(zipfile);

if (install_to == "GameData" || install_to.StartsWith("GameData/"))
{
// The installation path can be either "GameData" or a sub-directory of "GameData"
// but it cannot contain updirs
if (install_to.Contains("/../") || install_to.EndsWith("/.."))
throw new BadInstallLocationKraken("Invalid installation path: " + install_to);

string subDir = install_to.Substring("GameData".Length); // remove "GameData"
subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir; // remove a "/" at the beginning, if present

// Add the extracted subdirectory to the path of KSP's GameData
installDir = ksp == null ? null : (KSPPathUtils.NormalizePath(ksp.GameData() + "/" + subDir));
makeDirs = true;
}
else if (install_to.StartsWith("Ships"))
{
// Don't allow directory creation in ships directory
makeDirs = false;

switch (install_to)
{
case "Ships":
installDir = ksp?.Ships();
break;
case "Ships/VAB":
installDir = ksp?.ShipsVab();
break;
case "Ships/SPH":
installDir = ksp?.ShipsSph();
break;
case "Ships/@thumbs":
installDir = ksp?.ShipsThumbs();
break;
case "Ships/@thumbs/VAB":
installDir = ksp?.ShipsThumbsVAB();
break;
case "Ships/@thumbs/SPH":
installDir = ksp?.ShipsThumbsSPH();
break;
default:
throw new BadInstallLocationKraken("Unknown install_to " + install_to);
}
}
else
{
switch (install_to)
{
case "Tutorial":
installDir = ksp?.Tutorial();
makeDirs = true;
break;

case "Scenarios":
installDir = ksp?.Scenarios();
makeDirs = true;
break;

case "Missions":
installDir = ksp?.Missions();
makeDirs = true;
break;

case "GameRoot":
installDir = ksp?.GameDir();
makeDirs = false;
break;

default:
throw new BadInstallLocationKraken("Unknown install_to " + install_to);
}
}

// O(N^2) solution, as we're walking the zipfile for each stanza.
// Surely there's a better way, although this is fast enough we may not care.

foreach (ZipEntry entry in zipfile)
{
// Skips things not prescribed by our install stanza.
if (!stanza.IsWanted(entry.Name))
{
continue;
}

// Prepare our file info.
InstallableFile file_info = new InstallableFile
{
source = entry,
makedir = makeDirs,
destination = null
};

// If we have a place to install it, fill that in...
if (installDir != null)
{
// Get the full name of the file.
string outputName = entry.Name;

// Update our file info with the install location
file_info.destination = TransformOutputName(stanza.file, outputName, installDir, stanza.@as);
}

files.Add(file_info);
}

// If we have no files, then something is wrong! (KSP-CKAN/CKAN#93)
if (files.Count == 0)
{
// We have null as the first argument here, because we don't know which module we're installing
throw new BadMetadataKraken(null, String.Format("No files found in {0} to install!", stanza.file));
}

return files;
}

/// <summary>
/// Transforms the name of the output. This will strip the leading directories from the stanza file from
/// output name and then combine it with the installDir.
/// EX: "kOS-1.1/GameData/kOS", "kOS-1.1/GameData/kOS/Plugins/kOS.dll", "GameData" will be transformed
/// to "GameData/kOS/Plugins/kOS.dll"
/// </summary>
/// <returns>The output name.</returns>
/// <param name="file">The file directive of the stanza.</param>
/// <param name="outputName">The name of the file to transform.</param>
/// <param name="installDir">The installation dir where the file should end up with.</param>
internal static string TransformOutputName(string file, string outputName, string installDir, string @as)
{
string leadingPathToRemove = KSPPathUtils.GetLeadingPathElements(file);

// Special-casing, if stanza.file is just "GameData" or "Ships", strip it.
// TODO: Do we need to do anything special for tutorials or GameRoot?
if (
leadingPathToRemove == string.Empty &&
(file == "GameData" || file == "Ships")
)
{
leadingPathToRemove = file;

// It's unclear what the behavior should be in this special case if `as` is specified, therefore
// disallow it.
if (!string.IsNullOrWhiteSpace(@as))
{
throw new BadMetadataKraken(null, "Cannot specify `as` if `file` is GameData or Ships.");
}
}

// If there's a leading path to remove, then we have some extra work that
// needs doing...
if (leadingPathToRemove != string.Empty)
{
string leadingRegEx = "^" + Regex.Escape(leadingPathToRemove) + "/";
if (!Regex.IsMatch(outputName, leadingRegEx))
{
throw new BadMetadataKraken(null,
String.Format("Output file name ({0}) not matching leading path of stanza.file ({1})",
outputName, leadingRegEx
)
);
}
// Strip off leading path name
outputName = Regex.Replace(outputName, leadingRegEx, "");
}

// If an `as` is specified, replace the first component in the file path with the value of `as`
// This works for both when `find` specifies a directory and when it specifies a file.
if (!string.IsNullOrWhiteSpace(@as))
{
if (!@as.Contains("/") && !@as.Contains("\\"))
{
var components = outputName.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
components[0] = @as;

outputName = string.Join("/", components);
}
else
{
throw new BadMetadataKraken(null, "`as` may not include path seperators.");
}
}

// Return our snipped, normalised, and ready to go output filename!
return KSPPathUtils.NormalizePath(
Path.Combine(installDir, outputName)
);
}

/// <summary>
/// Given a module and an open zipfile, return all the files that would be installed
/// for this module.
Expand All @@ -739,13 +529,14 @@ public static List<InstallableFile> FindInstallableFiles(CkanModule module, ZipF
{
foreach (ModuleInstallDescriptor stanza in module.install)
{
files.AddRange(FindInstallableFiles(stanza, zipfile, ksp));
files.AddRange(stanza.FindInstallableFiles(zipfile, ksp));
}
}
else
{
ModuleInstallDescriptor default_stanza = ModuleInstallDescriptor.DefaultInstallStanza(module.identifier, zipfile);
files.AddRange(FindInstallableFiles(default_stanza, zipfile, ksp));
files.AddRange(ModuleInstallDescriptor
.DefaultInstallStanza(module.identifier)
.FindInstallableFiles(zipfile, ksp));
}
}
catch (BadMetadataKraken kraken)
Expand Down
Loading

0 comments on commit cfb5881

Please sign in to comment.