Пример #1
0
        public void GenerateDefaultInstall()
        {
            string filename = Tests.TestData.DogeCoinFlagZip();

            using (var zipfile = new ZipFile(filename))
            {
                CKAN.ModuleInstallDescriptor stanza = CKAN.ModuleInstaller.GenerateDefaultInstall("DogeCoinFlag", zipfile);

                TestDogeCoinStanza(stanza);

                // Same again, but screwing up the case (we see this *all the time*)
                CKAN.ModuleInstallDescriptor stanza2 = CKAN.ModuleInstaller.GenerateDefaultInstall("DogecoinFlag", zipfile);

                TestDogeCoinStanza(stanza2);

                // Now what happens if we can't find what to install?

                Assert.Throws <FileNotFoundKraken>(delegate
                {
                    CKAN.ModuleInstaller.GenerateDefaultInstall("Xyzzy", zipfile);
                });

                // Make sure the FNFKraken looks like what we expect.
                try
                {
                    CKAN.ModuleInstaller.GenerateDefaultInstall("Xyzzy", zipfile);
                }
                catch (FileNotFoundKraken kraken)
                {
                    Assert.AreEqual("Xyzzy", kraken.file);
                }
            }
        }
Пример #2
0
        /// <summary>
        /// Given a module and an open zipfile, return all the files that would be installed
        /// for this module.
        ///
        /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null.
        ///
        /// Throws a BadMetadataKraken if the stanza resulted in no files being returned.
        /// </summary>
        public static List <InstallableFile> FindInstallableFiles(CkanModule module, ZipFile zipfile, KSP ksp)
        {
            var files = new List <InstallableFile> ();

            try
            {
                // Use the provided stanzas, or use the default install stanza if they're absent.
                if (module.install != null && module.install.Length != 0)
                {
                    foreach (ModuleInstallDescriptor stanza in module.install)
                    {
                        files.AddRange(FindInstallableFiles(stanza, zipfile, ksp));
                    }
                }
                else
                {
                    ModuleInstallDescriptor default_stanza = GenerateDefaultInstall(module.identifier, zipfile);
                    files.AddRange(FindInstallableFiles(default_stanza, zipfile, ksp));
                }
            }
            catch (BadMetadataKraken kraken)
            {
                // Decorate our kraken with the current module, as the lower-level
                // methods won't know it.
                kraken.module = module;
                throw;
            }

            return(files);
        }
Пример #3
0
        /// <summary>
        /// Given a zipfile, returns a `file` ModuleInstallDescriptor that can be used for
        /// installation.
        /// Returns `this` if already of a `file` type.
        /// </summary>
        /// <param name="zipfile">Downloaded ZIP file containing the mod</param>
        public ModuleInstallDescriptor ConvertFindToFile(ZipFile zipfile)
        {
            // If we're already a file type stanza, then we have nothing to do.
            if (this.file != null)
            {
                return(this);
            }

            // Match *only* things with our find string as a directory.
            // We can't just look for directories, because some zipfiles
            // don't include entries for directories, but still include entries
            // for the files they contain.
            Regex inst_filt = this.find != null
                ? new Regex(@"(?:^|/)" + Regex.Escape(this.find) + @"$", RegexOptions.IgnoreCase)
                : new Regex(this.find_regexp, RegexOptions.IgnoreCase);

            // Find the shortest directory path that matches our filter,
            // including all parent directories of all entries.
            string shortest = null;

            foreach (ZipEntry entry in zipfile)
            {
                bool is_file = !entry.IsDirectory;
                // Normalize path before searching (path separator as '/', no trailing separator)
                for (string path = Regex.Replace(entry.Name.Replace('\\', '/'), "/$", "");
                     !string.IsNullOrEmpty(path);
                     path = Path.GetDirectoryName(path).Replace('\\', '/'), is_file = false)
                {
                    // Skip file paths if not allowed
                    if (!find_matches_files && is_file)
                    {
                        continue;
                    }

                    // Is this a shorter matching path?
                    if ((string.IsNullOrEmpty(shortest) || path.Length < shortest.Length) &&
                        inst_filt.IsMatch(path))
                    {
                        shortest = path;
                    }
                }
            }
            if (string.IsNullOrEmpty(shortest))
            {
                throw new FileNotFoundKraken(
                          this.find ?? this.find_regexp,
                          String.Format("Could not find {0} entry in zipfile to install", this.find ?? this.find_regexp)
                          );
            }

            // Fill in our stanza, and remove our old `find` and `find_regexp` info.
            ModuleInstallDescriptor stanza = (ModuleInstallDescriptor)this.Clone();

            stanza.file        = shortest;
            stanza.find        = null;
            stanza.find_regexp = null;
            return(stanza);
        }
Пример #4
0
 /// <summary>
 /// Returns a default install stanza for the identifer provided.
 /// </summary>
 public static ModuleInstallDescriptor DefaultInstallStanza(string ident, ZipFile zipfile)
 {
     // Really this is just making a dummy `find` stanza and returning the processed
     // result.
     var stanza = new ModuleInstallDescriptor();
     stanza.install_to = "GameData";
     stanza.find = ident;
     return stanza.ConvertFindToFile(zipfile);
 }
Пример #5
0
 private static bool InstallStanzaEquals(ModuleInstallDescriptor newInst, ModuleInstallDescriptor oldInst)
 {
     if (newInst.file != oldInst.file)
     {
         return(false);
     }
     if (newInst.install_to != oldInst.install_to)
     {
         return(false);
     }
     if (newInst.@as != oldInst.@as)
     {
         return(false);
     }
     if ((newInst.filter == null) != (oldInst.filter == null))
     {
         return(false);
     }
     if (newInst.filter != null &&
         !newInst.filter.SequenceEqual(oldInst.filter))
     {
         return(false);
     }
     if ((newInst.filter_regexp == null) != (oldInst.filter_regexp == null))
     {
         return(false);
     }
     if (newInst.filter_regexp != null &&
         !newInst.filter_regexp.SequenceEqual(oldInst.filter_regexp))
     {
         return(false);
     }
     if (newInst.find_matches_files != oldInst.find_matches_files)
     {
         return(false);
     }
     if ((newInst.include_only == null) != (oldInst.include_only == null))
     {
         return(false);
     }
     if (newInst.include_only != null &&
         !newInst.include_only.SequenceEqual(oldInst.include_only))
     {
         return(false);
     }
     if ((newInst.include_only_regexp == null) != (oldInst.include_only_regexp == null))
     {
         return(false);
     }
     if (newInst.include_only_regexp != null &&
         !newInst.include_only_regexp.SequenceEqual(oldInst.include_only_regexp))
     {
         return(false);
     }
     return(true);
 }
Пример #6
0
        /// <summary>
        /// Returns a default install stanza for the identifer provided.
        /// </summary>
        public static ModuleInstallDescriptor DefaultInstallStanza(string ident, ZipFile zipfile)
        {
            // Really this is just making a dummy `find` stanza and returning the processed
            // result.
            var stanza = new ModuleInstallDescriptor();

            stanza.install_to = "GameData";
            stanza.find       = ident;
            return(stanza.ConvertFindToFile(zipfile));
        }
Пример #7
0
        /// <summary>
        /// Returns a default install stanza for the module provided. This finds the topmost
        /// directory which matches the module identifier, and generates a stanza that
        /// installs that into GameData.
        ///
        /// Throws a FileNotFoundKraken() if unable to locate a suitable directory.
        /// </summary>
        internal static ModuleInstallDescriptor GenerateDefaultInstall(string identifier, ZipFile zipfile)
        {
            var stanza = new ModuleInstallDescriptor();

            stanza.install_to = "GameData";

            // Candidate top-level directories.
            var candidate_set = new HashSet <string>();

            // Match *only* things with our module identifier as a directory.
            // We can't just look for directories, because some zipfiles
            // don't include entries for directories, but still include entries
            // for the files they contain.

            string ident_filter = @"(?:^|/)" + Regex.Escape(identifier) + @"$";

            // Let's find that directory
            foreach (ZipEntry entry in zipfile)
            {
                string directory = Path.GetDirectoryName(entry.Name);

                // Normalise our path.
                directory = directory.Replace('\\', '/');
                directory = Regex.Replace(directory, "/$", "");

                // If this looks like what we're after, remember it.
                if (Regex.IsMatch(directory, ident_filter, RegexOptions.IgnoreCase))
                {
                    candidate_set.Add(directory);
                }
            }

            // Sort to have shortest first. It's not *quite* top-level directory order,
            // but it's good enough for now.
            var candidates = new List <string>(candidate_set);

            candidates.Sort((a, b) => a.Length.CompareTo(b.Length));

            if (candidates.Count == 0)
            {
                throw new FileNotFoundKraken(
                          identifier,
                          String.Format("Could not find {0} directory in zipfile to install", identifier)
                          );
            }

            // Fill in our stanza!
            stanza.file = candidates[0];
            return(stanza);
        }
Пример #8
0
        public string DescribeInstallStanzas()
        {
            List <string> descriptions = new List <string>();

            if (install != null)
            {
                foreach (ModuleInstallDescriptor mid in install)
                {
                    descriptions.Add(mid.DescribeMatch());
                }
            }
            else
            {
                descriptions.Add(ModuleInstallDescriptor.DefaultInstallStanza(identifier).DescribeMatch());
            }
            return(string.Join(", ", descriptions));
        }
Пример #9
0
        /// <summary>
        /// Given a stanza and an open zipfile, returns all files that would be installed
        /// for this stanza.
        ///
        /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null.
        ///
        /// Throws a BadInstallLocationKraken if the install stanza targets an
        /// unknown install location (eg: not GameData, Ships, etc)
        ///
        /// Throws a BadMetadataKraken if the stanza resulted in no files being returned.
        /// </summary>
        internal static List <InstallableFile> FindInstallableFiles(ModuleInstallDescriptor stanza, ZipFile zipfile, KSP ksp)
        {
            string installDir;
            bool   makeDirs;
            var    files = new List <InstallableFile> ();

            if (stanza.install_to == "GameData")
            {
                installDir = ksp == null ? null : ksp.GameData();
                makeDirs   = true;
            }
            else if (stanza.install_to == "Ships")
            {
                installDir = ksp == null ? null : ksp.Ships();
                makeDirs   = false; // Don't allow directory creation in ships directory
            }
            else if (stanza.install_to == "Tutorial")
            {
                installDir = ksp == null ? null : ksp.Tutorial();
                makeDirs   = true;
            }
            else if (stanza.install_to == "GameRoot")
            {
                installDir = ksp == null ? null : ksp.GameDir();
                makeDirs   = false;
            }
            else
            {
                throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to);
            }

            // O(N^2) solution, as we're walking the zipfile for each stanza.
            // Surely there's a better way, although this is fast enough we may not care.

            foreach (ZipEntry entry in zipfile)
            {
                // Skips things not prescribed by our install stanza.
                if (!stanza.IsWanted(entry.Name))
                {
                    continue;
                }

                // Prepare our file info.
                InstallableFile file_info = new InstallableFile();
                file_info.source      = entry;
                file_info.makedir     = makeDirs;
                file_info.destination = null;

                // If we have a place to install it, fill that in...
                if (installDir != null)
                {
                    // Get the full name of the file.
                    string outputName = entry.Name;

                    // Update our file info with the install location
                    file_info.destination = TransformOutputName(stanza.file, outputName, installDir);
                }

                files.Add(file_info);
            }

            // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93)
            if (files.Count == 0)
            {
                // We have null as the first argument here, because we don't know which module we're installing
                throw new BadMetadataKraken(null, String.Format("No files found in {0} to install!", stanza.file));
            }

            return(files);
        }
Пример #10
0
        /// <summary>
        /// Given a stanza and an open zipfile, returns all files that would be installed
        /// for this stanza.
        ///
        /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null.
        ///
        /// Throws a BadInstallLocationKraken if the install stanza targets an
        /// unknown install location (eg: not GameData, Ships, etc)
        ///
        /// Throws a BadMetadataKraken if the stanza resulted in no files being returned.
        /// </summary>
        /// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception>
        internal static List <InstallableFile> FindInstallableFiles(ModuleInstallDescriptor stanza, ZipFile zipfile, KSP ksp)
        {
            string installDir;
            bool   makeDirs;
            var    files = new List <InstallableFile> ();

            // Normalize the path before doing everything else
            // TODO: This really should happen in the ModuleInstallDescriptor itself.
            stanza.install_to = KSPPathUtils.NormalizePath(stanza.install_to);

            // Convert our stanza to a standard `file` type. This is a no-op if it's
            // already the basic type.

            stanza = stanza.ConvertFindToFile(zipfile);

            if (stanza.install_to == "GameData" || stanza.install_to.StartsWith("GameData/"))
            {
                // The installation path can be either "GameData" or a sub-directory of "GameData"
                // but it cannot contain updirs
                if (stanza.install_to.Contains("/../") || stanza.install_to.EndsWith("/.."))
                {
                    throw new BadInstallLocationKraken("Invalid installation path: " + stanza.install_to);
                }

                string subDir = stanza.install_to.Substring("GameData".Length);    // remove "GameData"
                subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir;    // remove a "/" at the beginning, if present

                // Add the extracted subdirectory to the path of KSP's GameData
                installDir = ksp == null ? null : (KSPPathUtils.NormalizePath(ksp.GameData() + "/" + subDir));
                makeDirs   = true;
            }
            else if (stanza.install_to.StartsWith("Ships"))
            {
                // Don't allow directory creation in ships directory
                makeDirs = false;

                switch (stanza.install_to)
                {
                case "Ships":
                    installDir = ksp == null ? null : ksp.Ships();
                    break;

                case "Ships/VAB":
                    installDir = ksp == null ? null : ksp.ShipsVab();
                    break;

                case "Ships/SPH":
                    installDir = ksp == null ? null : ksp.ShipsSph();
                    break;

                default:
                    throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to);
                }
            }
            else
            {
                switch (stanza.install_to)
                {
                case "Tutorial":
                    installDir = ksp == null ? null : ksp.Tutorial();
                    makeDirs   = true;
                    break;

                case "Scenarios":
                    installDir = ksp == null ? null : ksp.Scenarios();
                    makeDirs   = true;
                    break;

                case "GameRoot":
                    installDir = ksp == null ? null : ksp.GameDir();
                    makeDirs   = false;
                    break;

                default:
                    throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to);
                }
            }

            // O(N^2) solution, as we're walking the zipfile for each stanza.
            // Surely there's a better way, although this is fast enough we may not care.

            foreach (ZipEntry entry in zipfile)
            {
                // Skips things not prescribed by our install stanza.
                if (!stanza.IsWanted(entry.Name))
                {
                    continue;
                }

                // Prepare our file info.
                InstallableFile file_info = new InstallableFile
                {
                    source      = entry,
                    makedir     = makeDirs,
                    destination = null
                };

                // If we have a place to install it, fill that in...
                if (installDir != null)
                {
                    // Get the full name of the file.
                    string outputName = entry.Name;

                    // Update our file info with the install location
                    file_info.destination = TransformOutputName(stanza.file, outputName, installDir);
                }

                files.Add(file_info);
            }

            // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93)
            if (files.Count == 0)
            {
                // We have null as the first argument here, because we don't know which module we're installing
                throw new BadMetadataKraken(null, String.Format("No files found in {0} to install!", stanza.file));
            }

            return(files);
        }
Пример #11
0
        /// <summary>
        /// Given a stanza and an open zipfile, returns all files that would be installed
        /// for this stanza.
        ///
        /// If a KSP instance is provided, it will be used to generate output paths, otherwise these will be null.
        ///
        /// Throws a BadInstallLocationKraken if the install stanza targets an
        /// unknown install location (eg: not GameData, Ships, etc)
        ///
        /// Throws a BadMetadataKraken if the stanza resulted in no files being returned.
        /// </summary>
        /// <exception cref="BadInstallLocationKraken">Thrown when the installation path is not valid according to the spec.</exception>
        internal static List<InstallableFile> FindInstallableFiles(ModuleInstallDescriptor stanza, ZipFile zipfile, KSP ksp)
        {
            string installDir;
            bool makeDirs;
            var files = new List<InstallableFile> ();

            // Normalize the path before doing everything else
            // TODO: This really should happen in the ModuleInstallDescriptor itself.
            stanza.install_to = KSPPathUtils.NormalizePath(stanza.install_to);

            // Convert our stanza to a standard `file` type. This is a no-op if it's
            // already the basic type.

            stanza = stanza.ConvertFindToFile(zipfile);

            if (stanza.install_to == "GameData" || stanza.install_to.StartsWith("GameData/"))
            {
                // The installation path can be either "GameData" or a sub-directory of "GameData"
                // but it cannot contain updirs
                if (stanza.install_to.Contains("/../") || stanza.install_to.EndsWith("/.."))
                    throw new BadInstallLocationKraken("Invalid installation path: " + stanza.install_to);

                string subDir = stanza.install_to.Substring("GameData".Length);    // remove "GameData"
                subDir = subDir.StartsWith("/") ? subDir.Substring(1) : subDir;    // remove a "/" at the beginning, if present

                // Add the extracted subdirectory to the path of KSP's GameData
                installDir = ksp == null ? null : (KSPPathUtils.NormalizePath(ksp.GameData() + "/" + subDir));
                makeDirs = true;
            }
            else if (stanza.install_to.StartsWith("Ships"))
            {
                // Don't allow directory creation in ships directory
                makeDirs = false;

                switch (stanza.install_to)
                {
                    case "Ships":
                        installDir = ksp == null ? null : ksp.Ships();
                        break;
                    case "Ships/VAB":
                        installDir = ksp == null ? null : ksp.ShipsVab();
                        break;
                    case "Ships/SPH":
                        installDir = ksp == null ? null : ksp.ShipsSph();
                        break;
                    case "Ships/@thumbs":
                        installDir = ksp == null ? null : ksp.ShipsThumbs();
                        break;
                    case "Ships/@thumbs/VAB":
                        installDir = ksp == null ? null : ksp.ShipsThumbsVAB();
                        break;
                    case "Ships/@thumbs/SPH":
                        installDir = ksp == null ? null : ksp.ShipsThumbsSPH();
                        break;
                    default:
                        throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to);
                }
            }
            else switch (stanza.install_to)
            {
                case "Tutorial":
                    installDir = ksp == null ? null : ksp.Tutorial();
                    makeDirs = true;
                    break;

                case "Scenarios":
                    installDir = ksp == null ? null : ksp.Scenarios();
                    makeDirs = true;
                    break;

                case "GameRoot":
                    installDir = ksp == null ? null : ksp.GameDir();
                    makeDirs = false;
                    break;

                default:
                    throw new BadInstallLocationKraken("Unknown install_to " + stanza.install_to);
            }

            // O(N^2) solution, as we're walking the zipfile for each stanza.
            // Surely there's a better way, although this is fast enough we may not care.

            foreach (ZipEntry entry in zipfile)
            {
                // Skips things not prescribed by our install stanza.
                if (! stanza.IsWanted(entry.Name)) {
                    continue;
                }

                // Prepare our file info.
                InstallableFile file_info = new InstallableFile
                {
                    source = entry,
                    makedir = makeDirs,
                    destination = null
                };

                // If we have a place to install it, fill that in...
                if (installDir != null)
                {
                    // Get the full name of the file.
                    string outputName = entry.Name;

                    // Update our file info with the install location
                    file_info.destination = TransformOutputName(stanza.file, outputName, installDir);
                }

                files.Add(file_info);
            }

            // If we have no files, then something is wrong! (KSP-CKAN/CKAN#93)
            if (files.Count == 0)
            {
                // We have null as the first argument here, because we don't know which module we're installing
                throw new BadMetadataKraken(null, String.Format("No files found in {0} to install!", stanza.file));
            }

            return files;
        }
Пример #12
0
 private static void TestDogeCoinStanza(ModuleInstallDescriptor stanza)
 {
     Assert.AreEqual("GameData", stanza.install_to);
     Assert.AreEqual("DogeCoinFlag-1.01/GameData/DogeCoinFlag", stanza.file);
 }
Пример #13
0
        /// <summary>
        /// Compare two install stanzas
        /// </summary>
        /// <param name="other">The other stanza for comparison</param>
        /// <returns>
        /// True if they're equivalent, false if they're different
        /// </returns>
        public override bool Equals(object other)
        {
            ModuleInstallDescriptor otherStanza = other as ModuleInstallDescriptor;

            if (otherStanza == null)
            {
                // Not even the right type!
                return(false);
            }
            if (KSPPathUtils.NormalizePath(file) != KSPPathUtils.NormalizePath(otherStanza.file))
            {
                return(false);
            }
            if (KSPPathUtils.NormalizePath(find) != KSPPathUtils.NormalizePath(otherStanza.find))
            {
                return(false);
            }
            if (find_regexp != otherStanza.find_regexp)
            {
                return(false);
            }
            if (KSPPathUtils.NormalizePath(install_to) != KSPPathUtils.NormalizePath(otherStanza.install_to))
            {
                return(false);
            }
            if (@as != otherStanza.@as)
            {
                return(false);
            }
            if ((filter == null) != (otherStanza.filter == null))
            {
                return(false);
            }
            if (filter != null &&
                !filter.SequenceEqual(otherStanza.filter))
            {
                return(false);
            }
            if ((filter_regexp == null) != (otherStanza.filter_regexp == null))
            {
                return(false);
            }
            if (filter_regexp != null &&
                !filter_regexp.SequenceEqual(otherStanza.filter_regexp))
            {
                return(false);
            }
            if (find_matches_files != otherStanza.find_matches_files)
            {
                return(false);
            }
            if ((include_only == null) != (otherStanza.include_only == null))
            {
                return(false);
            }
            if (include_only != null &&
                !include_only.SequenceEqual(otherStanza.include_only))
            {
                return(false);
            }
            if ((include_only_regexp == null) != (otherStanza.include_only_regexp == null))
            {
                return(false);
            }
            if (include_only_regexp != null &&
                !include_only_regexp.SequenceEqual(otherStanza.include_only_regexp))
            {
                return(false);
            }
            return(true);
        }
Пример #14
0
 private void TestDogeCoinStanza(CKAN.ModuleInstallDescriptor stanza)
 {
     Assert.AreEqual("GameData", stanza.install_to);
     Assert.AreEqual("DogeCoinFlag-1.01/GameData/DogeCoinFlag", stanza.file);
 }