/// <summary> /// Restores control files added to a backup. /// </summary> /// <param name="target">The folder into which to restore the files</param> /// <returns>A restore report</returns> public string RestoreControlFiles(string target) { RestoreStatistics rs = new RestoreStatistics(DuplicatiOperationMode.RestoreControlfiles); SetupCommonOptions(rs); BackendWrapper backend = null; using (new Logging.Timer("Restore control files from " + m_backend + " to " + target)) { try { if (OperationStarted != null) OperationStarted(this, DuplicatiOperation.Restore, rs.OperationMode, 0, -1, Strings.Interface.StatusStarted, ""); backend = new BackendWrapper(rs, m_backend, m_options); List<ManifestEntry> attempts = backend.GetBackupSets(); List<ManifestEntry> flatlist = new List<ManifestEntry>(); foreach (ManifestEntry be in attempts) { flatlist.Add(be); flatlist.AddRange(be.Incrementals); } flatlist.Reverse(); string prefix = Utility.Utility.AppendDirSeparator(RSync.RSyncDir.CONTROL_ROOT); foreach (ManifestEntry be in flatlist) { if (be.Volumes.Count > 0) using(Utility.TempFile z = new Duplicati.Library.Utility.TempFile()) { OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, 0, -1, string.Format(Strings.Interface.StatusReadingIncrementalFile, be.Volumes[0].Key.Filename), ""); Manifestfile mf = GetManifest(backend, be); OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, 0, -1, string.Format(Strings.Interface.StatusReadingIncrementalFile, be.Volumes[0].Key.Filename), ""); using (new Logging.Timer("Get " + be.Volumes[0].Key.Filename)) backend.Get(be.Volumes[0].Key, mf, z, mf.SignatureHashes == null ? null : mf.SignatureHashes[0]); using(Library.Interface.ICompression fz = DynamicLoader.CompressionLoader.GetModule(be.Volumes[0].Key.Compression, z, m_options.RawOptions)) { bool any = false; foreach (string f in fz.ListFiles(prefix)) { any = true; using (System.IO.Stream s1 = fz.OpenRead(f)) using (System.IO.Stream s2 = System.IO.File.Create(System.IO.Path.Combine(target, f.Substring(prefix.Length)))) Utility.Utility.CopyStream(s1, s2); } if (any) break; rs.LogError(string.Format(Strings.Interface.FailedToFindControlFilesMessage, be.Volumes[0].Key.Filename), null); } } } } finally { if (backend != null) backend.Dispose(); if (OperationCompleted != null) OperationCompleted(this, DuplicatiOperation.Restore, rs.OperationMode, 100, -1, Strings.Interface.StatusCompleted, ""); } } rs.EndTime = DateTime.Now; return rs.ToString(); }
public string Restore(string[] target) { RestoreStatistics rs = new RestoreStatistics(DuplicatiOperationMode.Restore); SetupCommonOptions(rs); m_progress = 0; BackendWrapper backend = null; m_restorePatches = 0; using (new Logging.Timer("Restore from " + m_backend + " to " + string.Join(System.IO.Path.PathSeparator.ToString(), target))) { try { if (OperationStarted != null) OperationStarted(this, DuplicatiOperation.Restore, rs.OperationMode, -1, -1, Strings.Interface.StatusStarted, ""); OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, -1, -1, Strings.Interface.StatusStarted, ""); Utility.FilenameFilter filter = m_options.Filter; //Filter is prefered, if both file and filter is specified if (!m_options.HasFilter && !string.IsNullOrEmpty(m_options.FileToRestore)) { List<Utility.IFilenameFilter> list = new List<Duplicati.Library.Utility.IFilenameFilter>(); list.Add(new Utility.FilelistFilter(true, m_options.FileToRestore.Split(System.IO.Path.PathSeparator))); list.Add(new Utility.RegularExpressionFilter(false, ".*")); filter = new Duplicati.Library.Utility.FilenameFilter(list); } backend = new BackendWrapper(rs, m_backend, m_options); backend.ProgressEvent += new Duplicati.Library.Main.RSync.RSyncDir.ProgressEventDelegate(BackupTransfer_ProgressEvent); OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, (int)(m_progress * 100), -1, Strings.Interface.StatusReadingIncrementals, ""); ManifestEntry bestFit = backend.GetBackupSet(m_options.RestoreTime); //We will need all the manifests downloaded anyway if (!m_options.DontReadManifests) { if (bestFit.Incrementals.Count > 0) VerifyManifestChain(backend, bestFit.Incrementals[bestFit.Incrementals.Count - 1]); else VerifyManifestChain(backend, bestFit); OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, (int)(m_progress * 100), -1, Strings.Interface.StatusReadingIncrementals, ""); } m_progress = INCREMENAL_COST; List<ManifestEntry> entries = new List<ManifestEntry>(); entries.Add(bestFit); entries.AddRange(bestFit.Incrementals); int patchno = 0; foreach (ManifestEntry be in entries) m_restorePatches += be.Volumes.Count; Manifestfile rootManifest = GetManifest(backend, bestFit); int sourceDirCount = (rootManifest.SourceDirs == null || rootManifest.SourceDirs.Length == 0) ? 1 : rootManifest.SourceDirs.Length; //After reading the first manifest, we know the source folder count if ((rootManifest.SourceDirs == null || rootManifest.SourceDirs.Length == 0) && target.Length > 1) { //V1 support rs.LogWarning(string.Format(Strings.Interface.TooManyTargetFoldersWarning, 1, target.Length), null); Array.Resize(ref target, 1); } else if (target.Length > sourceDirCount) { //If we get too many, we can just cut them off rs.LogWarning(string.Format(Strings.Interface.TooManyTargetFoldersWarning, sourceDirCount, target.Length), null); Array.Resize(ref target, rootManifest.SourceDirs.Length); } else if (target.Length != 1 && target.Length < sourceDirCount) { //If we get too few, we have to bail throw new Exception(string.Format(Strings.Interface.TooFewTargetFoldersError, sourceDirCount, target.Length)); } else if (target.Length == 1 && sourceDirCount > 1) { //If there is just one target folder, we automatically compose target subfolders string[] newtargets = new string[rootManifest.SourceDirs.Length]; List<string> suggestions = new List<string>(); for (int i = 0; i < rootManifest.SourceDirs.Length; i++) { string s = rootManifest.SourceDirs[i]; //HACK: We use a leading / in the path name to detect source OS // all paths are absolute, so this detects all unix like systems string dirSepChar = s.StartsWith("/") ? "/" : "\\"; if (s.EndsWith(dirSepChar)) s = s.Substring(0, s.Length - 1); int lix = s.LastIndexOf(dirSepChar); if (lix < 0 || lix + 1 >= s.Length) s = i.ToString(); else s = s.Substring(lix + 1); foreach (char c in System.IO.Path.GetInvalidFileNameChars()) s = s.Replace(c, '_'); suggestions.Add(s); } Dictionary<string, int> duplicates = new Dictionary<string, int>(Library.Utility.Utility.ClientFilenameStringComparer); for (int i = 0; i < suggestions.Count; i++) if (duplicates.ContainsKey(suggestions[i])) duplicates[suggestions[i]]++; else duplicates[suggestions[i]] = 1; for (int i = 0; i < newtargets.Length; i++) { string suffix = duplicates[suggestions[i]] > 1 ? i.ToString() : suggestions[i]; newtargets[i] = System.IO.Path.Combine(target[0], suffix); } target = newtargets; } //Make sure all targets exist foreach(string s in target) if (!System.IO.Directory.Exists(s)) System.IO.Directory.CreateDirectory(s); using (RSync.RSyncDir sync = new Duplicati.Library.Main.RSync.RSyncDir(target, rs, filter)) { sync.ProgressEvent += new Duplicati.Library.Main.RSync.RSyncDir.ProgressEventDelegate(RestoreRSyncDir_ProgressEvent); foreach (ManifestEntry be in entries) { m_progress = ((1.0 - INCREMENAL_COST) * (patchno / (double)m_restorePatches)) + INCREMENAL_COST; CheckLiveControl(); Manifestfile manifest = be == bestFit ? rootManifest : GetManifest(backend, be); CheckLiveControl(); foreach (KeyValuePair<SignatureEntry, ContentEntry> vol in be.Volumes) { ContentEntry contentVol = vol.Value; SignatureEntry signatureVol = vol.Key; m_progress = ((1.0 - INCREMENAL_COST) * (patchno / (double)m_restorePatches)) + INCREMENAL_COST; //Skip nonlisted if (manifest.ContentHashes != null && contentVol.Volumenumber > manifest.ContentHashes.Count) { Logging.Log.WriteMessage(string.Format(Strings.Interface.SkippedContentVolumeLogMessage, contentVol.Volumenumber), Duplicati.Library.Logging.LogMessageType.Warning); rs.LogWarning(string.Format(Strings.Interface.SkippedContentVolumeLogMessage, contentVol.Volumenumber), null); patchno++; continue; } using (Utility.TempFile patchzip = new Duplicati.Library.Utility.TempFile()) { OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, (int)(m_progress * 100), -1, string.Format(Strings.Interface.StatusPatching, patchno + 1), ""); CheckLiveControl(); if (m_options.HasFilter || !string.IsNullOrEmpty(m_options.FileToRestore)) { bool hasFiles = false; using (Utility.TempFile sigFile = new Duplicati.Library.Utility.TempFile()) { OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, (int)(m_progress * 100), -1, string.Format(Strings.Interface.StatusDownloadingSignatureVolume, patchno + 1), ""); try { using (new Logging.Timer("Get " + signatureVol.Filename)) backend.Get(signatureVol, manifest, sigFile, manifest.SignatureHashes == null ? null : manifest.SignatureHashes[signatureVol.Volumenumber - 1]); } catch (BackendWrapper.HashMismathcException hme) { hasFiles = true; rs.LogError(string.Format(Strings.Interface.FileHashFailure, hme.Message), hme); } if (!hasFiles) using (Library.Interface.ICompression patch = DynamicLoader.CompressionLoader.GetModule(signatureVol.Compression, sigFile, m_options.RawOptions)) { foreach(KeyValuePair<RSync.RSyncDir.PatchFileType, string> k in sync.ListPatchFiles(patch)) if (filter.ShouldInclude("", System.IO.Path.DirectorySeparatorChar.ToString() + k.Value)) { //TODO: Perhaps a bit much to download the content archive // if the file is only marked for deletion? hasFiles = true; break; } } } if (!hasFiles) { //Avoid downloading the content file patchno++; continue; } } OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, (int)(m_progress * 100), -1, string.Format(Strings.Interface.StatusDownloadingContentVolume, patchno + 1), ""); using (new Logging.Timer("Get " + contentVol.Filename)) backend.Get(contentVol, manifest, patchzip, manifest.ContentHashes == null ? null : manifest.ContentHashes[contentVol.Volumenumber - 1]); OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, (int)(m_progress * 100), -1, string.Format(Strings.Interface.StatusPatching, patchno + 1), ""); using (new Logging.Timer((patchno == 0 ? "Full restore to: " : "Incremental restore " + patchno.ToString() + " to: ") + string.Join(System.IO.Path.PathSeparator.ToString(), target))) using (Library.Interface.ICompression patch = DynamicLoader.CompressionLoader.GetModule(contentVol.Compression, patchzip, m_options.RawOptions)) sync.Patch(target, patch); } patchno++; } //Make sure there are no partial files, as partial files are not allowed to span backup sets sync.FinalizeRestore(); } } } finally { if (backend != null) backend.Dispose(); if (OperationCompleted != null) OperationCompleted(this, DuplicatiOperation.Restore, rs.OperationMode, 100, -1, Strings.Interface.StatusCompleted, ""); OperationProgress(this, DuplicatiOperation.Restore, rs.OperationMode, 100, -1, Strings.Interface.StatusCompleted, ""); } } rs.EndTime = DateTime.Now; return rs.ToString(); }