public async Task <IAsset> StoreAsset(IDownloadedAsset downloadedAsset) { switch (downloadedAsset.ApiAsset.Type) { case AssetType.Blueprint: case AssetType.Savegame: case AssetType.Scenario: { // Create the directory where the asset should be stored and create a path to where the asset should be stored. var storagePath = _parkitect.Paths.GetAssetPath(downloadedAsset.ApiAsset.Type); var assetPath = Path.Combine(storagePath, downloadedAsset.FileName); Directory.CreateDirectory(storagePath); _log.WriteLine($"Storing asset to {assetPath}."); // If the file already exists, add a number behind the file name. if (File.Exists(assetPath)) { _log.WriteLine("Asset already exists, comparing hashes."); // Compute hash of downloaded asset to match with installed hash. var validHash = downloadedAsset.Stream.CreateMD5Checksum(); if (validHash.SequenceEqual(File.OpenRead(assetPath).CreateMD5Checksum())) { _log.WriteLine("Asset hashes match, aborting."); return(null); } _log.WriteLine("Asset hashes mismatch, computing new file name."); // Separate the filename and the extension. var attempt = 1; var fileName = Path.GetFileNameWithoutExtension(downloadedAsset.FileName); var fileExtension = Path.GetExtension(downloadedAsset.FileName); // Update the path to where the the asset should be stored by adding a number behind the name until an available filename has been found. do { assetPath = Path.Combine(storagePath, $"{fileName} ({++attempt}){fileExtension}"); if (File.Exists(assetPath) && validHash.SequenceEqual(File.OpenRead(assetPath).CreateMD5Checksum())) { return(null); } } while (File.Exists(assetPath)); _log.WriteLine($"Newly computed path is {assetPath}."); } _log.WriteLine("Writing asset to file."); // Write the stream to a file at the asset path. using (var fileStream = File.Create(assetPath)) { downloadedAsset.Stream.Seek(0, SeekOrigin.Begin); await downloadedAsset.Stream.CopyToAsync(fileStream); } var meta = new AssetMetadata { Id = downloadedAsset.ApiAsset.Id // InstalledVersion = downloadedAsset.ApiAsset.UpdatedAt }; // TODO: Re-add installed version _assetMetadataStorage.StoreMetadata(downloadedAsset.ApiAsset.Type, assetPath, meta); var cachedData = await _assetCachedDataStorage.GetData(downloadedAsset.ApiAsset.Type, meta, assetPath); Asset createdAsset = null; switch (downloadedAsset.ApiAsset.Type) { case AssetType.Blueprint: createdAsset = new BlueprintAsset(assetPath, meta, cachedData); break; case AssetType.Savegame: createdAsset = new SavegameAsset(assetPath, meta, cachedData as AssetWithImageCachedData); break; case AssetType.Scenario: createdAsset = new ScenarioAsset(assetPath, meta, cachedData as AssetWithImageCachedData); break; } OnAssetAdded(new AssetEventArgs(createdAsset)); return(createdAsset); } case AssetType.Mod: { _log.WriteLine("Attempting to open mod stream."); using (var zip = new ZipArchive(downloadedAsset.Stream, ZipArchiveMode.Read)) { // Compute name of main directory inside archive. var mainFolder = zip.Entries.FirstOrDefault()?.FullName; if (mainFolder == null) { throw new Exception("invalid archive"); } _log.WriteLine($"Mod archive main folder is {mainFolder}."); // Find the mod.json file. Yay for / \ path divider compatibility. var modJsonPath = Path.Combine(mainFolder, "mod.json").Replace('/', '\\'); var modJson = zip.Entries.FirstOrDefault(e => e.FullName.Replace('/', '\\') == modJsonPath); // Read mod.json. if (modJson == null) { throw new Exception("mod is missing mod.json file"); } using (var streamReader = new StreamReader(modJson.Open())) { var modInformationString = await streamReader.ReadToEndAsync(); var modInformation = JsonConvert.DeserializeObject <ModInformation>(modInformationString); var meta = new ModMetadata { Id = downloadedAsset.ApiAsset.Id, // InstalledVersion = downloadedAsset.ApiAsset.UpdatedAt, Tag = downloadedAsset.Info.Tag, Repository = downloadedAsset.Info.Repository }; // TODO: Re-add installed version var installationPath = Path.Combine(_parkitect.Paths.GetAssetPath(AssetType.Mod), downloadedAsset.Info.Repository.Replace('/', '@')); // TODO: Should actually try and look if the mod has been updated since and delete the whole folder. if (Directory.Exists(installationPath)) { if (File.Exists(Path.Combine(installationPath, "modinfo.meta"))) { File.Delete(Path.Combine(installationPath, "modinfo.meta")); } if (File.Exists(Path.Combine(installationPath, "moddata.cache"))) { File.Delete(Path.Combine(installationPath, "moddata.cache")); } } _log.WriteLine($"mod.json was deserialized to mod object '{modInformation}'."); // Set default mod properties. modInformation.IsEnabled = true; modInformation.IsDevelopment = false; // Find previous version of mod. // TODO uninstall previous versions // Install mod. _log.WriteLine("Copying mod to mods folder."); foreach (var entry in zip.Entries) { if (!entry.FullName.StartsWith(mainFolder)) { continue; } // Compute path. var partDir = entry.FullName.Substring(mainFolder.Length); var path = Path.Combine(installationPath, partDir); var ignoredFiles = new[] { "moddata.cache", "modinfo.meta", "mod.log" }; if (ignoredFiles.Contains(partDir)) { continue; } if (string.IsNullOrEmpty(entry.Name)) { _log.WriteLine($"Creating directory '{path}'."); Directory.CreateDirectory(path); } else { _log.WriteLine($"Storing mod file '{path}'."); using (var openStream = entry.Open()) using (var fileStream = File.OpenWrite(path)) await openStream.CopyToAsync(fileStream); } } _log.WriteLine("Register installation to API."); _website.API.RegisterDownload(downloadedAsset.ApiAsset.Id); _assetMetadataStorage.StoreMetadata(downloadedAsset.ApiAsset.Type, installationPath, meta); var cachedData = await _assetCachedDataStorage.GetData(downloadedAsset.ApiAsset.Type, meta, installationPath); modInformationString = JsonConvert.SerializeObject(modInformation); File.WriteAllText(Path.Combine(installationPath, "mod.json"), modInformationString); var createdAsset = new ModAsset(installationPath, meta, cachedData as AssetWithImageCachedData, modInformation); OnAssetAdded(new AssetEventArgs(createdAsset)); return(createdAsset); } } } default: throw new Exception("unknown asset type"); } }
public async Task<IAsset> StoreAsset(IDownloadedAsset downloadedAsset) { switch (downloadedAsset.ApiAsset.Type) { case AssetType.Blueprint: case AssetType.Savegame: { // Create the directory where the asset should be stored and create a path to where the asset should be stored. var storagePath = _parkitect.Paths.GetAssetPath(downloadedAsset.ApiAsset.Type); var assetPath = Path.Combine(storagePath, downloadedAsset.FileName); Directory.CreateDirectory(storagePath); _log.WriteLine($"Storing asset to {assetPath}."); // If the file already exists, add a number behind the file name. if (File.Exists(assetPath)) { _log.WriteLine("Asset already exists, comparing hashes."); // Compute hash of downloaded asset to match with installed hash. var validHash = downloadedAsset.Stream.CreateMD5Checksum(); if (validHash.SequenceEqual(File.OpenRead(assetPath).CreateMD5Checksum())) { _log.WriteLine("Asset hashes match, aborting."); return null; } _log.WriteLine("Asset hashes mismatch, computing new file name."); // Separate the filename and the extension. var attempt = 1; var fileName = Path.GetFileNameWithoutExtension(downloadedAsset.FileName); var fileExtension = Path.GetExtension(downloadedAsset.FileName); // Update the path to where the the asset should be stored by adding a number behind the name until an available filename has been found. do { assetPath = Path.Combine(storagePath, $"{fileName} ({++attempt}){fileExtension}"); if (File.Exists(assetPath) && validHash.SequenceEqual(File.OpenRead(assetPath).CreateMD5Checksum())) return null; } while (File.Exists(assetPath)); _log.WriteLine($"Newly computed path is {assetPath}."); } _log.WriteLine("Writing asset to file."); // Write the stream to a file at the asset path. using (var fileStream = File.Create(assetPath)) { downloadedAsset.Stream.Seek(0, SeekOrigin.Begin); await downloadedAsset.Stream.CopyToAsync(fileStream); } var meta = new AssetMetadata { Id = downloadedAsset.ApiAsset.Id // InstalledVersion = downloadedAsset.ApiAsset.UpdatedAt };// TODO: Re-add installed version _assetMetadataStorage.StoreMetadata(downloadedAsset.ApiAsset.Type, assetPath, meta); var cachedData = await _assetCachedDataStorage.GetData(downloadedAsset.ApiAsset.Type, meta, assetPath); Asset createdAsset = null; switch (downloadedAsset.ApiAsset.Type) { case AssetType.Blueprint: createdAsset = new BlueprintAsset(assetPath, meta, cachedData); break; case AssetType.Savegame: createdAsset = new SavegameAsset(assetPath, meta, cachedData as AssetWithImageCachedData); break; } OnAssetAdded(new AssetEventArgs(createdAsset)); return createdAsset; } case AssetType.Mod: { _log.WriteLine("Attempting to open mod stream."); using (var zip = new ZipArchive(downloadedAsset.Stream, ZipArchiveMode.Read)) { // Compute name of main directory inside archive. var mainFolder = zip.Entries.FirstOrDefault()?.FullName; if (mainFolder == null) throw new Exception("invalid archive"); _log.WriteLine($"Mod archive main folder is {mainFolder}."); // Find the mod.json file. Yay for / \ path divider compatibility. var modJsonPath = Path.Combine(mainFolder, "mod.json").Replace('/', '\\'); var modJson = zip.Entries.FirstOrDefault(e => e.FullName.Replace('/', '\\') == modJsonPath); // Read mod.json. if (modJson == null) throw new Exception("mod is missing mod.json file"); using (var streamReader = new StreamReader(modJson.Open())) { var modInformationString = await streamReader.ReadToEndAsync(); var modInformation = JsonConvert.DeserializeObject<ModInformation>(modInformationString); var meta = new ModMetadata { Id = downloadedAsset.ApiAsset.Id, // InstalledVersion = downloadedAsset.ApiAsset.UpdatedAt, Tag = downloadedAsset.Info.Tag, Repository = downloadedAsset.Info.Repository }; // TODO: Re-add installed version var installationPath = Path.Combine(_parkitect.Paths.GetAssetPath(AssetType.Mod), downloadedAsset.Info.Repository.Replace('/', '@')); // TODO: Should actually try and look if the mod has been updated since and delete the whole folder. if (Directory.Exists(installationPath)) { if (File.Exists(Path.Combine(installationPath, "modinfo.meta"))) File.Delete(Path.Combine(installationPath, "modinfo.meta")); if (File.Exists(Path.Combine(installationPath, "moddata.cache"))) File.Delete(Path.Combine(installationPath, "moddata.cache")); } _log.WriteLine($"mod.json was deserialized to mod object '{modInformation}'."); // Set default mod properties. modInformation.IsEnabled = true; modInformation.IsDevelopment = false; // Find previous version of mod. // TODO uninstall previous versions // Install mod. _log.WriteLine("Copying mod to mods folder."); foreach (var entry in zip.Entries) { if (!entry.FullName.StartsWith(mainFolder)) continue; // Compute path. var partDir = entry.FullName.Substring(mainFolder.Length); var path = Path.Combine(installationPath, partDir); if (partDir == "moddata.cache" || partDir == "modinfo.meta") continue; if (string.IsNullOrEmpty(entry.Name)) { _log.WriteLine($"Creating directory '{path}'."); Directory.CreateDirectory(path); } else { _log.WriteLine($"Storing mod file '{path}'."); using (var openStream = entry.Open()) using (var fileStream = File.OpenWrite(path)) await openStream.CopyToAsync(fileStream); } } _log.WriteLine("Register installation to API."); _website.API.RegisterDownload(downloadedAsset.ApiAsset.Id); _assetMetadataStorage.StoreMetadata(downloadedAsset.ApiAsset.Type, installationPath, meta); var cachedData = await _assetCachedDataStorage.GetData(downloadedAsset.ApiAsset.Type, meta, installationPath); modInformationString = JsonConvert.SerializeObject(modInformation); File.WriteAllText(Path.Combine(installationPath, "mod.json"), modInformationString); var createdAsset = new ModAsset(installationPath, meta, cachedData as AssetWithImageCachedData, modInformation); OnAssetAdded(new AssetEventArgs(createdAsset)); return createdAsset; } } } default: throw new Exception("unknown asset type"); } }