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 }