/// <summary> /// Applies a patch (content file) to the destination /// </summary> /// <param name="destination">The destination that contains the previous version of the data</param> /// <param name="patch">The content file that the destination is patched with</param> public void Patch(string[] destination, Library.Interface.ICompression patch) { Snapshots.ISystemIO SystemIO = Utility.Utility.IsClientLinux ? (Snapshots.ISystemIO)new Snapshots.SystemIOLinux() : (Snapshots.ISystemIO)new Snapshots.SystemIOWindows(); if (m_partialDeltas == null) m_partialDeltas = new Dictionary<string, XervBackup.Library.Utility.TempFile>(); if (m_folderTimestamps == null) m_folderTimestamps = new Dictionary<string, DateTime>(); for (int i = 0; i < destination.Length; i++) destination[i] = Utility.Utility.AppendDirSeparator(destination[i]); bool isUtc = patch.FileExists(UTC_TIME_MARKER); //Set up the filter system to avoid dealing with filtered items FilterHelper fh = new FilterHelper(this, destination, m_filter); //Delete all files that were removed if (patch.FileExists(DELETED_FILES)) foreach (string s in fh.Filterlist(FilenamesFromPlatformIndependant(patch.ReadAllLines(DELETED_FILES)), false)) { if (SystemIO.FileExists(s)) { try { //TODO: Perhaps read ahead in patches to prevent creation long size = SystemIO.FileLength(s); SystemIO.FileDelete(s); if (m_stat as RestoreStatistics != null) { (m_stat as RestoreStatistics).FilesRestored--; (m_stat as RestoreStatistics).SizeOfRestoredFiles -= size; (m_stat as RestoreStatistics).FilesDeleted++; } } catch (Exception ex) { if (m_stat != null) m_stat.LogError(string.Format(Strings.RSyncDir.DeleteFileError, s, ex.Message), ex); Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.DeleteFileError, s, ex.Message), XervBackup.Library.Logging.LogMessageType.Warning, ex); } } else { Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.FileToDeleteMissingError, s), XervBackup.Library.Logging.LogMessageType.Warning); } } //Delete all folders that were removed if (patch.FileExists(DELETED_FOLDERS)) { if (m_folders_to_delete == null) m_folders_to_delete = new List<string>(); List<string> deletedfolders = new List<string>(fh.Filterlist(FilenamesFromPlatformIndependant(patch.ReadAllLines(DELETED_FOLDERS)), true)); //Make sure subfolders are deleted first deletedfolders.Sort(); deletedfolders.Reverse(); //Append to the list of folders to remove. //The folders are removed when the patch sequence is finalized, //because the deleted file list is not present until //the last content file has been applied. m_folders_to_delete.AddRange(deletedfolders); } //Add folders. This mainly applies to empty folders, //as non-empty folders will also be created when files are restored if (patch.FileExists(ADDED_FOLDERS)) { List<string> addedfolders = new List<string>(fh.Filterlist(FilenamesFromPlatformIndependant(patch.ReadAllLines(ADDED_FOLDERS)), true)); //Make sure topfolders are created first addedfolders.Sort(); foreach (string s in addedfolders) { if (!SystemIO.DirectoryExists(s)) try { SystemIO.DirectoryCreate(s); if (m_stat as RestoreStatistics != null) (m_stat as RestoreStatistics).FoldersRestored++; } catch (Exception ex) { if (m_stat != null) m_stat.LogError(string.Format(Strings.RSyncDir.CreateFolderError, s, ex.Message), ex); Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.CreateFolderError, s, ex.Message), XervBackup.Library.Logging.LogMessageType.Warning, ex); } } } if (patch.FileExists(ADDED_FOLDERS_TIMESTAMPS)) { //These times are always utc string[] folders = FilenamesFromPlatformIndependant(patch.ReadAllLines(ADDED_FOLDERS)); string[] timestamps = patch.ReadAllLines(ADDED_FOLDERS_TIMESTAMPS); for (int i = 0; i < folders.Length; i++) m_folderTimestamps[RSyncDir.GetFullPathFromRelname(destination, folders[i])] = Utility.Utility.EPOCH.AddSeconds(long.Parse(timestamps[i])); } if (patch.FileExists(UPDATED_FOLDERS) && patch.FileExists(UPDATED_FOLDERS_TIMESTAMPS)) { //These times are always utc string[] folders = FilenamesFromPlatformIndependant(patch.ReadAllLines(UPDATED_FOLDERS)); string[] timestamps = patch.ReadAllLines(UPDATED_FOLDERS_TIMESTAMPS); long l; for (int i = 0; i < folders.Length; i++) if (long.TryParse(timestamps[i], out l)) m_folderTimestamps[RSyncDir.GetFullPathFromRelname(destination, folders[i])] = Utility.Utility.EPOCH.AddSeconds(l); } PartialEntryRecord pe = null; if (patch.FileExists(INCOMPLETE_FILE)) pe = new PartialEntryRecord(patch.ReadAllLines(INCOMPLETE_FILE)); PartialEntryRecord fe = null; if (patch.FileExists(COMPLETED_FILE)) fe = new PartialEntryRecord(patch.ReadAllLines(COMPLETED_FILE)); int lastPg = -1; string contentprefix = Utility.Utility.AppendDirSeparator(CONTENT_ROOT); List<string> contentfiles = m_filter.FilterList(contentprefix, patch.ListFiles(contentprefix)); string deltaprefix = Utility.Utility.AppendDirSeparator(DELTA_ROOT); List<string> deltafiles = m_filter.FilterList(deltaprefix, patch.ListFiles(deltaprefix)); string symlinkprefix = Utility.Utility.AppendDirSeparator(SYMLINK_ROOT); List<string> symlinks = m_filter.FilterList(symlinkprefix, patch.ListFiles(symlinkprefix)); long totalfiles = deltafiles.Count + contentfiles.Count; long fileindex = 0; //Restore new files foreach (string s in contentfiles) { string target = GetFullPathFromRelname(destination, s.Substring(contentprefix.Length)); try { if (!SystemIO.DirectoryExists(SystemIO.PathGetDirectoryName(target))) { Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.RestoreFolderMissingError, target), XervBackup.Library.Logging.LogMessageType.Warning); SystemIO.DirectoryCreate(SystemIO.PathGetDirectoryName(target)); } //Update each 0.5% int pg = (int)((fileindex / (double)totalfiles) * 200); if (pg != lastPg) { ProgressEvent(pg / 2, target); lastPg = pg; } using (System.IO.Stream s1 = patch.OpenRead(s)) { PartialEntryRecord pex = null; Utility.TempFile partialFile = null; if (pe != null && string.Equals(pe.PlatformConvertedFilename, s)) pex = pe; //The file is incomplete else if (fe != null && string.Equals(fe.PlatformConvertedFilename, s)) pex = fe; //The file has the final segment if (pex != null && string.Equals(pex.PlatformConvertedFilename, s)) { //Ensure that the partial file list is in the correct state if (pex.StartOffset == 0 && m_partialDeltas.ContainsKey(s)) throw new Exception(string.Format(Strings.RSyncDir.InvalidPartialFileEntry, s)); else if (pex.StartOffset != 0 && !m_partialDeltas.ContainsKey(s)) throw new Exception(string.Format(Strings.RSyncDir.InvalidPartialFileEntry, s)); else if (pex.StartOffset == 0) //First entry, so create a temp file m_partialDeltas.Add(s, new XervBackup.Library.Utility.TempFile()); partialFile = m_partialDeltas[s]; } else if (m_partialDeltas.ContainsKey(s)) throw new Exception(string.Format(Strings.RSyncDir.FileShouldBePartialError, s)); long startOffset = pex == null ? 0 : pex.StartOffset; using (System.IO.Stream s2 = SystemIO.FileOpenWrite(partialFile == null ? target : (string)partialFile)) { if (s2.Length != startOffset) throw new Exception(string.Format(Strings.RSyncDir.InvalidPartialFileEntry, s)); s2.Position = startOffset; if (startOffset == 0) s2.SetLength(0); Utility.Utility.CopyStream(s1, s2); } if (pex != null && pex == fe) { if (SystemIO.FileExists(target)) SystemIO.FileDelete(target); SystemIO.FileMove(partialFile, target); partialFile.Dispose(); m_partialDeltas.Remove(s); } if (m_stat is RestoreStatistics && (partialFile == null || pex == fe)) { (m_stat as RestoreStatistics).FilesRestored++; (m_stat as RestoreStatistics).SizeOfRestoredFiles += SystemIO.FileLength(target); } } if (File.Exists(target)) { DateTime t = patch.GetLastWriteTime(s); if (!isUtc) t = t.ToUniversalTime(); try { SystemIO.FileSetLastWriteTimeUtc(target, t); } catch (Exception ex) { if (m_stat != null) m_stat.LogWarning(string.Format(Strings.RSyncDir.FailedToSetFileWriteTime, target, ex.Message), ex); } } } catch (Exception ex) { if (m_stat != null) m_stat.LogError(string.Format(Strings.RSyncDir.RestoreFileError, s, ex.Message), ex); Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.RestoreFileError, s, ex.Message), XervBackup.Library.Logging.LogMessageType.Error, ex); } fileindex++; } //Patch modfied files foreach (string s in deltafiles) { string target = GetFullPathFromRelname(destination, s.Substring(deltaprefix.Length)); try { //Update each 0.5% int pg = (int)((fileindex / (double)totalfiles) * 200); if (pg != lastPg) { ProgressEvent(pg / 2, target); lastPg = pg; } if (!SystemIO.DirectoryExists(SystemIO.PathGetDirectoryName(target))) { Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.RestoreFolderDeltaError, target), XervBackup.Library.Logging.LogMessageType.Warning); SystemIO.DirectoryCreate(SystemIO.PathGetDirectoryName(target)); } PartialEntryRecord pex = null; if (pe != null && string.Equals(pe.PlatformConvertedFilename, s)) pex = pe; //The file is incomplete else if (fe != null && string.Equals(fe.PlatformConvertedFilename, s)) pex = fe; //The file has the final segment Utility.TempFile tempDelta = null; if (pex != null && string.Equals(pex.PlatformConvertedFilename, s)) { //Ensure that the partial file list is in the correct state if (pex.StartOffset == 0 && m_partialDeltas.ContainsKey(s)) throw new Exception(string.Format(Strings.RSyncDir.InvalidPartialFileEntry, s)); else if (pex.StartOffset != 0 && !m_partialDeltas.ContainsKey(s)) throw new Exception(string.Format(Strings.RSyncDir.InvalidPartialFileEntry, s)); else if (pex.StartOffset == 0) //First entry, so create a temp file m_partialDeltas.Add(s, new XervBackup.Library.Utility.TempFile()); //Dump the content in the temp file at the specified offset using (System.IO.Stream st = SystemIO.FileOpenWrite(m_partialDeltas[s])) { if (st.Length != pex.StartOffset) throw new Exception(string.Format(Strings.RSyncDir.InvalidPartialFileEntry, s)); st.Position = pex.StartOffset; using (System.IO.Stream s2 = patch.OpenRead(s)) Utility.Utility.CopyStream(s2, st); } //We can't process it until it is received completely if (pex != fe) continue; tempDelta = m_partialDeltas[s]; m_partialDeltas.Remove(s); } else if (m_partialDeltas.ContainsKey(s)) throw new Exception(string.Format(Strings.RSyncDir.FileShouldBePartialError, s)); using (Utility.TempFile tempfile = new Utility.TempFile()) using (tempDelta) //May be null, but the using directive does not care { //Use either the patch directly, or the partial temp file System.IO.Stream deltaStream = tempDelta == null ? patch.OpenRead(s) : SystemIO.FileOpenRead(tempDelta); using (System.IO.Stream s2 = deltaStream) using (System.IO.Stream s1 = SystemIO.FileOpenRead(target)) using (System.IO.Stream s3 = SystemIO.FileCreate(tempfile)) SharpRSync.Interface.PatchFile(s1, s2, s3); if (m_stat as RestoreStatistics != null) { (m_stat as RestoreStatistics).SizeOfRestoredFiles -= SystemIO.FileLength(target); (m_stat as RestoreStatistics).SizeOfRestoredFiles += SystemIO.FileLength(tempfile); (m_stat as RestoreStatistics).FilesPatched++; } SystemIO.FileDelete(target); try { SystemIO.FileMove(tempfile, target); } catch { //The OS sometimes reports the file as existing even after a delete // this seems to be related to MS Security Essentials? System.Threading.Thread.Sleep(500); SystemIO.FileMove(tempfile, target); } } if (File.Exists(target)) { DateTime t = patch.GetLastWriteTime(s); if (!isUtc) t = t.ToUniversalTime(); try { SystemIO.FileSetLastWriteTimeUtc(target, t); } catch (Exception ex) { if (m_stat != null) m_stat.LogWarning(string.Format(Strings.RSyncDir.FailedToSetFileWriteTime, target, ex.Message), ex); } } } catch (Exception ex) { if (m_stat != null) m_stat.LogError(string.Format(Strings.RSyncDir.RestoreFileError, s, ex.Message), ex); Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.RestoreFileError, s, ex.Message), XervBackup.Library.Logging.LogMessageType.Error, ex); try { SystemIO.FileDelete(target); } catch { } } fileindex++; } //Re-create symlinks (no progress report here, should be really fast) foreach (string s in symlinks) { string target = GetFullPathFromRelname(destination, s.Substring(symlinkprefix.Length)); string symlinktarget = ""; try { symlinktarget = FilenamesFromPlatformIndependant(new string[] { Encoding.UTF8.GetString(patch.ReadAllBytes(s)) })[0]; bool isDir = symlinktarget[symlinktarget.Length - 1] == Path.DirectorySeparatorChar; if (isDir) symlinktarget = symlinktarget.Substring(0, symlinktarget.Length - 1); try { //In case another symlink is present, we "update" it if (SystemIO.FileExists(target) && (SystemIO.GetFileAttributes(target) & FileAttributes.ReparsePoint) != 0) SystemIO.FileDelete(target); } catch (Exception ex) { Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.RestoreFileError, s, ex.Message), XervBackup.Library.Logging.LogMessageType.Error, ex); } SystemIO.CreateSymlink(target, symlinktarget, isDir); } catch (Exception ex) { if (m_stat != null) m_stat.LogError(string.Format(Strings.RSyncDir.RestoreFileError, s, ex.Message), ex); Logging.Log.WriteMessage(string.Format(Strings.RSyncDir.RestoreFileError, s, ex.Message), XervBackup.Library.Logging.LogMessageType.Error, ex); try { SystemIO.FileDelete(target); } catch { } try { if (!string.IsNullOrEmpty(symlinktarget)) using (System.IO.StreamWriter sw = new System.IO.StreamWriter(SystemIO.FileOpenWrite(target))) sw.Write(symlinktarget); } catch { } } } }