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 }