Beispiel #1
0
        /// <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);
        }
Beispiel #3
0
        /// <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
        }
Beispiel #4
0
        /// <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);
                }
            }
        }
Beispiel #5
0
 /// <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());
     }
 }
Beispiel #6
0
        /// <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());
        }
Beispiel #7
0
        /// <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));
        }
Beispiel #8
0
        /// <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());
        }
Beispiel #9
0
        /// <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());
        }
Beispiel #10
0
        /// <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());
        }
Beispiel #11
0
        /// <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());
        }
Beispiel #12
0
        /// <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);
        }
Beispiel #13
0
        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;
        }
Beispiel #14
0
        /// <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!
        }
Beispiel #15
0
        /// <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);
        }
Beispiel #16
0
        /// <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);
        }
Beispiel #17
0
 /// <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);
 }
Beispiel #18
0
 /// <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);
 }
Beispiel #19
0
 /// <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);
 }
Beispiel #20
0
        /// <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!
        }
Beispiel #21
0
        /// <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);
        }
Beispiel #22
0
        /// <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;
        }
Beispiel #23
0
        /// <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!
        }
Beispiel #24
0
 public void MakeCache()
 {
     Directory.CreateDirectory(cache_dir);
     cache = new NetFileCache(cache_dir);
 }