/// <summary> /// Downloads the given mod to the cache. Returns the filename it was saved to. /// </summary> public static string Download(Uri url, string filename, NetFileCache cache) { log.Info("Downloading " + filename); string tmp_file = Net.Download(url); return(cache.Store(url, tmp_file, filename, true)); }
public void TestFixtureSetup() { var tempDirectory = Path.Combine(Path.GetTempPath(), "CKAN", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(tempDirectory); _cache = new NetFileCache(tempDirectory); }
/// <summary> /// Releases all resource used by the <see cref="CKAN.KSP"/> object. /// </summary> /// <remarks>Call <see cref="Dispose"/> when you are finished using the <see cref="CKAN.KSP"/>. The <see cref="Dispose"/> /// method leaves the <see cref="CKAN.KSP"/> in an unusable state. After calling <see cref="Dispose"/>, you must /// release all references to the <see cref="CKAN.KSP"/> so the garbage collector can reclaim the memory that /// the <see cref="CKAN.KSP"/> was occupying.</remarks> public void Dispose() { if (Cache != null) { Cache.Dispose(); Cache = null; } // Attempting to dispose of the related RegistryManager object here is a bad idea, it cause loads of failures }
/// <summary> /// Stores all of our files in the cache once done. /// Called by NetAsyncDownloader on completion. /// Called with all nulls on download cancellation. /// </summary> private void ModuleDownloadsComplete(NetFileCache cache, Uri[] urls, string[] filenames, Exception[] errors) { if (urls != null) { // spawn up to 3 dialogs int errorDialogsLeft = 3; for (int i = 0; i < errors.Length; i++) { if (errors[i] != null) { if (errorDialogsLeft > 0) { User.RaiseError("Failed to download \"{0}\" - error: {1}", urls[i], errors[i].Message); errorDialogsLeft--; } } else { // Even if some of our downloads failed, we want to cache the // ones which succeeded. // This doesn't work :( // for some reason the tmp files get deleted before we get here and we get a nasty exception // not only that but then we try _to install_ the rest of the mods and then CKAN crashes // and the user's registry gets corrupted forever // commenting out until this is resolved // ~ nlight try { // store in cache without query params cache.Store(new Uri(urls[i].GetLeftPart(UriPartial.Path)), filenames[i], modules[i].standardFileName); } catch (FileNotFoundException e) { log.WarnFormat("cache.Store(): FileNotFoundException: {0}", e.Message); } } } } if (filenames != null) { // Finally, remove all our temp files. // We probably *could* have used Store's integrated move function above, but if we managed // to somehow get two URLs the same in our download set, that could cause right troubles! foreach (string tmpfile in filenames) { log.DebugFormat("Cleaning up {0}", tmpfile); File.Delete(tmpfile); } } }
/// <summary> /// Returns a KSP object, insisting that directory contains a valid KSP install. /// Will initialise a CKAN instance in the KSP dir if it does not already exist. /// Throws a NotKSPDirKraken if directory is not a KSP install. /// </summary> public KSP(string gameDir, IUser user) { User = user; // Make sure our path is absolute and has normalised slashes. this.gameDir = KSPPathUtils.NormalizePath(Path.GetFullPath(gameDir)); if (Valid) { SetupCkanDirectories(); LoadCompatibleVersions(); Cache = new NetFileCache(DownloadCacheDir()); } }
/// <summary> /// Returns a KSP object, insisting that directory contains a valid KSP install. /// Will initialise a CKAN instance in the KSP dir if it does not already exist. /// Throws a NotKSPDirKraken if directory is not a KSP install. /// </summary> public KSP(string directory) { // Make sure our path is absolute and has normalised slashes. directory = KSPPathUtils.NormalizePath(Path.GetFullPath(directory)); if (!IsKspDir(directory)) { throw new NotKSPDirKraken(directory); } gamedir = directory; Init(); _Cache = new NetFileCache(DownloadCacheDir()); }
/// <summary> /// Try to add a file to the module cache. /// Throws exceptions if the file doesn't match the metadata. /// </summary> /// <param name="module">The module object corresponding to the download</param> /// <param name="path">Path to the file to add</param> /// <param name="description">Description of the file</param> /// <param name="move">True to move the file, false to copy</param> /// <returns> /// Name of the new file in the cache /// </returns> public string Store(CkanModule module, string path, string description = null, bool move = false) { // Check file exists FileInfo fi = new FileInfo(path); if (!fi.Exists) { throw new FileNotFoundKraken(path); } // Check file size if (module.download_size > 0 && fi.Length != module.download_size) { throw new InvalidModuleFileKraken(module, path, $"{module}: {path} has length {fi.Length}, should be {module.download_size}"); } // Check valid CRC string invalidReason; if (!NetFileCache.ZipValid(path, out invalidReason)) { throw new InvalidModuleFileKraken(module, path, $"{module}: {path} is not a valid ZIP file: {invalidReason}"); } // Some older metadata doesn't have hashes if (module.download_hash != null) { // Check SHA1 match string sha1 = GetFileHashSha1(path); if (sha1 != module.download_hash.sha1) { throw new InvalidModuleFileKraken(module, path, $"{module}: {path} has SHA1 {sha1}, should be {module.download_hash.sha1}"); } // Check SHA256 match string sha256 = GetFileHashSha256(path); if (sha256 != module.download_hash.sha256) { throw new InvalidModuleFileKraken(module, path, $"{module}: {path} has SHA256 {sha256}, should be {module.download_hash.sha256}"); } } // If no exceptions, then everything is fine return(cache.Store(module.download, path, description ?? module.StandardName(), move)); }
/// <summary> /// Returns a KSP object, insisting that directory contains a valid KSP install. /// Will initialise a CKAN instance in the KSP dir if it does not already exist. /// Throws a NotFactorioDirectoryKraken if directory is not a KSP install. /// </summary> public KSP(string directory, IUser user) { User = user; // Make sure our path is absolute and has normalised slashes. directory = KSPPathUtils.NormalizePath(Path.GetFullPath(directory)); VerifyFactorioDirectory(directory); gamedir = directory; gamedatadir = DetectGameDataDirectory(gamedir); VerifyFactorioDataDirectory(gamedatadir); Init(); Cache = new NetFileCache(DownloadCacheDir()); }
/// <summary> /// Returns a KSP object, insisting that directory contains a valid KSP install. /// Will initialise a CKAN instance in the KSP dir if it does not already exist. /// Throws a NotKSPDirKraken if directory is not a KSP install. /// </summary> public KSP(string gameDir, IUser user) { User = user; // Make sure our path is absolute and has normalised slashes. gameDir = KSPPathUtils.NormalizePath(Path.GetFullPath(gameDir)); if (!IsKspDir(gameDir)) { throw new NotKSPDirKraken(gameDir); } this.gameDir = gameDir; Init(); Cache = new NetFileCache(DownloadCacheDir()); }
/// <summary> /// Returns a KSP object, insisting that directory contains a valid KSP install. /// Will initialise a CKAN instance in the KSP dir if it does not already exist. /// Throws a NotKSPDirKraken if directory is not a KSP install. /// </summary> public KSP(string directory, IUser user) { User = user; // Make sure our path is absolute and has normalised slashes. directory = KSPPathUtils.NormalizePath(Path.GetFullPath(directory)); if (! IsKspDir(directory)) { throw new NotKSPDirKraken(directory); } gamedir = directory; Init(); Cache = new NetFileCache(DownloadCacheDir()); }
/// <summary> /// Returns a KSP object, insisting that directory contains a valid KSP install. /// Will initialise a CKAN instance in the KSP dir if it does not already exist. /// Throws a NotKSPDirKraken if directory is not a KSP install. /// </summary> public KSP(string gameDir, IUser user) { User = user; // Make sure our path is absolute and has normalised slashes. gameDir = KSPPathUtils.NormalizePath(Path.GetFullPath(gameDir)); if (! IsKspDir(gameDir)) { throw new NotKSPDirKraken(gameDir); } this.gameDir = gameDir; Init(); Cache = new NetFileCache(DownloadCacheDir()); }
/// <summary> /// Stores all of our files in the cache once done. /// Called by NetAsyncDownloader on completion. /// Called with all nulls on download cancellation. /// </summary> private void ModuleDownloadsComplete(NetFileCache cache, Uri[] urls, string[] filenames, Exception[] errors) { if (urls != null) { for (int i = 0; i < errors.Length; i++) { if (errors[i] != null) { User.RaiseError("Failed to download \"{0}\" - error: {1}", urls[i], errors[i].Message); } else { // Even if some of our downloads failed, we want to cache the // ones which succeeded. cache.Store(urls[i], filenames[i], modules[i].StandardName()); } } } // TODO: If we've had our download cancelled, how do we clean our tmpfiles? if (filenames != null) { // Finally, remove all our temp files. // We probably *could* have used Store's integrated move function above, but if we managed // to somehow get two URLs the same in our download set, that could cause right troubles! foreach (string tmpfile in filenames) { log.DebugFormat("Cleaning up {0}", tmpfile); file_transaction.Delete(tmpfile); } } // Signal that we're done. lock (download_complete_lock) { Monitor.Pulse(download_complete_lock); } User.RaiseDownloadsCompleted(urls, filenames, errors); }
public void Setup() { // Make sure curl is all set up. Curl.Init(); // Give us a registry to play with. ksp = new DisposableKSP(); registry = ksp.KSP.Registry; registry.ClearAvailable(); registry.ClearDlls(); registry.Installed().Clear(); // Make sure we have a registry we can use. CKAN.Repo.UpdateRegistry(TestData.TestKANZip(), registry, ksp.KSP, new NullUser()); // Ready our downloader. async = new CKAN.NetAsyncDownloader(new NullUser()); // General shortcuts cache = ksp.KSP.Cache; }
/// <summary> /// Downloads all the modules specified to the cache. /// Even if modules share download URLs, they will only be downloaded once. /// Blocks until the download is complete, cancelled, or errored. /// </summary> public void DownloadModules( NetFileCache cache, IEnumerable<CkanModule> modules ) { var unique_downloads = new Dictionary<Uri, CkanModule>(); // Walk through all our modules, but only keep the first of each // one that has a unique download path. foreach (CkanModule module in modules.Where(module => !unique_downloads.ContainsKey(module.download))) { unique_downloads[module.download] = module; } this.modules.AddRange(unique_downloads.Values); // Schedule us to process our modules on completion. onCompleted = (_uris, paths, errors) => ModuleDownloadsComplete(cache, _uris, paths, errors); // retrieve the expected download size for each mod List<KeyValuePair<Uri, long>> downloads_with_size = new List<KeyValuePair<Uri, long>>(); foreach(var item in unique_downloads) { downloads_with_size.Add(new KeyValuePair<Uri, long>(item.Key, item.Value.download_size)); } // Start the download! Download(downloads_with_size); log.Debug("Waiting for downloads to finish..."); complete_or_canceled.WaitOne(); var old_download_canceled = download_canceled; // Set up the inter-thread comms for next time. Can not be done at the start // of the method as the thread could pause on the opening line long enough for // a user to cancel. download_canceled = false; complete_or_canceled.Reset(); // If the user cancelled our progress, then signal that. // This *should* be harmless if we're using the curlsharp downloader, // which watches for downloadCanceled all by itself. :) if (old_download_canceled) { // Abort all our traditional downloads, if there are any. foreach (var download in downloads) { download.agent.CancelAsync(); } // Abort all our curl downloads, if there are any. foreach (var thread in curl_threads) { thread.Abort(); } // Signal to the caller that the user cancelled the download. throw new CancelledActionKraken("Download cancelled by user"); } // Check to see if we've had any errors. If so, then release the kraken! var exceptions = downloads .Select(x => x.error) .Where(ex => ex != null) .ToList(); // Let's check if any of these are certificate errors. If so, // we'll report that instead, as this is common (and user-fixable) // under Linux. if (exceptions.Any(ex => ex is WebException && Regex.IsMatch(ex.Message, "authentication or decryption has failed"))) { throw new MissingCertificateKraken(); } if (exceptions.Count > 0) { throw new DownloadErrorsKraken(exceptions); } // Yay! Everything worked! }
/// <summary> /// Stores all of our files in the cache once done. /// Called by NetAsyncDownloader on completion. /// Called with all nulls on download cancellation. /// </summary> private void ModuleDownloadsComplete(NetFileCache cache, Uri[] urls, string[] filenames, Exception[] errors) { if (urls != null) { // spawn up to 3 dialogs int errorDialogsLeft = 3; for (int i = 0; i < errors.Length; i++) { if (errors[i] != null) { if (errorDialogsLeft > 0) { User.RaiseError("Failed to download \"{0}\" - error: {1}", urls[i], errors[i].Message); errorDialogsLeft--; } } else { // Even if some of our downloads failed, we want to cache the // ones which succeeded. // This doesn't work :( // for some reason the tmp files get deleted before we get here and we get a nasty exception // not only that but then we try _to install_ the rest of the mods and then CKAN crashes // and the user's registry gets corrupted forever // commenting out until this is resolved // ~ nlight try { cache.Store(urls[i], filenames[i], modules[i].StandardName()); } catch (FileNotFoundException e) { log.WarnFormat("cache.Store(): FileNotFoundException: {0}", e.Message); } } } } if (filenames != null) { // Finally, remove all our temp files. // We probably *could* have used Store's integrated move function above, but if we managed // to somehow get two URLs the same in our download set, that could cause right troubles! foreach (string tmpfile in filenames) { log.DebugFormat("Cleaning up {0}", tmpfile); File.Delete(tmpfile); } } // Signal that we're done. complete_or_canceled.Set(); User.RaiseDownloadsCompleted(urls, filenames, errors); }
/// <summary> /// Returns the path to a cached copy of a module if it exists, or downloads /// and returns the downloaded copy otherwise. /// /// If no filename is provided, the module's standard name will be used. /// Chcecks provided cache first. /// </summary> public static string CachedOrDownload(string identifier, Version version, Uri url, NetFileCache cache, string filename = null) { if (filename == null) { filename = CkanModule.StandardName(identifier, version); } string full_path = cache.GetCachedZip(url); if (full_path == null) { return(Download(url, filename, cache)); } log.DebugFormat("Using {0} (cached)", filename); return(full_path); }
/// <summary> /// Initialize the cache /// </summary> /// <param name="path">Path to directory to use as the cache</param> public NetModuleCache(string path) { cache = new NetFileCache(path); }
/// <summary> /// Initialize the cache /// </summary> /// <param name="mgr">GameInstanceManager containing instances that might have legacy caches</param> public NetModuleCache(GameInstanceManager mgr, string path) { cache = new NetFileCache(mgr, path); }
/// <summary> /// Initialize the cache /// </summary> /// <param name="mgr">KSPManager containing instances that might have legacy caches</param> public NetModuleCache(KSPManager mgr, string path) { cache = new NetFileCache(mgr, path); }
/// <summary> /// Downloads all the modules specified to the cache. /// Even if modules share download URLs, they will only be downloaded once. /// Blocks until the download is complete, cancelled, or errored. /// </summary> public void DownloadModules( NetFileCache cache, IEnumerable <CkanModule> modules ) { var unique_downloads = new Dictionary <Uri, CkanModule>(); // Walk through all our modules, but only keep the first of each // one that has a unique download path. foreach (CkanModule module in modules.Where(module => !unique_downloads.ContainsKey(module.download))) { unique_downloads[module.download] = module; } this.modules.AddRange(unique_downloads.Values); // Attach our progress report, if requested. onCompleted = (_uris, paths, errors) => ModuleDownloadsComplete(cache, _uris, paths, errors); // Start the download! Download(unique_downloads.Keys); // The Monitor.Wait function releases a lock, and then waits until it can re-acquire it. // Elsewhere, our downloading callback pulses the lock, which causes us to wake up and // continue. lock (download_complete_lock) { log.Debug("Waiting for downloads to finish..."); Monitor.Wait(download_complete_lock); } // If the user cancelled our progress, then signal that. if (downloadCanceled) { foreach (var download in downloads) { download.agent.CancelAsync(); } throw new CancelledActionKraken("Download cancelled by user"); } // Check to see if we've had any errors. If so, then release the kraken! List <Exception> exceptions = downloads .Select(x => x.error) .Where(ex => ex != null) .ToList(); // Let's check if any of these are certificate errors. If so, // we'll report that instead, as this is common (and user-fixable) // under Linux. if (exceptions.Any(ex => ex is WebException && Regex.IsMatch(ex.Message, "authentication or decryption has failed"))) { throw new MissingCertificateKraken(); } if (exceptions.Count > 0) { throw new DownloadErrorsKraken(exceptions); } // Yay! Everything worked! }
/// <summary> /// Downloads the given mod to the cache. Returns the filename it was saved to. /// </summary> public static string Download(Uri url, string filename, NetFileCache cache) { log.Info("Downloading " + filename); string tmp_file = Net.Download(url); return cache.Store(url, tmp_file, filename, true); }
/// <summary> /// Returns the path to a cached copy of a module if it exists, or downloads /// and returns the downloaded copy otherwise. /// /// If no filename is provided, the module's standard name will be used. /// Chcecks provided cache first. /// </summary> public static string CachedOrDownload(string identifier, Version version, Uri url, NetFileCache cache, string filename = null) { if (filename == null) { filename = CkanModule.StandardName(identifier, version); } string full_path = cache.GetCachedZip(url); if (full_path == null) { return Download(url, filename, cache); } log.DebugFormat("Using {0} (cached)", filename); return full_path; }
/// <summary> /// <see cref="IDownloader.DownloadModules(NetFileCache, IEnumerable{CkanModule})"/> /// </summary> public void DownloadModules( NetFileCache cache, IEnumerable <CkanModule> modules ) { var unique_downloads = new Dictionary <Uri, CkanModule>(); // Walk through all our modules, but only keep the first of each // one that has a unique download path. foreach (CkanModule module in modules.Where(module => !unique_downloads.ContainsKey(module.download))) { unique_downloads[module.download] = module; } this.modules.AddRange(unique_downloads.Values); // Schedule us to process our modules on completion. onCompleted = (_uris, paths, errors) => ModuleDownloadsComplete(cache, _uris, paths, errors); // retrieve the expected download size for each mod List <KeyValuePair <Uri, long> > downloads_with_size = new List <KeyValuePair <Uri, long> >(); foreach (var item in unique_downloads) { downloads_with_size.Add(new KeyValuePair <Uri, long>(item.Key, item.Value.download_size)); } // Start the download! Download(downloads_with_size); log.Debug("Waiting for downloads to finish..."); complete_or_canceled.WaitOne(); var old_download_canceled = download_canceled; // Set up the inter-thread comms for next time. Can not be done at the start // of the method as the thread could pause on the opening line long enough for // a user to cancel. download_canceled = false; complete_or_canceled.Reset(); // If the user cancelled our progress, then signal that. // This *should* be harmless if we're using the curlsharp downloader, // which watches for downloadCanceled all by itself. :) if (old_download_canceled) { // Abort all our traditional downloads, if there are any. foreach (var download in downloads.ToList()) { download.agent.CancelAsync(); } // Abort all our curl downloads, if there are any. foreach (var thread in curl_threads.ToList()) { thread.Abort(); } // Signal to the caller that the user cancelled the download. throw new CancelledActionKraken("Download cancelled by user"); } // Check to see if we've had any errors. If so, then release the kraken! var exceptions = downloads .Select(x => x.error) .Where(ex => ex != null) .ToList(); // Let's check if any of these are certificate errors. If so, // we'll report that instead, as this is common (and user-fixable) // under Linux. if (exceptions.Any(ex => ex is WebException && Regex.IsMatch(ex.Message, "authentication or decryption has failed"))) { throw new MissingCertificateKraken(); } if (exceptions.Count > 0) { throw new DownloadErrorsKraken(exceptions); } // Yay! Everything worked! }
public void MakeCache() { Directory.CreateDirectory(cache_dir); cache = new NetFileCache(cache_dir); }