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");
            }
        }