Exemple #1
0
 private void EnsurePattern()
 {
     if (inst_pattern == null)
     {
         if (file != null)
         {
             file         = CKANPathUtils.NormalizePath(file);
             inst_pattern = new Regex(@"^" + Regex.Escape(file) + @"(/|$)",
                                      RegexOptions.IgnoreCase | RegexOptions.Compiled);
         }
         else if (find != null)
         {
             find         = CKANPathUtils.NormalizePath(find);
             inst_pattern = new Regex(@"(?:^|/)" + Regex.Escape(find) + @"(/|$)",
                                      RegexOptions.IgnoreCase | RegexOptions.Compiled);
         }
         else if (find_regexp != null)
         {
             inst_pattern = new Regex(find_regexp,
                                      RegexOptions.IgnoreCase | RegexOptions.Compiled);
         }
         else
         {
             throw new UnsupportedKraken("Install stanzas requires `file` or `find` or `find_regexp`.");
         }
     }
 }
Exemple #2
0
        internal void DeSerialisationFixes(StreamingContext like_i_could_care)
        {
            // Make sure our install_to fields exists. We may be able to remove
            // this check now that we're doing better json-fu above.
            if (install_to == null)
            {
                throw new BadMetadataKraken(null, "Install stanzas must have an install_to");
            }

            var setCount = new[] { file, find, find_regexp }.Count(i => i != null);

            // Make sure we have either a `file`, `find`, or `find_regexp` stanza.
            if (setCount == 0)
            {
                throw new BadMetadataKraken(null, "Install stanzas require either a file, find, or find_regexp directive");
            }

            if (setCount > 1)
            {
                throw new BadMetadataKraken(null, "Install stanzas must only include one of file, find, or find_regexp directives");
            }

            // Make sure only filter or include_only fields exist but not both at the same time
            var filterCount = new[] { filter, filter_regexp }.Count(i => i != null);
            var includeOnlyCount = new[] { include_only, include_only_regexp }.Count(i => i != null);

            if (filterCount > 0 && includeOnlyCount > 0)
            {
                throw new BadMetadataKraken(null, "Install stanzas can only contain filter or include_only directives, not both");
            }

            // Normalize paths on load (note, doesn't cover assignment like in tests)
            install_to = CKANPathUtils.NormalizePath(install_to);
        }
Exemple #3
0
 public string CkanDir()
 {
     if (!Valid)
     {
         log.Error("Could not find KSP version");
         throw new NotKSPDirKraken(gameDir, "Could not find KSP version in buildID.txt or readme.txt");
     }
     return(CKANPathUtils.NormalizePath(
                Path.Combine(GameDir(), "CKAN")));
 }
Exemple #4
0
        /// <summary>
        /// Transforms the name of the output. This will strip the leading directories from the stanza file from
        /// output name and then combine it with the installDir.
        /// EX: "kOS-1.1/GameData/kOS", "kOS-1.1/GameData/kOS/Plugins/kOS.dll", "GameData" will be transformed
        /// to "GameData/kOS/Plugins/kOS.dll"
        /// </summary>
        /// <param name="outputName">The name of the file to transform</param>
        /// <param name="installDir">The installation dir where the file should end up with</param>
        /// <returns>The output name</returns>
        internal string TransformOutputName(IGame game, string outputName, string installDir, string @as)
        {
            string leadingPathToRemove = Path
                                         .GetDirectoryName(ShortestMatchingPrefix(outputName))
                                         .Replace('\\', '/');

            if (!string.IsNullOrEmpty(leadingPathToRemove))
            {
                Regex leadingRE = new Regex(
                    "^" + Regex.Escape(leadingPathToRemove) + "/",
                    RegexOptions.Compiled);
                if (!leadingRE.IsMatch(outputName))
                {
                    throw new BadMetadataKraken(null, String.Format(
                                                    "Output file name ({0}) not matching leading path of stanza ({1})",
                                                    outputName, leadingPathToRemove));
                }
                // Strip off leading path name
                outputName = leadingRE.Replace(outputName, "");
            }

            // Now outputname looks like PATH/what/ever/file.ext, where
            // PATH is the part that matched `file` or `find` or `find_regexp`

            if (!string.IsNullOrWhiteSpace(@as))
            {
                if (@as.Contains("/") || @as.Contains("\\"))
                {
                    throw new BadMetadataKraken(null, "`as` may not include path separators.");
                }
                // Replace first path component with @as
                outputName = ReplaceFirstPiece(outputName, "/", @as);
            }
            else
            {
                var reservedPrefix = game.ReservedPaths.FirstOrDefault(prefix =>
                                                                       outputName.StartsWith(prefix + "/", StringComparison.InvariantCultureIgnoreCase));
                if (reservedPrefix != null)
                {
                    // If we try to install a folder with the same name as
                    // one of the reserved directories, strip it off.
                    // Delete reservedPrefix and one forward slash
                    outputName = outputName.Substring(reservedPrefix.Length + 1);
                }
            }

            // Return our snipped, normalised, and ready to go output filename!
            return(CKANPathUtils.NormalizePath(
                       Path.Combine(installDir, outputName)
                       ));
        }
Exemple #5
0
        /// <summary>
        /// Ensures all files for this module have relative paths.
        /// Called when upgrading registry versions. Should be a no-op
        /// if called on newer registries.
        /// </summary>
        public void Renormalise(GameInstance ksp)
        {
            var normalised_installed_files = new Dictionary <string, InstalledModuleFile>();

            foreach (KeyValuePair <string, InstalledModuleFile> tuple in installed_files)
            {
                string path = CKANPathUtils.NormalizePath(tuple.Key);

                if (Path.IsPathRooted(path))
                {
                    path = ksp.ToRelativeGameDir(path);
                }

                normalised_installed_files[path] = tuple.Value;
            }

            installed_files = normalised_installed_files;
        }
        /// <summary>
        /// Ensures all files for this module have relative paths.
        /// Called when upgrading registry versions. Should be a no-op
        /// if called on newer registries.
        /// </summary>
        public void Renormalise(GameInstance ksp)
        {
            // We need case insensitive path matching on Windows
            var normalised_installed_files = Platform.IsWindows
                ? new Dictionary <string, InstalledModuleFile>(StringComparer.OrdinalIgnoreCase)
                : new Dictionary <string, InstalledModuleFile>();

            foreach (KeyValuePair <string, InstalledModuleFile> tuple in installed_files)
            {
                string path = CKANPathUtils.NormalizePath(tuple.Key);

                if (Path.IsPathRooted(path))
                {
                    path = ksp.ToRelativeGameDir(path);
                }

                normalised_installed_files[path] = tuple.Value;
            }

            installed_files = normalised_installed_files;
        }
Exemple #7
0
 /// <summary>
 /// Returns a KSP object.
 /// Will initialise a CKAN instance in the KSP dir if it does not already exist,
 /// if the directory contains a valid KSP install.
 /// </summary>
 public GameInstance(IGame game, string gameDir, string name, IUser user, bool scan = true)
 {
     this.game = game;
     Name      = name;
     User      = user;
     // Make sure our path is absolute and has normalised slashes.
     this.gameDir = CKANPathUtils.NormalizePath(Path.GetFullPath(gameDir));
     if (Platform.IsWindows)
     {
         // Normalized slashes are bad for pure drive letters,
         // Path.Combine turns them into drive-relative paths like
         // K:GameData/whatever
         if (Regex.IsMatch(this.gameDir, @"^[a-zA-Z]:$"))
         {
             this.gameDir = $"{this.gameDir}/";
         }
     }
     if (Valid)
     {
         SetupCkanDirectories(scan);
         LoadCompatibleVersions();
     }
 }
Exemple #8
0
 /// <summary>
 /// Given a path relative to this KSP's GameDir, returns the
 /// absolute path on the system.
 /// </summary>
 public string ToAbsoluteGameDir(string path)
 {
     return(CKANPathUtils.ToAbsolute(path, GameDir()));
 }
Exemple #9
0
 /// <summary>
 /// Returns path relative to this KSP's GameDir.
 /// </summary>
 public string ToRelativeGameDir(string path)
 {
     return(CKANPathUtils.ToRelative(path, GameDir()));
 }
Exemple #10
0
 public string TempDir()
 {
     return(CKANPathUtils.NormalizePath(
                Path.Combine(CkanDir(), "temp")
                ));
 }
Exemple #11
0
 public string InstallHistoryDir()
 {
     return(CKANPathUtils.NormalizePath(
                Path.Combine(CkanDir(), "history")
                ));
 }
Exemple #12
0
 public string DownloadCacheDir()
 {
     return(CKANPathUtils.NormalizePath(
                Path.Combine(CkanDir(), "downloads")));
 }
Exemple #13
0
        /// <summary>
        /// Given 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>
        public List <InstallableFile> FindInstallableFiles(ZipFile zipfile, GameInstance ksp)
        {
            string installDir;
            var    files = new List <InstallableFile>();

            // Normalize the path before doing everything else
            string install_to = CKANPathUtils.NormalizePath(this.install_to);

            // The installation path cannot contain updirs
            if (install_to.Contains("/../") || install_to.EndsWith("/.."))
            {
                throw new BadInstallLocationKraken("Invalid installation path: " + install_to);
            }

            if (ksp == null)
            {
                installDir = null;
            }
            else if (install_to == ksp.game.PrimaryModDirectoryRelative ||
                     install_to.StartsWith($"{ksp.game.PrimaryModDirectoryRelative}/"))
            {
                // The installation path can be either "GameData" or a sub-directory of "GameData"
                string subDir = install_to.Substring(ksp.game.PrimaryModDirectoryRelative.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 = CKANPathUtils.NormalizePath(ksp.game.PrimaryModDirectory(ksp) + "/" + subDir);
            }
            else
            {
                switch (install_to)
                {
                case "GameRoot":
                    installDir = ksp.GameDir();
                    break;

                default:
                    if (ksp.game.AllowInstallationIn(install_to, out string path))
                    {
                        installDir = ksp.ToAbsoluteGameDir(path);
                    }
                    else
                    {
                        throw new BadInstallLocationKraken("Unknown install_to " + install_to);
                    }
                    break;
                }
            }

            EnsurePattern();

            // `find` is supposed to match the "topmost" folder. Find it.
            var shortestMatch = find == null ? (int?)null
                : zipfile.Cast <ZipEntry>()
                                .Select(entry => inst_pattern.Match(entry.Name.Replace('\\', '/')))
                                .Where(match => match.Success)
                                .DefaultIfEmpty()
                                .Min(match => match?.Index);

            // 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 dirs and things not prescribed by our install stanza.
                if (!IsWanted(entry.Name, shortestMatch))
                {
                    continue;
                }

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

                // If we have a place to install it, fill that in...
                if (installDir != null)
                {
                    // Get the full name of the file.
                    // Update our file info with the install location
                    file_info.destination = TransformOutputName(
                        ksp.game, entry.Name, installDir, @as);
                    file_info.makedir = AllowDirectoryCreation(
                        ksp.game,
                        ksp?.ToRelativeGameDir(file_info.destination)
                        ?? file_info.destination);
                }

                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 matching {0} to install!", DescribeMatch()));
            }

            return(files);
        }
Exemple #14
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.
 /// IEquatable<> uses this for more efficient comparisons.
 /// </returns>
 public bool Equals(ModuleInstallDescriptor otherStanza)
 {
     if (otherStanza == null)
     {
         // Not even the right type!
         return(false);
     }
     if (CKANPathUtils.NormalizePath(file) != CKANPathUtils.NormalizePath(otherStanza.file))
     {
         return(false);
     }
     if (CKANPathUtils.NormalizePath(find) != CKANPathUtils.NormalizePath(otherStanza.find))
     {
         return(false);
     }
     if (find_regexp != otherStanza.find_regexp)
     {
         return(false);
     }
     if (CKANPathUtils.NormalizePath(install_to) != CKANPathUtils.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);
 }