using ModpackUpdater.Model; using System.IO.Compression; namespace ModpackUpdater.Manager; public class ModpackInstaller(ModpackConfig updateConfig, ModpackInfo modpackInfo) { private class LocalZipCacheEntry { public string DownloadUrl { get; set; } public string ExtractedZipPath { get; set; } } public event InstallProgessUpdatedEventHandler InstallProgessUpdated; public delegate void InstallProgessUpdatedEventHandler(UpdateCheckResult result, int processedSyncs); public event CheckingProgressUpdatedEventHandler CheckingProgressUpdated; public delegate void CheckingProgressUpdatedEventHandler(int toCheck, int processed); private readonly HttpClient httpClient = new(); ~ModpackInstaller() { httpClient.Dispose(); } private async Task DownloadUpdateInfos() { var content = await httpClient.GetStringAsync(updateConfig.UpdateUrl); return UpdateInfos.Parse(content); } private async Task DownloadInstallInfos() { var content = await httpClient.GetStringAsync(updateConfig.InstallUrl); return InstallInfos.Parse(content); } public async Task Check(UpdateCheckOptions options) { InstallInfos installInfos = null; UpdateInfos updateInfos = null; var hasConfig = modpackInfo.Exists; var result = new UpdateCheckResult(); if (updateConfig.Maintenance && !options.IgnoreMaintenance) { result.IsInMaintenance = true; return result; } if (!hasConfig) { installInfos = await DownloadInstallInfos(); if (installInfos is not null && installInfos.Actions.Count != 0) { var actions = installInfos.Actions.Where(n => n.Side.IsSide(options.Side)); if (actions.Any()) { result.Actions.AddRange(installInfos.Actions); result.LatestVersion = installInfos.Version; } } if (result.Actions.Count == 0) result.HasError = true; } if (options.AllowUpdaterAfterInstall) { updateInfos = await DownloadUpdateInfos(); if (updateInfos is not null && updateInfos.Updates.Count != 0) { var updatesOrderes = updateInfos.Updates.OrderByDescending(n => n.Version); result.LatestVersion = updatesOrderes.First().Version; result.CurrentVersion = modpackInfo.Version; result.IsInstalled = true; var checkingVersionIndex = 0; var checkingVersion = updatesOrderes.ElementAtOrDefault(checkingVersionIndex); while (checkingVersion is not null && checkingVersion.Version > result.CurrentVersion) { var actionsToAdd = new List(); foreach (var action in checkingVersion.Actions) { if (action.Side.IsSide(options.Side) && !result.Actions.Any(n => n.DestPath == action.DestPath)) actionsToAdd.Add(action); } result.Actions.InsertRange(0, actionsToAdd); checkingVersionIndex += 1; checkingVersion = updatesOrderes.ElementAtOrDefault(checkingVersionIndex); } } else result.HasError = true; } return result; } public async Task Install(UpdateCheckResult checkResult) { int processed = 0; var localZipCache = new List(); foreach (InstallAction iaction in checkResult.Actions) { string destFilePath = Path.Combine(modpackInfo.LocaLPath, iaction.DestPath); if (iaction is UpdateAction uaction) { switch (uaction.Type) { case UpdateActionType.Update: await InstallFile(destFilePath, uaction.DownloadUrl, uaction.IsZip, uaction.ZipPath, localZipCache); break; case UpdateActionType.Delete: { if (uaction.IsDirectory) { if (Directory.Exists(destFilePath)) Directory.Delete(destFilePath, true); } else if (File.Exists(destFilePath)) File.Delete(destFilePath); } break; case UpdateActionType.Copy: { var srcFilePath = Path.Combine(modpackInfo.LocaLPath, uaction.SrcPath); if (uaction.IsDirectory) { if (Directory.Exists(srcFilePath)) Extensions.CopyDirectory(srcFilePath, destFilePath, true); } else if (File.Exists(srcFilePath)) { Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)); File.Copy(srcFilePath, destFilePath, true); } } break; case UpdateActionType.Move: { var srcFilePath = Path.Combine(modpackInfo.LocaLPath, uaction.SrcPath); if (uaction.IsDirectory) { if (Directory.Exists(srcFilePath)) Directory.Move(srcFilePath, destFilePath); } else if (File.Exists(srcFilePath)) { Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)); File.Move(srcFilePath, destFilePath, true); } } break; } } else await InstallFile(destFilePath, iaction.DownloadUrl, iaction.IsZip, iaction.ZipPath, localZipCache); processed += 1; InstallProgessUpdated?.Invoke(checkResult, processed); } // Save new modpack info modpackInfo.Version = checkResult.LatestVersion; modpackInfo.ConfigUrl = updateConfig.ConfigUrl; modpackInfo.Save(); // Delete cached zip files foreach (var task in localZipCache) Directory.Delete(task.ExtractedZipPath, true); return true; } private async Task InstallFile(string destFilePath, string sourceUrl, bool isZip, string zipPath, List localZipCache) { Directory.CreateDirectory(Path.GetDirectoryName(destFilePath)); if (!isZip || localZipCache.FirstOrDefault(n => n.DownloadUrl == sourceUrl) is not LocalZipCacheEntry cachedZipInfo) { // Download var fsDestinationPath = isZip ? Path.Combine(Path.GetTempPath(), $"mc_update_file_{DateTime.Now.ToBinary()}.zip") : destFilePath; var sRemote = await httpClient.GetStreamAsync(sourceUrl); var fs = new FileStream(fsDestinationPath, FileMode.Create, FileAccess.ReadWrite); await sRemote.CopyToAsync(fs); await fs.FlushAsync(); sRemote.Close(); fs.Close(); // Extract if (isZip) { // Extract files var zipDir = Path.Combine(Path.GetDirectoryName(fsDestinationPath), Path.GetFileNameWithoutExtension(fsDestinationPath)); ZipFile.ExtractToDirectory(fsDestinationPath, zipDir); // Create cache entry cachedZipInfo = new() { DownloadUrl = sourceUrl, ExtractedZipPath = zipDir }; localZipCache.Add(cachedZipInfo); // Remofe temp zip file File.Delete(fsDestinationPath); } else cachedZipInfo = null; } // Handle zip file content if (cachedZipInfo != null) { // Copy content var zipSrc = Path.Combine(cachedZipInfo.ExtractedZipPath, zipPath); Extensions.CopyDirectory(zipSrc, destFilePath, true); } } }