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.backup;
7 
8 import logger = std.experimental.logger;
9 import std.array : empty;
10 import std.exception : collectException;
11 import std.algorithm : filter, map;
12 
13 import sumtype;
14 
15 import dsnapshot.config : Config;
16 import dsnapshot.types;
17 
18 import dsnapshot.exception;
19 import dsnapshot.layout : Name, Layout;
20 import dsnapshot.layout_utils;
21 import dsnapshot.console;
22 import dsnapshot.backend;
23 
24 version (unittest) {
25     import unit_threaded.assertions;
26 }
27 
28 int cmdBackup(Config.Global global, Config.Backup backup, SnapshotConfig[] snapshots) {
29     int exitStatus;
30 
31     foreach (s; snapshots.filter!(a => backup.name.value.empty || backup.name.value == a.name)) {
32         int snapshotStatus = 1;
33         logger.info("# Start snapshot ", s.name);
34         scope (exit)
35             logger.info("# Done snapshot ", s.name);
36 
37         try {
38             snapshot(s, backup);
39             snapshotStatus = 0;
40         } catch (SnapshotException e) {
41             e.errMsg.match!(a => a.print);
42             logger.error(e.msg);
43         } catch (Exception e) {
44             logger.error(e.msg);
45         }
46 
47         exitStatus = (snapshotStatus + exitStatus) == 0 ? 0 : 1;
48     }
49 
50     return exitStatus;
51 }
52 
53 private:
54 
55 void snapshot(SnapshotConfig snapshot, const Config.Backup conf) {
56     import std.datetime : Clock;
57 
58     auto backend = makeSyncBackend(snapshot, conf);
59 
60     auto crypt = makeCrypBackend(snapshot.crypt);
61     open(crypt, backend.flow);
62     scope (exit)
63         crypt.close;
64 
65     auto layout = backend.update(snapshot.layout);
66     logger.trace("Updated layout with information from destination: ", layout);
67 
68     // this ensure that dsnapshot is only executed when there are actual work
69     // to do. If multiple snapshots are taken close to each other in time then
70     // it means that the "last" one of them is actually the only one that is
71     // kept because it is closest to the bucket.
72     if (!conf.forceBackup && !layout.isFirstBucketEmpty) {
73         const first = layout.snapshotTimeInBucket(0);
74         const timeLeft = first.get - layout.times[0].begin;
75         if (timeLeft > conf.newSnapshotMargin) {
76             logger.infof("No new snapshot because one where recently created");
77 
78             if (!first.isNull) {
79                 logger.infof("Latest snapshot created at %s. Next snapshot will be created in %s",
80                         first.get, timeLeft);
81             }
82             return;
83         }
84     }
85 
86     const newSnapshot = () {
87         if (conf.resume && !layout.resume.isNull) {
88             return layout.resume.get.name.value;
89         }
90         return Clock.currTime.toUTC.toISOExtString ~ snapshotInProgressSuffix;
91     }();
92 
93     backend.sync(layout, snapshot, newSnapshot);
94 
95     backend.publishSnapshot(newSnapshot);
96     backend.removeDiscarded(layout);
97 }