1 /** 2 Copyright: Copyright (c) 2019, Joakim Brännström. All rights reserved. 3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0) 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 */ 6 module dsnapshot.cmdgroup.restore; 7 8 import logger = std.experimental.logger; 9 import std.array : empty; 10 import std.exception : collectException; 11 12 import dsnapshot.config : Config; 13 import dsnapshot.exception; 14 import dsnapshot.layout_utils; 15 import dsnapshot.types; 16 17 int cmdRestore(Snapshot[] snapshots, const Config.Restore conf) nothrow { 18 import std.algorithm : map, filter; 19 import dsnapshot.layout; 20 import dsnapshot.layout_utils; 21 22 if (conf.name.value.empty) { 23 logger.error("No snapshot name specified (-s|--snapshot)").collectException; 24 return 1; 25 } 26 27 if (conf.restoreTo.empty) { 28 logger.error("No destination is specified (--dst)").collectException; 29 return 1; 30 } 31 32 foreach (snapshot; snapshots.filter!(a => a.name == conf.name.value)) { 33 try { 34 return snapshot.syncCmd.match!((None a) { 35 logger.infof("Unable to restore %s (missing command configuration)", snapshot.name); 36 return 1; 37 }, (RsyncConfig a) => restore(a, snapshot, conf)); 38 } catch (SnapshotException e) { 39 e.errMsg.match!(a => a.print).collectException; 40 logger.error(e.msg).collectException; 41 } catch (Exception e) { 42 logger.error(e.msg).collectException; 43 break; 44 } 45 } 46 47 logger.errorf("No snapshot with the name %s found", conf.name.value).collectException; 48 logger.infof("Available snapshots are: %-(%s, %)", 49 snapshots.map!(a => a.name)).collectException; 50 return 1; 51 } 52 53 int restore(const RsyncConfig conf, Snapshot snapshot, const Config.Restore rconf) { 54 import std.file : exists, mkdirRecurse; 55 import std.path : buildPath; 56 import std.process : spawnProcess, wait; 57 import dsnapshot.console : isInteractiveShell; 58 59 // Extract an updated layout of the snapshots at the destination. 60 auto layout = snapshot.syncCmd.match!((None a) => snapshot.layout, 61 (RsyncConfig a) => fillLayout(snapshot.layout, a.flow, snapshot.remoteCmd)); 62 63 auto flow = snapshot.syncCmd.match!((None a) => None.init.Flow, (RsyncConfig a) => a.flow); 64 65 const bestFitSnapshot = layout.bestFitBucket(rconf.time); 66 if (bestFitSnapshot.isNull) { 67 logger.error("Unable to find a snapshot to restore for the time ", rconf.time); 68 return 1; 69 } 70 71 string src; 72 73 // TODO: this code is similare to the one in cmdgroup.backup. Consider how 74 // it can be deduplicated. Note, similare not the same. 75 string[] buildOpts() { 76 string[] opts = [conf.cmdRsync]; 77 opts ~= conf.args.dup; 78 79 if (!conf.rsh.empty) 80 opts ~= ["-e", conf.rsh]; 81 82 if (isInteractiveShell) 83 opts ~= conf.progress; 84 85 if (rconf.deleteFromTo) { 86 // --delete delete files from dest if they are removed in src 87 opts ~= ["--delete"]; 88 } 89 90 foreach (a; conf.exclude) 91 opts ~= ["--exclude", a]; 92 93 flow.match!((None a) {}, (FlowLocal a) { 94 src = fixRsyncAddr((a.dst.value.Path ~ bestFitSnapshot.name.value).toString); 95 }, (FlowRsyncToLocal a) { 96 src = fixRsyncAddr((a.dst.value.Path ~ bestFitSnapshot.name.value).toString); 97 }, (FlowLocalToRsync a) { 98 src = makeRsyncAddr(a.dst.addr, fixRsyncAddr(buildPath(a.dst.path, 99 bestFitSnapshot.name.value))); 100 }); 101 102 opts ~= src; 103 // dst is always on the local machine as specified by the user 104 opts ~= rconf.restoreTo; 105 return opts; 106 } 107 108 const opts = buildOpts(); 109 110 if (!exists(rconf.restoreTo)) 111 mkdirRecurse(rconf.restoreTo); 112 113 logger.infof("Restoring %s to %s", bestFitSnapshot.name.value, rconf.restoreTo); 114 115 logger.infof("%-(%s %)", opts); 116 if (spawnProcess(opts).wait != 0) 117 throw new SnapshotException(SnapshotException.SyncFailed(src, 118 rconf.restoreTo).SnapshotError); 119 120 return 0; 121 }