Skip to content

Commit c506710

Browse files
committed
Merge #4469 Refactor module installer to use relative paths internally
2 parents f0a6eb8 + f51877a commit c506710

21 files changed

Lines changed: 308 additions & 380 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ All notable changes to this project will be documented in this file.
4242
- [Netkan] Make CachingHttpService more thread-safe (#4402 by: HebaruSan)
4343
- [Netkan] Lookback for `version_from_asset` (#4407 by: HebaruSan)
4444
- [Build] Migrate Mac app bundle from Mono to dotnet (#4410, #4418 by: HebaruSan; reviewed: lewisfm)
45+
- [Core] Refactor module installer to use relative paths internally (#4469 by: HebaruSan)
4546

4647
## v1.36.0 (Quasar)
4748

Core/IO/ModuleInstaller.cs

Lines changed: 40 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ private List<string> InstallModule(CkanModule
361361
var filters = config.GetGlobalInstallFilters(instance.Game)
362362
.Concat(instance.InstallFilters)
363363
.ToHashSet();
364-
var groups = FindInstallableFiles(module, zipfile, instance, instance.Game)
364+
var groups = FindInstallableFiles(module, zipfile, instance.Game)
365365
// Skip the file if it's a ckan file, these should never be copied to GameData
366366
.Where(instF => !IsInternalCkan(instF.source))
367367
// Check whether each file matches any installation filter
@@ -377,7 +377,7 @@ private List<string> InstallModule(CkanModule
377377
// Find where we're installing identifier.optionalversion.dll
378378
// (file name might not be an exact match with manually installed)
379379
var dllFolders = files
380-
.Select(f => instance.ToRelativeGameDir(f.destination))
380+
.Select(f => f.destination)
381381
.Where(relPath => instance.DllPathToIdentifier(relPath) == module.identifier)
382382
.Select(Path.GetDirectoryName)
383383
.ToHashSet();
@@ -409,7 +409,7 @@ private List<string> InstallModule(CkanModule
409409
{
410410
var fileMsg = string.Join(Environment.NewLine,
411411
conflicting.OrderBy(tuple => tuple.same)
412-
.Select(tuple => $"- {instance.ToRelativeGameDir(tuple.file.destination)} ({(tuple.same ? Properties.Resources.ModuleInstallerFileSame : Properties.Resources.ModuleInstallerFileDifferent)})"));
412+
.Select(tuple => $"- {tuple.file.destination} ({(tuple.same ? Properties.Resources.ModuleInstallerFileSame : Properties.Resources.ModuleInstallerFileDifferent)})"));
413413
if (User.RaiseYesNoDialog(string.Format(Properties.Resources.ModuleInstallerOverwrite,
414414
module.name, fileMsg)))
415415
{
@@ -431,8 +431,8 @@ private List<string> InstallModule(CkanModule
431431
throw new CancelledActionKraken();
432432
}
433433
log.DebugFormat("Copying {0}", file.source.Name);
434-
var path = InstallFile(zipfile, file.source, file.destination, file.makedir,
435-
candidateDuplicates?.GetValueOrDefault((relPath: instance.ToRelativeGameDir(file.destination),
434+
var path = InstallFile(zipfile, file.source, instance.ToAbsoluteGameDir(file.destination), file.makedir,
435+
candidateDuplicates?.GetValueOrDefault((relPath: file.destination,
436436
size: file.source.Size))
437437
?? Array.Empty<string>(),
438438
fileProgress);
@@ -479,21 +479,20 @@ public static bool IsInternalCkan(ZipEntry ze)
479479
IEnumerable<InstallableFile> files,
480480
Registry registry)
481481
=> files.Where(file => !file.source.IsDirectory
482-
&& File.Exists(file.destination)
483-
&& registry.FileOwner(instance.ToRelativeGameDir(file.destination)) == null)
484-
.Select(file =>
485-
{
486-
log.DebugFormat("Comparing {0}", file.destination);
487-
using (Stream zipStream = zip.GetInputStream(file.source))
488-
using (FileStream curFile = new FileStream(file.destination,
489-
FileMode.Open,
490-
FileAccess.Read))
491-
{
492-
return (file,
493-
same: file.source.Size == curFile.Length
494-
&& StreamsEqual(zipStream, curFile));
495-
}
496-
});
482+
&& registry.FileOwner(file.destination) == null)
483+
.Select(file => (file, absPath: instance.ToAbsoluteGameDir(file.destination)))
484+
.Where(tuple => File.Exists(tuple.absPath))
485+
.Select(tuple =>
486+
{
487+
log.DebugFormat("Comparing {0}", tuple.absPath);
488+
using (Stream zipStream = zip.GetInputStream(tuple.file.source))
489+
using (FileStream curFile = File.OpenRead(tuple.absPath))
490+
{
491+
return (tuple.file,
492+
same: tuple.file.source.Size == curFile.Length
493+
&& StreamsEqual(zipStream, curFile));
494+
}
495+
});
497496

498497
/// <summary>
499498
/// Compare the contents of two streams
@@ -547,10 +546,10 @@ private static bool StreamsEqual(Stream s1, Stream s2)
547546
private void DeleteConflictingFiles(IEnumerable<InstallableFile> files)
548547
{
549548
var txFileMgr = new TxFileManager(instance.CkanDir);
550-
foreach (InstallableFile file in files)
549+
foreach (var absPath in files.Select(f => instance.ToAbsoluteGameDir(f.destination)))
551550
{
552-
log.DebugFormat("Trying to delete {0}", file.destination);
553-
txFileMgr.Delete(file.destination);
551+
log.DebugFormat("Trying to delete {0}", absPath);
552+
txFileMgr.Delete(absPath);
554553
}
555554
}
556555

@@ -566,74 +565,31 @@ private void DeleteConflictingFiles(IEnumerable<InstallableFile> files)
566565
///
567566
/// Throws a BadMetadataKraken if the stanza resulted in no files being returned.
568567
/// </summary>
569-
570-
public static List<InstallableFile> FindInstallableFiles(CkanModule module,
571-
ZipFile zipfile,
572-
GameInstance inst)
573-
=> FindInstallableFiles(module, zipfile, inst, inst.Game);
574-
575-
public static List<InstallableFile> FindInstallableFiles(CkanModule module,
576-
ZipFile zipfile,
577-
IGame game)
578-
=> FindInstallableFiles(module, zipfile, null, game);
579-
580-
private static List<InstallableFile> FindInstallableFiles(CkanModule module,
581-
ZipFile zipfile,
582-
GameInstance? inst,
583-
IGame game)
584-
{
585-
try
586-
{
587-
// Use the provided stanzas, or use the default install stanza if they're absent.
588-
return module.install is { Length: > 0 }
589-
? module.install
590-
.SelectMany(stanza => stanza.FindInstallableFiles(zipfile, inst))
591-
.ToList()
592-
: ModuleInstallDescriptor.DefaultInstallStanza(game,
593-
module.identifier)
594-
.FindInstallableFiles(zipfile, inst);
595-
}
596-
catch (BadMetadataKraken kraken)
597-
{
598-
// Decorate our kraken with the current module, as the lower-level
599-
// methods won't know it.
600-
kraken.module ??= module;
601-
throw;
602-
}
603-
}
568+
public static IEnumerable<InstallableFile> FindInstallableFiles(CkanModule module,
569+
ZipFile zipfile,
570+
IGame game)
571+
// Use the provided stanzas, or use the default install stanza if they're absent.
572+
=> module.GetInstallStanzas(game)
573+
.SelectMany(stanza => stanza.FindInstallableFiles(module, zipfile, game))
574+
.ToArray();
604575

605576
/// <summary>
606577
/// Given a module and a path to a zipfile, returns all the files that would be installed
607578
/// from that zip for this module.
608579
///
609-
/// This *will* throw an exception if the file does not exist.
610-
///
611580
/// Throws a BadMetadataKraken if the stanza resulted in no files being returned.
612581
///
613582
/// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null.
614583
/// </summary>
615-
// TODO: Document which exception!
616-
public static List<InstallableFile> FindInstallableFiles(CkanModule module,
617-
string zip_filename,
618-
IGame game)
584+
public static IEnumerable<InstallableFile> FindInstallableFiles(CkanModule module,
585+
string zip_filename,
586+
IGame game)
619587
{
620588
// `using` makes sure our zipfile gets closed when we exit this block.
621589
using (ZipFile zipfile = new ZipFile(zip_filename))
622590
{
623591
log.DebugFormat("Searching {0} using {1} as module", zip_filename, module);
624-
return FindInstallableFiles(module, zipfile, null, game);
625-
}
626-
}
627-
628-
public static List<InstallableFile> FindInstallableFiles(CkanModule module,
629-
string zip_filename,
630-
GameInstance inst)
631-
{
632-
// `using` makes sure our zipfile gets closed when we exit this block.
633-
using (ZipFile zipfile = new ZipFile(zip_filename))
634-
{
635-
log.DebugFormat("Searching {0} using {1} as module", zip_filename, module);
636-
return FindInstallableFiles(module, zipfile, inst);
592+
return FindInstallableFiles(module, zipfile, game);
637593
}
638594
}
639595

@@ -653,10 +609,10 @@ public static List<InstallableFile> FindInstallableFiles(CkanModule module,
653609
filters);
654610

655611
private static IEnumerable<(string path, bool dir, bool exists)> GetModuleContents(
656-
GameInstance instance,
657-
IReadOnlyCollection<string> installed,
658-
HashSet<string> parents,
659-
HashSet<string> filters)
612+
GameInstance instance,
613+
IEnumerable<string> installed,
614+
HashSet<string> parents,
615+
HashSet<string> filters)
660616
=> installed.Where(f => !filters.Any(filt => f.Contains(filt)))
661617
.GroupBy(parents.Contains)
662618
.SelectMany(grp =>
@@ -677,20 +633,18 @@ public static List<InstallableFile> FindInstallableFiles(CkanModule module,
677633
CkanModule module,
678634
HashSet<string> filters)
679635
=> (Cache.GetCachedFilename(module) is string filename
680-
? GetModuleContents(instance,
681-
Utilities.DefaultIfThrows(
682-
() => FindInstallableFiles(module, filename, instance)),
636+
? GetModuleContents(Utilities.DefaultIfThrows(
637+
() => FindInstallableFiles(module, filename, instance.Game)),
683638
filters)
684639
: null)
685640
?? Enumerable.Empty<(string path, bool dir, bool exists)>();
686641

687642
private static IEnumerable<(string path, bool dir, bool exists)>? GetModuleContents(
688-
GameInstance instance,
689643
IEnumerable<InstallableFile>? installable,
690644
HashSet<string> filters)
691645
=> installable?.Where(instF => !filters.Any(filt => instF.destination != null
692646
&& instF.destination.Contains(filt)))
693-
.Select(f => (path: instance.ToRelativeGameDir(f.destination),
647+
.Select(f => (path: f.destination,
694648
dir: f.source.IsDirectory,
695649
exists: true));
696650

Core/Net/NetModuleCache.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ public string GetFileHashSha256(string filePath, IProgress<int> progress, Cancel
142142
/// </summary>
143143
/// <param name="module">The module object corresponding to the download</param>
144144
/// <param name="path">Path to the file to add</param>
145-
/// <param name="progress">Callback to notify as we traverse the input, called with percentages from 0 to 100</param>
145+
/// <param name="progress">Callback to notify as we traverse the input, called with byte counts</param>
146146
/// <param name="description">Description of the file</param>
147147
/// <param name="move">True to move the file, false to copy</param>
148148
/// <param name="cancelToken">Cancellation token to cancel the operation</param>
@@ -209,7 +209,7 @@ public string Store(CkanModule module,
209209
/// </summary>
210210
/// <param name="filename">Path to zip file to check</param>
211211
/// <param name="invalidReason">Description of problem with the file</param>
212-
/// <param name="progress">Callback to notify as we traverse the input, called with percentages from 0 to 100</param>
212+
/// <param name="progress">Callback to notify as we traverse the input, called with byte counts</param>
213213
/// <param name="cancelToken">Cancellation token to cancel the operation</param>
214214
/// <returns>
215215
/// True if valid, false otherwise. See invalidReason param for explanation.

Core/Types/CkanModule.cs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,16 @@ public class CkanModule : IEquatable<CkanModule>
140140
[JsonProperty("install", Order = 24, NullValueHandling = NullValueHandling.Ignore)]
141141
public ModuleInstallDescriptor[]? install;
142142

143+
public ModuleInstallDescriptor[] GetInstallStanzas(IGame game)
144+
=> install switch
145+
{
146+
{ Length: > 0 } => install,
147+
_ => new ModuleInstallDescriptor[]
148+
{
149+
ModuleInstallDescriptor.DefaultInstallStanza(game, identifier)
150+
}
151+
};
152+
143153
[JsonProperty("localizations", Order = 17, NullValueHandling = NullValueHandling.Ignore)]
144154
public string[]? localizations;
145155

@@ -182,23 +192,14 @@ public ModuleVersion spec_version
182192
[JsonProperty("release_date", Order = 30, NullValueHandling = NullValueHandling.Ignore)]
183193
public DateTime? release_date;
184194

185-
// A list of eveything this mod provides.
186-
public List<string> ProvidesList
187-
{
188-
// TODO: Consider caching this, but not in a way that the serialiser will try and
189-
// serialise it.
190-
get
191-
{
192-
var provides = new List<string> { identifier };
193-
194-
if (this.provides != null)
195-
{
196-
provides.AddRange(this.provides);
197-
}
195+
[JsonIgnore]
196+
private string[]? providesList = null;
198197

199-
return provides;
200-
}
201-
}
198+
// A list of eveything this mod provides.
199+
public string[] ProvidesList
200+
=> providesList ??= (provides ?? Enumerable.Empty<string>())
201+
.Prepend(identifier)
202+
.ToArray();
202203

203204
// These are used to simplify the search by dropping special chars.
204205
[JsonIgnore]
@@ -714,10 +715,7 @@ public override string ToString()
714715
=> string.Format("{0} {1}", identifier, version);
715716

716717
public string DescribeInstallStanzas(IGame game)
717-
=> install == null
718-
? ModuleInstallDescriptor.DefaultInstallStanza(game, identifier)
719-
.DescribeMatch()
720-
: string.Join(", ", install.Select(mid => mid.DescribeMatch()));
718+
=> string.Join(", ", GetInstallStanzas(game).Select(mid => mid.DescribeMatch()));
721719

722720
/// <summary>
723721
/// Return an archive.org URL for this download, or null if it's not there.

0 commit comments

Comments
 (0)