using System.Buffers; using System.Diagnostics; using System.IO.Compression; namespace PackTool { internal static partial class ZipFileUtils { // Per the .ZIP File Format Specification 4.4.17.1 all slashes should be forward slashes private const char PathSeparatorChar = '/'; private const string PathSeparatorString = "/"; public static string EntryFromPath(string entry, int offset, int length, ref char[] buffer, bool appendPathSeparator = false) { Debug.Assert(length <= entry.Length - offset); Debug.Assert(buffer != null); // Remove any leading slashes from the entry name: while (length > 0) { if (entry[offset] != Path.DirectorySeparatorChar && entry[offset] != Path.AltDirectorySeparatorChar) break; offset++; length--; } if (length == 0) return appendPathSeparator ? PathSeparatorString : string.Empty; int resultLength = appendPathSeparator ? length + 1 : length; EnsureCapacity(ref buffer, resultLength); entry.CopyTo(offset, buffer, 0, length); // '/' is a more broadly recognized directory separator on all platforms (eg: mac, linux) // We don't use Path.DirectorySeparatorChar or AltDirectorySeparatorChar because this is // explicitly trying to standardize to '/' for (int i = 0; i < length; i++) { char ch = buffer[i]; if (ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar) buffer[i] = PathSeparatorChar; } if (appendPathSeparator) buffer[length] = PathSeparatorChar; return new string(buffer, 0, resultLength); } public static void EnsureCapacity(ref char[] buffer, int min) { Debug.Assert(buffer != null); Debug.Assert(min > 0); if (buffer.Length < min) { int newCapacity = buffer.Length * 2; if (newCapacity < min) newCapacity = min; char[] oldBuffer = buffer; buffer = ArrayPool.Shared.Rent(newCapacity); ArrayPool.Shared.Return(oldBuffer); } } public static bool IsDirEmpty(DirectoryInfo possiblyEmptyDir) { using (IEnumerator enumerator = Directory.EnumerateFileSystemEntries(possiblyEmptyDir.FullName).GetEnumerator()) return !enumerator.MoveNext(); } } class Program { private static void CreateZipFileFromDir(string sourceDirectoryName, string destinationArchiveFileName, string[] excludeFolders) { sourceDirectoryName = Path.GetFullPath(sourceDirectoryName); destinationArchiveFileName = Path.GetFullPath(destinationArchiveFileName); using (ZipArchive archive = ZipFile.Open(destinationArchiveFileName, ZipArchiveMode.Create)) { //add files and directories DirectoryInfo di = new DirectoryInfo(sourceDirectoryName); var basePath = di.FullName; // Windows' MaxPath (260) is used as an arbitrary default capacity, as it is likely // to be greater than the length of typical entry names from the file system, even // on non-Windows platforms. The capacity will be increased, if needed. const int DefaultCapacity = 260; char[] entryNameBuffer = ArrayPool.Shared.Rent(DefaultCapacity); try { foreach (FileSystemInfo file in di.EnumerateFileSystemInfos("*", SearchOption.AllDirectories)) { bool exclude = false; var fileName = file.FullName.ToLower(); foreach(var excludeFolder in excludeFolders) { if (fileName.StartsWith(excludeFolder)) { exclude = true; break; } } if (exclude) { continue; } int entryNameLength = file.FullName.Length - basePath.Length; if (file is FileInfo) { // Create entry for file: string entryName = ZipFileUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer); ZipFileExtensions.CreateEntryFromFile(archive, file.FullName, entryName); } else { // Entry marking an empty dir: if (file is DirectoryInfo possiblyEmpty && ZipFileUtils.IsDirEmpty(possiblyEmpty)) { // FullName never returns a directory separator character on the end, // but Zip archives require it to specify an explicit directory: string entryName = ZipFileUtils.EntryFromPath(file.FullName, basePath.Length, entryNameLength, ref entryNameBuffer, appendPathSeparator: true); archive.CreateEntry(entryName); } } } // foreach } finally { ArrayPool.Shared.Return(entryNameBuffer); } } } private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) { // Get the subdirectories for the specified directory. DirectoryInfo dir = new DirectoryInfo(sourceDirName); if (!dir.Exists) { throw new DirectoryNotFoundException( "Source directory does not exist or could not be found: " + sourceDirName); } DirectoryInfo[] dirs = dir.GetDirectories(); // If the destination directory doesn't exist, create it. Directory.CreateDirectory(destDirName); // Get the files in the directory and copy them to the new location. FileInfo[] files = dir.GetFiles(); foreach (FileInfo file in files) { string tempPath = Path.Combine(destDirName, file.Name); file.CopyTo(tempPath, true); } // If copying subdirectories, copy them and their contents to new location. if (copySubDirs) { foreach (DirectoryInfo subdir in dirs) { string tempPath = Path.Combine(destDirName, subdir.Name); DirectoryCopy(subdir.FullName, tempPath, copySubDirs); } } } private static void CopyFiles(string reveiveSource, string copyDest, string repoName, string productName, bool unzip) { var distRootFolder = Environment.GetEnvironmentVariable("DistFolder"); if (string.IsNullOrEmpty(distRootFolder)) { Console.WriteLine("The environment variable [DistFolder] is missing, use default dist folder."); //Using default repackFolder distRootFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Packages"); } if (!Directory.Exists(distRootFolder)) { Directory.CreateDirectory(distRootFolder); } var repoDistFolder = Path.Combine(distRootFolder, repoName, "Dist"); if (!Directory.Exists(repoDistFolder)) { Directory.CreateDirectory(repoDistFolder); } var productRootFolder = Environment.GetEnvironmentVariable("ProductFolder"); if (string.IsNullOrEmpty(productRootFolder)) { Console.WriteLine("The environment variable [ProductFolder] is missing, use default product folder."); //Using default repackFolder productRootFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Product"); } if (!Directory.Exists(productRootFolder)) { Directory.CreateDirectory(productRootFolder); } var productFolder = Path.Combine(productRootFolder, productName); if (!Directory.Exists(productFolder)) { Directory.CreateDirectory(productFolder); } var source = Path.Combine(repoDistFolder, reveiveSource); var dest = Path.Combine(productFolder, copyDest); if (!File.Exists(source) && !Directory.Exists(source)) { Console.WriteLine($"The received file/s {reveiveSource} can not be found."); return; } if (File.Exists(source)) { if (unzip) { ZipFile.ExtractToDirectory(source, dest, true); } else { var destInfo = new FileInfo(dest); if (destInfo.Directory != null) { var destFolder = destInfo.Directory.FullName; if (!Directory.Exists(destFolder)) { Directory.CreateDirectory(destFolder); } } File.Copy(source, dest, true); } } else { if (!Directory.Exists(dest)) { Directory.CreateDirectory(dest); } DirectoryCopy(source, dest, true); } } private static void UpdateVersion(string productName, string repoName, string repoVersion) { var productRootFolder = Environment.GetEnvironmentVariable("ProductFolder"); if (string.IsNullOrEmpty(productRootFolder)) { Console.WriteLine("The environment variable [ProductFolder] is missing, use default product folder."); //Using default repackFolder productRootFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Product"); } if (!Directory.Exists(productRootFolder)) { Directory.CreateDirectory(productRootFolder); } var productFolder = Path.Combine(productRootFolder, productName); if (!Directory.Exists(productFolder)) { Directory.CreateDirectory(productFolder); } var projectVersionFile = Path.Combine(productFolder, "Versions.txt"); var versions = new List(); if (File.Exists(projectVersionFile)) { versions = File.ReadLines(projectVersionFile).ToList(); bool updated = false; for (var i = 0; i < versions.Count; i++) { var line = versions[i]; var nameAndVersion = line.Split('='); var name = nameAndVersion[0]; if (name == repoName) { versions[i] = repoName + "=" + repoVersion; File.WriteAllLines(projectVersionFile, versions); updated = true; break; } } if (!updated) { versions.Add(repoName + "=" + repoVersion); File.WriteAllLines(projectVersionFile, versions); } } else { versions.Add(repoName + "=" + repoVersion); File.WriteAllLines(projectVersionFile, versions); } } private static void RepackProduct(string productName, string[] excludeFolderNames, bool attachTimestamp) { var productRootFolder = Environment.GetEnvironmentVariable("ProductFolder"); if (string.IsNullOrEmpty(productRootFolder)) { Console.WriteLine("The environment variable [ProductFolder] is missing, use default product folder."); //Using default repackFolder productRootFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Product"); } if (!Directory.Exists(productRootFolder)) { Directory.CreateDirectory(productRootFolder); } var productFolder = Path.Combine(productRootFolder, productName); if (!Directory.Exists(productFolder)) { Directory.CreateDirectory(productFolder); } var excludeFolders = new List(); foreach (var excludeFolderName in excludeFolderNames) { excludeFolders.Add(Path.Combine(productFolder.ToLower(), excludeFolderName.ToLower())); } var repackRootFolder = Environment.GetEnvironmentVariable("RepackFolder"); if (string.IsNullOrEmpty(repackRootFolder)) { Console.WriteLine("The environment variable [RepackFolder] is missing, use default repack folder."); //Using default repackFolder repackRootFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Repack"); } if (!Directory.Exists(repackRootFolder)) { Directory.CreateDirectory(repackRootFolder); } var repackFolder = Path.Combine(repackRootFolder, productName, DateTime.Now.ToString("yyyyMMdd")); if (!Directory.Exists(repackFolder)) { Directory.CreateDirectory(repackFolder); } var repackFilePath = Path.Combine(repackFolder, $"{productName}.zip"); if (attachTimestamp) { var timestamp = DateTime.Now.ToString("yyyyMMdd"); var buildTime = 1; var formattedBuildTime = buildTime.ToString("D2"); repackFilePath = Path.Combine(repackFolder, $"{productName}_{timestamp}.{formattedBuildTime}.zip"); while (File.Exists(repackFilePath)) { buildTime++; formattedBuildTime = buildTime.ToString("D2"); repackFilePath = Path.Combine(repackFolder, $"{productName}_{timestamp}.{formattedBuildTime}.zip"); } } if (File.Exists(repackFilePath)) { File.Delete(repackFilePath); } CreateZipFileFromDir(productFolder, repackFilePath, excludeFolders.ToArray()); } private static void HandleCopy(Dictionary argMap) { //Copy the file/s if (!argMap.TryGetValue("/rs", out var receivedSource)) { Console.WriteLine($"The [/rs] - received source argument is missing."); return; } if (!argMap.TryGetValue("/cd", out var extractDest)) { Console.WriteLine($"The [/cd] - copy dest argument is missing."); return; } if (!argMap.TryGetValue("/pn", out var productName)) { Console.WriteLine($"The [/pn] - product name argument is missing."); return; } if (!argMap.TryGetValue("/rn", out var repoName)) { Console.WriteLine($"The [/rn] - repo name argument is missing."); return; } var unzip = false; if (argMap.TryGetValue("/unzip", out var unzipStr)) { if (!bool.TryParse(unzipStr, out unzip)) { Console.WriteLine($"The unzip argument {unzipStr} is not a bool value."); } } CopyFiles(receivedSource, extractDest, repoName, productName, unzip); } private static void HandleUpdateVersion(Dictionary argMap) { //Only update version. if (!argMap.TryGetValue("/pn", out var productName)) { Console.WriteLine($"The [/pn] - product name argument is missing."); return; } if (!argMap.TryGetValue("/rn", out var repoName)) { Console.WriteLine($"The [/rn] - repo name argument is missing."); return; } if (!argMap.TryGetValue("/rv", out var repoVersion)) { Console.WriteLine($"The [/rv] - repo version argument is missing."); return; } UpdateVersion(productName, repoName, repoVersion); } private static void HandleRepack(Dictionary argMap) { if (!argMap.TryGetValue("/pn", out var productName)) { Console.WriteLine($"The [/pn] - product name argument is missing."); return; } var excludFoldereNames = new List(); if (argMap.TryGetValue("/ef", out var excludes)) { excludFoldereNames.AddRange(excludes.Split(',')); } Console.WriteLine($"Exclude folders: [{string.Join(',', excludFoldereNames)}]"); var attachTimestamp = true; if (argMap.TryGetValue("/at", out var attachTimestampStr)) { if (!bool.TryParse(attachTimestampStr, out attachTimestamp)) { Console.WriteLine($"The attachTimestamp argument {attachTimestampStr} is not a bool value."); } } RepackProduct(productName, excludFoldereNames.ToArray(), attachTimestamp); } private static void HandleFull(Dictionary argMap) { if (!argMap.TryGetValue("/rs", out var receivedSource)) { Console.WriteLine($"The [/rs] - received source argument is missing."); return; } if (!argMap.TryGetValue("/cd", out var extractDest)) { Console.WriteLine($"The [/cd] - copy dest argument is missing."); return; } if (!argMap.TryGetValue("/pn", out var productName)) { Console.WriteLine($"The [/pn] - product name argument is missing."); return; } if (!argMap.TryGetValue("/rn", out var repoName)) { Console.WriteLine($"The [/rn] - repo name argument is missing."); return; } if (!argMap.TryGetValue("/rv", out var repoVersion)) { Console.WriteLine($"The [/rv] - repo version argument is missing."); return; } var excludFoldereNames = new List(); if (argMap.TryGetValue("/ef", out var excludes)) { excludFoldereNames.AddRange(excludes.Split(',')); } var unzip = false; if (argMap.TryGetValue("/unzip", out var unzipStr)) { if (!bool.TryParse(unzipStr, out unzip)) { Console.WriteLine($"The unzip argument {unzipStr} is not a bool value."); } } CopyFiles(receivedSource, extractDest, repoName, productName, unzip); UpdateVersion(productName, repoName, repoVersion); var attachTimestamp = true; if (argMap.TryGetValue("/at", out var attachTimestampStr)) { if (!bool.TryParse(attachTimestampStr, out attachTimestamp)) { Console.WriteLine($"The attachTimestamp argument {attachTimestampStr} is not a bool value."); } } RepackProduct(productName, excludFoldereNames.ToArray(), attachTimestamp); } private static void StartInno(string innoSourceFileName, string setupSource, string productName, string productVersion, string outputDir) { var productRootFolder = Environment.GetEnvironmentVariable("ProductFolder"); if (string.IsNullOrEmpty(productRootFolder)) { Console.WriteLine("The environment variable [ProductFolder] is missing, use default product folder."); //Using default repackFolder productRootFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Product"); } if (!Directory.Exists(productRootFolder)) { Directory.CreateDirectory(productRootFolder); } var productFolder = Path.Combine(productRootFolder, productName); if (!Directory.Exists(productFolder)) { Directory.CreateDirectory(productFolder); } var innoSourceFilePath = Path.Combine(productFolder, innoSourceFileName); var projectSetupSource = Path.Combine(productFolder, setupSource); var setupOutputDir = Path.Combine(productFolder, outputDir); if (!File.Exists(innoSourceFilePath)) { Console.WriteLine("The inno setup source file does not exist."); return; } if (!Directory.Exists(projectSetupSource)) { Console.WriteLine("The setup project source folder does not exist"); return; } if (Directory.GetFiles(projectSetupSource).Length == 0) { Console.WriteLine("No file in setup project source folder."); return; } if (!Directory.Exists(setupOutputDir)) { Directory.CreateDirectory(setupOutputDir); } var iscc_argument = $"/dProjectFolder={projectSetupSource} /dSetupAppVersion={productVersion} /dSetupOutputDir={setupOutputDir} {innoSourceFilePath}"; Console.WriteLine($"Inno arg: {iscc_argument}"); var process = Process.Start(new ProcessStartInfo("ISCC", iscc_argument) { UseShellExecute = true }); process?.WaitForExit(); } private static void HandleInno(Dictionary argMap) { if (!argMap.TryGetValue("/pn", out var productName)) { Console.WriteLine($"The [/pn] - product name argument is missing."); return; } if (!argMap.TryGetValue("/pv", out var productVersion)) { Console.WriteLine($"The [/pv] - product version argument is missing."); return; } if (!argMap.TryGetValue("/ss", out var setupSource)) { Console.WriteLine($"The [/ss] - setup source argument is missing."); return; } if (!argMap.TryGetValue("/od", out var outputDir)) { Console.WriteLine($"The [/od] - output dir argument is missing."); return; } if (!argMap.TryGetValue("/iss", out var issFile)) { Console.WriteLine($"The [/iss] - inno setup source argument is missing."); return; } StartInno(issFile, setupSource, productName, productVersion, outputDir); } private static void DeleteFileOrFolder(string productName, string deleteTraget) { var productRootFolder = Environment.GetEnvironmentVariable("ProductFolder"); if (string.IsNullOrEmpty(productRootFolder)) { Console.WriteLine("The environment variable [ProductFolder] is missing, use default product folder."); //Using default repackFolder productRootFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Product"); } if (!Directory.Exists(productRootFolder)) { Directory.CreateDirectory(productRootFolder); } var productFolder = Path.Combine(productRootFolder, productName); if (!Directory.Exists(productFolder)) { Directory.CreateDirectory(productFolder); } var target = Path.Combine(productFolder, deleteTraget); if (File.Exists(target)) { File.Delete(target); } else if (Directory.Exists(target)) { Directory.Delete(target, true); } else { Console.WriteLine($"File or dir {deleteTraget} does not exist."); } } private static void HandleDelete(Dictionary argMap) { if (!argMap.TryGetValue("/pn", out var productName)) { Console.WriteLine($"The [/pn] - product name argument is missing."); return; } if (!argMap.TryGetValue("/dt", out var deleteTarget)) { Console.WriteLine($"The [/dt] - delete target argument is missing."); return; } DeleteFileOrFolder(productName, deleteTarget); } static void Main(string[] args) { var argMap = new Dictionary(); foreach (var arg in args) { var nameAndValue = arg.Split('='); if (nameAndValue.Length == 2) { var name = nameAndValue[0].ToLower(); if (string.IsNullOrEmpty(name)) { Console.WriteLine("An empty arg name is not acceptable."); return; } if (argMap.ContainsKey(name)) { Console.WriteLine($"Arg name {name} already exists"); return; } argMap.Add(name, nameAndValue[1]); } } if (!argMap.TryGetValue("/mode", out var mode)) { mode = "full"; } Console.WriteLine($"Run in {mode} mode."); switch (mode) { case "full": //Copy files then pack the files then update the version then pack the files. HandleFull(argMap); break; case "copy": HandleCopy(argMap); break; case "version": HandleUpdateVersion(argMap); break; case "repack": //Obly Pack the files. HandleRepack(argMap); break; case "inno": HandleInno(argMap); break; case "delete": HandleDelete(argMap); break; default: Console.WriteLine($"Not suported mode: {mode}"); break; } } } }