/// <summary> /// Read all files under the source path into a single destination file. /// </summary> /// <param name="srcPath">Full path of source directory</param> /// <param name="dstFilePath">Full path of destination file</param> /// <param name="signingCertPath">PFX file containing </param> /// <param name="certPassword">password securing the pfx file</param> public static void FolderToFile(string srcPath, string dstFilePath, string signingCertPath = null, string certPassword = null) { // list out name+hash -> [path] // write this to a single file // gzip that file var tmpPath = dstFilePath+".tmp"; var filePaths = new Dictionary<string, PathList>(); var symLinks = new Dictionary<string, string>(); // link path -> target path // find distinct files var files = NativeIO.EnumerateFiles(new PathInfo(srcPath).FullNameUnc, (symPath, targetPath)=>{ if (IsSubpath(srcPath, targetPath)) { symLinks.Add(symPath, targetPath); return false; } return true; }, searchOption: SearchOption.AllDirectories); foreach (var file in files) { var hash = HashOf(file); Add(hash, file, filePaths); } // pack everything into a temp file if (File.Exists(tmpPath)) File.Delete(tmpPath); using (var fs = File.OpenWrite(tmpPath)) { // Write data files foreach (var fileKey in filePaths.Keys) { var pathList = filePaths[fileKey]; var catPaths = Encoding.UTF8.GetBytes(string.Join("|", Filter(pathList.Paths, srcPath))); // Write <MD5:16 bytes> fs.Write(pathList.HashData, 0, 16); // Write <length:8 bytes><paths:utf8 str> WriteLength(catPaths.Length, fs); fs.Write(catPaths, 0, catPaths.Length); var info = NativeIO.ReadFileDetails(new PathInfo(pathList.Paths[0])); // Write <length:8 bytes><data:byte array> WriteLength((long)info.Length, fs); using (var inf = NativeIO.OpenFileStream(info.PathInfo, FileAccess.Read)) inf.CopyTo(fs); fs.Flush(); } // Write symbolic links foreach (var linkSrc in symLinks.Keys) { var linkTarget = symLinks[linkSrc]; var linkData = Encoding.UTF8.GetBytes(string.Join("|", Filter(new[] { linkSrc, linkTarget }, srcPath))); // Write <zeros:16 bytes> WriteLength(0, fs); WriteLength(0, fs); // Write <length:8 bytes><path pair:utf8 str>, path pair is 'src|target' WriteLength(linkData.Length, fs); fs.Write(linkData, 0, linkData.Length); // Write <length:8 bytes>, always zero (there is not file content in a link) WriteLength(0, fs); } fs.Flush(); } // If cert, write to *another* temp file with the signing header in place if ( ! string.IsNullOrWhiteSpace(signingCertPath)) { var tmpSignPath = tmpPath + ".signed"; try { using (var cat = File.OpenRead(tmpPath)) { var signingBytes = Crypto.BuildSigningHeader(cat,signingCertPath, certPassword); cat.Seek(0, SeekOrigin.Begin); using (var final = File.Open(tmpSignPath, FileMode.Create, FileAccess.Write)) { final.Write(signingBytes, 0, signingBytes.Length); cat.CopyTo(final); final.Flush(); } } File.Delete(tmpPath); // wipe the old one File.Move(tmpSignPath, tmpPath); // use the new one for compression } catch (Exception ex) { Console.WriteLine("Signing failed: "+ex); throw; } } // Compress the file if (File.Exists(dstFilePath)) File.Delete(dstFilePath); using (var compressing = new GZipStream(File.OpenWrite(dstFilePath), CompressionLevel.Optimal)) using (var cat = File.OpenRead(tmpPath)) { cat.CopyTo(compressing, 65536); compressing.Flush(); } // Kill the temp file File.Delete(tmpPath); }