void applyDiffToFile(string deltaPath, string relativeFilePath, string workingDirectory) { var inputFile = Path.Combine(deltaPath, relativeFilePath); var finalTarget = Path.Combine(workingDirectory, Regex.Replace(relativeFilePath, @".diff$", "")); var tempTargetFile = default(string); Utility.WithTempFile(out tempTargetFile, localAppDirectory); try { // NB: Zero-length diffs indicate the file hasn't actually changed if (new FileInfo(inputFile).Length == 0) { this.Log().Info("{0} exists unchanged, skipping", relativeFilePath); return; } if (relativeFilePath.EndsWith(".diff", StringComparison.InvariantCultureIgnoreCase)) { this.Log().Info("Applying Diff to {0}", relativeFilePath); var msDelta = new MsDeltaCompression(); msDelta.ApplyDelta(inputFile, finalTarget, tempTargetFile); verifyPatchedFile(relativeFilePath, inputFile, tempTargetFile); } else { using (var of = File.OpenWrite(tempTargetFile)) using (var inf = File.OpenRead(inputFile)) { this.Log().Info("Adding new file: {0}", relativeFilePath); inf.CopyTo(of); } } if (File.Exists(finalTarget)) { File.Delete(finalTarget); } var targetPath = Directory.GetParent(finalTarget); if (!targetPath.Exists) { targetPath.Create(); } File.Move(tempTargetFile, finalTarget); } finally { if (File.Exists(tempTargetFile)) { Utility.DeleteFileHarder(tempTargetFile, true); } } }
void updateLink(ShellLink shortcut, string newAppPath) { this.Log().Info("Processing shortcut '{0}'", shortcut.ShortCutFile); var target = Environment.ExpandEnvironmentVariables(shortcut.Target); var targetIsUpdateDotExe = target.EndsWith("update.exe", StringComparison.OrdinalIgnoreCase); this.Log().Info("Old shortcut target: '{0}'", target); if (!targetIsUpdateDotExe) { target = Path.Combine(newAppPath, Path.GetFileName(shortcut.Target)); } this.Log().Info("New shortcut target: '{0}'", target); shortcut.WorkingDirectory = newAppPath; shortcut.Target = target; // NB: If the executable was in a previous version but not in this // one, we should disappear this pin. if (!File.Exists(target)) { shortcut.Dispose(); this.ErrorIfThrows(() => Utility.DeleteFileHarder(target), "Failed to delete outdated pinned shortcut to: " + target); return; } this.Log().Info("Old iconPath is: '{0}'", shortcut.IconPath); if (!File.Exists(shortcut.IconPath) || shortcut.IconPath.IndexOf("app-", StringComparison.OrdinalIgnoreCase) > 1) { var iconPath = Path.Combine(newAppPath, Path.GetFileName(shortcut.IconPath)); if (!File.Exists(iconPath) && targetIsUpdateDotExe) { var executable = shortcut.Arguments.Replace("--processStart ", ""); iconPath = Path.Combine(newAppPath, executable); } this.Log().Info("Setting iconPath to: '{0}'", iconPath); shortcut.IconPath = iconPath; if (!File.Exists(iconPath)) { this.Log().Warn("Tried to use {0} for icon path but didn't exist, falling back to EXE", iconPath); shortcut.IconPath = target; shortcut.IconIndex = 0; } } this.ErrorIfThrows(() => Utility.Retry(() => shortcut.Save(), 2), "Couldn't write shortcut " + shortcut.ShortCutFile); this.Log().Info("Finished shortcut successfully"); }
Task <string> installPackageToAppDir(UpdateInfo updateInfo, ReleaseEntry release) { return(Task.Run(async() => { var zipper = new FastZip(); var target = getDirectoryForRelease(release.Version); // NB: This might happen if we got killed partially through applying the release if (target.Exists) { this.Log().Warn("Found partially applied release folder, killing it: " + target.FullName); await Utility.DeleteDirectory(target.FullName); } target.Create(); this.Log().Info("Writing files to app directory: {0}", target.FullName); zipper.ExtractZip( Path.Combine(updateInfo.PackageDirectory, release.Filename), target.FullName, FastZip.Overwrite.Always, (o) => true, null, @"lib", true); // Move all of the files out of the lib/ dirs in the NuGet package // into our target App directory. // // NB: We sort this list in order to guarantee that if a Net20 // and a Net40 version of a DLL get shipped, we always end up // with the 4.0 version. var libDir = target.GetDirectories().First(x => x.Name.Equals("lib", StringComparison.OrdinalIgnoreCase)); var toMove = libDir.GetDirectories().OrderBy(x => x.Name); toMove.ForEach(ld => { ld.GetDirectories() .ForEachAsync(subdir => subdir.MoveTo(subdir.FullName.Replace(ld.FullName, target.FullName))) .Wait(); ld.GetFiles() .ForEachAsync(file => { var tgt = Path.Combine(target.FullName, file.Name); this.Log().Info("Moving file {0} to {1}", file.FullName, tgt); if (File.Exists(tgt)) { Utility.DeleteFileHarder(tgt, true); } file.MoveTo(tgt); }) .Wait(); }); await Utility.DeleteDirectory(libDir.FullName); return target.FullName; })); }
public static List <ReleaseEntry> BuildReleasesFile(string releasePackagesDir) { var packagesDir = new DirectoryInfo(releasePackagesDir); // Generate release entries for all of the local packages var entriesQueue = new ConcurrentQueue <ReleaseEntry>(); Parallel.ForEach(packagesDir.GetFiles("*.nupkg"), x => { using (var file = x.OpenRead()) { entriesQueue.Enqueue(GenerateFromFile(file, x.Name)); } }); // Write the new RELEASES file to a temp file then move it into // place var entries = entriesQueue.ToList(); var tempFile = default(string); Utility.WithTempFile(out tempFile, releasePackagesDir); try { using (var of = File.OpenWrite(tempFile)) { if (entries.Count > 0) { WriteReleaseFile(entries, of); } } var target = Path.Combine(packagesDir.FullName, "RELEASES"); if (File.Exists(target)) { File.Delete(target); } File.Move(tempFile, target); } finally { if (File.Exists(tempFile)) { Utility.DeleteFileHarder(tempFile, true); } } return(entries); }
public static List <ReleaseEntry> BuildReleasesFile(string releasePackagesDir) { DirectoryInfo info = new DirectoryInfo(releasePackagesDir); ConcurrentQueue <ReleaseEntry> entriesQueue = new ConcurrentQueue <ReleaseEntry>(); Parallel.ForEach <FileInfo>(info.GetFiles("*.nupkg"), delegate(FileInfo x) { using (FileStream stream = x.OpenRead()) { entriesQueue.Enqueue(GenerateFromFile(stream, x.Name, null)); } }); List <ReleaseEntry> releaseEntries = entriesQueue.ToList <ReleaseEntry>(); string path = null; Utility.WithTempFile(out path, releasePackagesDir); try { using (FileStream stream = File.OpenWrite(path)) { if (releaseEntries.Count > 0) { WriteReleaseFile(releaseEntries, stream); } } string str2 = Path.Combine(info.FullName, "RELEASES"); if (File.Exists(str2)) { File.Delete(str2); } File.Move(path, str2); } finally { if (File.Exists(path)) { Utility.DeleteFileHarder(path, true); } } return(releaseEntries); }
void fixPinnedExecutables(SemanticVersion newCurrentVersion, bool removeAll = false) { if (Environment.OSVersion.Version < new Version(6, 1)) { this.Log().Warn("fixPinnedExecutables: Found OS Version '{0}', exiting...", Environment.OSVersion.VersionString); return; } var newCurrentFolder = "app-" + newCurrentVersion; var newAppPath = Path.Combine(rootAppDirectory, newCurrentFolder); var taskbarPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar"); if (!Directory.Exists(taskbarPath)) { this.Log().Info("fixPinnedExecutables: PinnedExecutables directory doesn't exitsts, skiping..."); return; } var resolveLink = new Func <FileInfo, ShellLink>(file => { try { this.Log().Info("Examining Pin: " + file); return(new ShellLink(file.FullName)); } catch (Exception ex) { var message = String.Format("File '{0}' could not be converted into a valid ShellLink", file.FullName); this.Log().WarnException(message, ex); return(null); } }); var shellLinks = (new DirectoryInfo(taskbarPath)).GetFiles("*.lnk").Select(resolveLink).ToArray(); foreach (var shortcut in shellLinks) { try { if (shortcut == null) { continue; } if (String.IsNullOrWhiteSpace(shortcut.Target)) { continue; } if (!shortcut.Target.StartsWith(rootAppDirectory, StringComparison.OrdinalIgnoreCase)) { continue; } if (removeAll) { Utility.DeleteFileHarder(shortcut.ShortCutFile); } else { updateLink(shortcut, newAppPath); } } catch (Exception ex) { var message = String.Format("fixPinnedExecutables: shortcut failed: {0}", shortcut.Target); this.Log().ErrorException(message, ex); } } }
public ReleasePackage ApplyDeltaPackage(ReleasePackage basePackage, ReleasePackage deltaPackage, string outputFile, Action <int> progress = null) { Contract.Requires(deltaPackage != null); Contract.Requires(!String.IsNullOrEmpty(outputFile) && !File.Exists(outputFile)); if (progress == null) { progress = i => { } } ; string workingPath; string deltaPath; using (Utility.WithTempDirectory(out deltaPath, localAppDirectory)) using (Utility.WithTempDirectory(out workingPath, localAppDirectory)) { var fz = new FastZip(); fz.ExtractZip(deltaPackage.InputPackageFile, deltaPath, null); // The percentages here are somewhat arbitrary and reflect typical Bloom file sizes. // Probably not appropriate to push upstream. // If you are thinking about improving this using FastZip's Progress event, check with JohnT. // I experimented with it and found that the PercentCompleted which it reports is useless, // toggling between 0 and 100% repeatedly...possibly it applies to a particular file? // I also found that it is VERY expensive to turn on progress reporting while unzipping; // seems to slow the process down to the point of making it take two to three times as long. // This may of course improve in a later version of the library. progress(10); fz.ExtractZip(basePackage.InputPackageFile, workingPath, null); progress(35); var pathsVisited = new List <string>(); var deltaPathRelativePaths = new DirectoryInfo(deltaPath).GetAllFilesRecursively() .Select(x => x.FullName.Replace(deltaPath + Path.DirectorySeparatorChar, "")) .ToArray(); // Apply all of the .diff files deltaPathRelativePaths .Where(x => x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.EndsWith(".shasum", StringComparison.InvariantCultureIgnoreCase)) .Where(x => !x.EndsWith(".diff", StringComparison.InvariantCultureIgnoreCase) || !deltaPathRelativePaths.Contains(x.Replace(".diff", ".bsdiff"))) .ForEach(file => { pathsVisited.Add(Regex.Replace(file, @"\.(bs)?diff$", "").ToLowerInvariant()); applyDiffToFile(deltaPath, file, workingPath); }); // Delete all of the files that were in the old package but // not in the new one. new DirectoryInfo(workingPath).GetAllFilesRecursively() .Select(x => x.FullName.Replace(workingPath + Path.DirectorySeparatorChar, "").ToLowerInvariant()) .Where(x => x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase) && !pathsVisited.Contains(x)) .ForEach(x => { this.Log().Info("{0} was in old package but not in new one, deleting", x); File.Delete(Path.Combine(workingPath, x)); }); // Update all the files that aren't in 'lib' with the delta // package's versions (i.e. the nuspec file, etc etc). deltaPathRelativePaths .Where(x => !x.StartsWith("lib", StringComparison.InvariantCultureIgnoreCase)) .ForEach(x => { this.Log().Info("Updating metadata file: {0}", x); File.Copy(Path.Combine(deltaPath, x), Path.Combine(workingPath, x), true); }); progress(50); this.Log().Info("Repacking into full package: {0}", outputFile); fz.CreateZip(outputFile, workingPath, true, null); progress(100); } return(new ReleasePackage(outputFile)); } void createDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirectory, Dictionary <string, string> baseFileListing) { // NB: There are three cases here that we'll handle: // // 1. Exists only in new => leave it alone, we'll use it directly. // 2. Exists in both old and new => write a dummy file so we know // to keep it. // 3. Exists in old but changed in new => create a delta file // // The fourth case of "Exists only in old => delete it in new" // is handled when we apply the delta package var relativePath = targetFile.FullName.Replace(workingDirectory.FullName, ""); if (!baseFileListing.ContainsKey(relativePath)) { this.Log().Info("{0} not found in base package, marking as new", relativePath); return; } var oldData = File.ReadAllBytes(baseFileListing[relativePath]); var newData = File.ReadAllBytes(targetFile.FullName); if (bytesAreIdentical(oldData, newData)) { this.Log().Info("{0} hasn't changed, writing dummy file", relativePath); File.Create(targetFile.FullName + ".diff").Dispose(); File.Create(targetFile.FullName + ".shasum").Dispose(); targetFile.Delete(); return; } this.Log().Info("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName); var msDelta = new MsDeltaCompression(); try { msDelta.CreateDelta(baseFileListing[relativePath], targetFile.FullName, targetFile.FullName + ".diff"); } catch (Win32Exception) { this.Log().Warn("We couldn't create a delta for {0}, attempting to create bsdiff", targetFile.Name); var of = default(FileStream); try { of = File.Create(targetFile.FullName + ".bsdiff"); BinaryPatchUtility.Create(oldData, newData, of); // NB: Create a dummy corrupt .diff file so that older // versions which don't understand bsdiff will fail out // until they get upgraded, instead of seeing the missing // file and just removing it. File.WriteAllText(targetFile.FullName + ".diff", "1"); } catch (Exception ex) { this.Log().WarnException(String.Format("We really couldn't create a delta for {0}", targetFile.Name), ex); return; } finally { if (of != null) { of.Dispose(); } } } var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum"); File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); targetFile.Delete(); } void applyDiffToFile(string deltaPath, string relativeFilePath, string workingDirectory) { var inputFile = Path.Combine(deltaPath, relativeFilePath); var finalTarget = Path.Combine(workingDirectory, Regex.Replace(relativeFilePath, @"\.(bs)?diff$", "")); var tempTargetFile = default(string); Utility.WithTempFile(out tempTargetFile, localAppDirectory); try { // NB: Zero-length diffs indicate the file hasn't actually changed if (new FileInfo(inputFile).Length == 0) { this.Log().Info("{0} exists unchanged, skipping", relativeFilePath); return; } if (relativeFilePath.EndsWith(".bsdiff", StringComparison.InvariantCultureIgnoreCase)) { using (var of = File.OpenWrite(tempTargetFile)) using (var inf = File.OpenRead(finalTarget)) { this.Log().Info("Applying BSDiff to {0}", relativeFilePath); BinaryPatchUtility.Apply(inf, () => File.OpenRead(inputFile), of); } verifyPatchedFile(relativeFilePath, inputFile, tempTargetFile); } else if (relativeFilePath.EndsWith(".diff", StringComparison.InvariantCultureIgnoreCase)) { this.Log().Info("Applying MSDiff to {0}", relativeFilePath); var msDelta = new MsDeltaCompression(); msDelta.ApplyDelta(inputFile, finalTarget, tempTargetFile); verifyPatchedFile(relativeFilePath, inputFile, tempTargetFile); } else { using (var of = File.OpenWrite(tempTargetFile)) using (var inf = File.OpenRead(inputFile)) { this.Log().Info("Adding new file: {0}", relativeFilePath); inf.CopyTo(of); } } if (File.Exists(finalTarget)) { File.Delete(finalTarget); } var targetPath = Directory.GetParent(finalTarget); if (!targetPath.Exists) { targetPath.Create(); } File.Move(tempTargetFile, finalTarget); } finally { if (File.Exists(tempTargetFile)) { Utility.DeleteFileHarder(tempTargetFile, true); } } } void verifyPatchedFile(string relativeFilePath, string inputFile, string tempTargetFile) { var shaFile = Regex.Replace(inputFile, @"\.(bs)?diff$", ".shasum"); var expectedReleaseEntry = ReleaseEntry.ParseReleaseEntry(File.ReadAllText(shaFile, Encoding.UTF8)); var actualReleaseEntry = ReleaseEntry.GenerateFromFile(tempTargetFile); if (expectedReleaseEntry.Filesize != actualReleaseEntry.Filesize) { this.Log().Warn("Patched file {0} has incorrect size, expected {1}, got {2}", relativeFilePath, expectedReleaseEntry.Filesize, actualReleaseEntry.Filesize); throw new ChecksumFailedException() { Filename = relativeFilePath }; } if (expectedReleaseEntry.SHA1 != actualReleaseEntry.SHA1) { this.Log().Warn("Patched file {0} has incorrect SHA1, expected {1}, got {2}", relativeFilePath, expectedReleaseEntry.SHA1, actualReleaseEntry.SHA1); throw new ChecksumFailedException() { Filename = relativeFilePath }; } } bool bytesAreIdentical(byte[] oldData, byte[] newData) { if (oldData == null || newData == null) { return(oldData == newData); } if (oldData.LongLength != newData.LongLength) { return(false); } for (long i = 0; i < newData.LongLength; i++) { if (oldData[i] != newData[i]) { return(false); } } return(true); } }
void createDeltaForSingleFile(FileInfo targetFile, DirectoryInfo workingDirectory, Dictionary <string, string> baseFileListing) { // NB: There are three cases here that we'll handle: // // 1. Exists only in new => leave it alone, we'll use it directly. // 2. Exists in both old and new => write a dummy file so we know // to keep it. // 3. Exists in old but changed in new => create a delta file // // The fourth case of "Exists only in old => delete it in new" // is handled when we apply the delta package var relativePath = targetFile.FullName.Replace(workingDirectory.FullName, ""); if (!baseFileListing.ContainsKey(relativePath)) { this.Log().Info("{0} not found in base package, marking as new", relativePath); return; } var oldData = File.ReadAllBytes(baseFileListing[relativePath]); var newData = File.ReadAllBytes(targetFile.FullName); if (bytesAreIdentical(oldData, newData)) { this.Log().Info("{0} hasn't changed, writing dummy file", relativePath); File.Create(targetFile.FullName + ".diff").Dispose(); File.Create(targetFile.FullName + ".shasum").Dispose(); targetFile.Delete(); return; } this.Log().Info("Delta patching {0} => {1}", baseFileListing[relativePath], targetFile.FullName); var msDelta = new MsDeltaCompression(); if (targetFile.Extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) || targetFile.Extension.Equals(".dll", StringComparison.OrdinalIgnoreCase) || targetFile.Extension.Equals(".node", StringComparison.OrdinalIgnoreCase)) { try { msDelta.CreateDelta(baseFileListing[relativePath], targetFile.FullName, targetFile.FullName + ".diff"); goto exit; } catch (Exception) { this.Log().Warn("We couldn't create a delta for {0}, attempting to create bsdiff", targetFile.Name); } } try { using (FileStream of = File.Create(targetFile.FullName + ".bsdiff")) { BinaryPatchUtility.Create(oldData, newData, of); // NB: Create a dummy corrupt .diff file so that older // versions which don't understand bsdiff will fail out // until they get upgraded, instead of seeing the missing // file and just removing it. File.WriteAllText(targetFile.FullName + ".diff", "1"); } } catch (Exception ex) { this.Log().WarnException(String.Format("We really couldn't create a delta for {0}", targetFile.Name), ex); Utility.DeleteFileHarder(targetFile.FullName + ".bsdiff", true); Utility.DeleteFileHarder(targetFile.FullName + ".diff", true); return; } exit: var rl = ReleaseEntry.GenerateFromFile(new MemoryStream(newData), targetFile.Name + ".shasum"); File.WriteAllText(targetFile.FullName + ".shasum", rl.EntryAsString, Encoding.UTF8); targetFile.Delete(); }
Task <string> installPackageToAppDir(UpdateInfo updateInfo, ReleaseEntry release, Action <int> progress) { return(Task.Run(async() => { var target = getDirectoryForRelease(release.Version); // NB: This might happen if we got killed partially through applying the release if (target.Exists) { this.Log().Warn("Found partially applied release folder, killing it: " + target.FullName); await Utility.DeleteDirectory(target.FullName); } target.Create(); var totalSize = GetTotalUncompressedSize(updateInfo, release); var currentExtractedSize = 0L; using (var stream = File.OpenRead(Path.Combine(updateInfo.PackageDirectory, release.Filename))) { // progress.Report(new InstallerStep("Extracting")); var zipIn = new ZipInputStream(stream); var zipEntry = zipIn.GetNextEntry(); while (zipEntry != null) { var entryName = zipEntry.Name; var pathParts = entryName.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var foundLib = false; foreach (var part in pathParts) { if (part.Equals("lib", StringComparison.InvariantCulture)) { foundLib = true; } } if (!foundLib) { zipEntry = zipIn.GetNextEntry(); continue; } var buffer = new byte[4096]; var fullZipToPath = Path.Combine(target.FullName, entryName.Replace('/', Path.DirectorySeparatorChar)); var directoryName = zipEntry.IsFile ? Path.GetDirectoryName(fullZipToPath) : fullZipToPath; if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } using (var streamWriter = File.Create(fullZipToPath)) { currentExtractedSize += zipEntry.Size; StreamUtils.Copy(zipIn, streamWriter, buffer); progress((int)((double)currentExtractedSize * 100.0 / (double)totalSize)); } zipEntry = zipIn.GetNextEntry(); } } this.Log().Info("Writing files to app directory: {0}", target.FullName); // Move all of the files out of the lib/ dirs in the NuGet package // into our target App directory. // // NB: We sort this list in order to guarantee that if a Net20 // and a Net40 version of a DLL get shipped, we always end up // with the 4.0 version. var libDir = target.GetDirectories().First(x => x.Name.Equals("lib", StringComparison.OrdinalIgnoreCase)); var toMove = libDir.GetDirectories().OrderBy(x => x.Name); toMove.ForEach(ld => { ld.GetDirectories() .ForEachAsync(subdir => subdir.MoveTo(subdir.FullName.Replace(ld.FullName, target.FullName))) .Wait(); ld.GetFiles() .ForEachAsync(file => { var tgt = Path.Combine(target.FullName, file.Name); this.Log().Info("Moving file {0} to {1}", file.FullName, tgt); if (File.Exists(tgt)) { Utility.DeleteFileHarder(tgt, true); } file.MoveTo(tgt); }) .Wait(); }); await Utility.DeleteDirectory(libDir.FullName); return target.FullName; })); }
private void applyDiffToFile(string deltaPath, string relativeFilePath, string workingDirectory) { string inputFile = Path.Combine(deltaPath, relativeFilePath); string path = Path.Combine(workingDirectory, Regex.Replace(relativeFilePath, @"\.(bs)?diff$", "")); string str2 = null; Utility.WithTempFile(out str2, this.localAppDirectory); try { if (new FileInfo(inputFile).Length == 0) { this.Log <DeltaPackageBuilder>().Info <string>("{0} exists unchanged, skipping", relativeFilePath); } else { if (relativeFilePath.EndsWith(".bsdiff", StringComparison.InvariantCultureIgnoreCase)) { using (FileStream stream = File.OpenWrite(str2)) { using (FileStream stream2 = File.OpenRead(path)) { this.Log <DeltaPackageBuilder>().Info <string>("Applying BSDiff to {0}", relativeFilePath); BinaryPatchUtility.Apply(stream2, () => File.OpenRead(inputFile), stream); } } this.verifyPatchedFile(relativeFilePath, inputFile, str2); } else if (relativeFilePath.EndsWith(".diff", StringComparison.InvariantCultureIgnoreCase)) { this.Log <DeltaPackageBuilder>().Info <string>("Applying MSDiff to {0}", relativeFilePath); new MsDeltaCompression().ApplyDelta(inputFile, path, str2); this.verifyPatchedFile(relativeFilePath, inputFile, str2); } else { using (FileStream stream3 = File.OpenWrite(str2)) { using (FileStream stream4 = File.OpenRead(inputFile)) { this.Log <DeltaPackageBuilder>().Info <string>("Adding new file: {0}", relativeFilePath); stream4.CopyTo(stream3); } } } if (File.Exists(path)) { File.Delete(path); } DirectoryInfo parent = Directory.GetParent(path); if (!parent.Exists) { parent.Create(); } File.Move(str2, path); } } finally { if (File.Exists(str2)) { Utility.DeleteFileHarder(str2, true); } } }