diff --git a/CHANGES b/CHANGES index 95423b5f7..382556ddf 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,7 @@ Version 1.6.0-dev (development of upcoming release) * Breaking Change: Auto-remove rules "Free inodes" and "Free space" disabled by default (#1976) * Fix!: Smart-remove rule "Keep one snapshots per week or the last week" use calendar weeks * Feature: Toolbar context menu to display the buttons in different combinations with icons and text (#1105, #2002) (Samuel Moore @s4moore) +* Fix bug: Check if Include folders/files do exists (in case they are removed) (#1586) (@rafaelhdr) * Feature: Add offset minutes to hourly schedules (David Gibbs @fallingrock) Version 1.5.3 (2024-11-13) diff --git a/common/snapshots.py b/common/snapshots.py index 98874182d..8586dd1f1 100644 --- a/common/snapshots.py +++ b/common/snapshots.py @@ -695,6 +695,23 @@ def remove(self, sid): return True + def _check_included_sources_exist_on_take_snapshot(self, config): + """ + Check if files and/or directories in the include list exist on the source. + + If a file or directory does not exist, a warning message is logged. + + Args: + cfg (config.Config): Config that should be used. + """ + missing = has_missing_includes(config.include()) + + if missing: + msg = ', '.join(missing) + msg = f'The following files/folders are missing: {msg}' + logger.warning(msg) + self.setTakeSnapshotMessage(1, msg) + # TODO Refactor: This functions is extremely difficult to understand: # - Nested "if"s # - Fuzzy names of classes, attributes and methods @@ -804,6 +821,7 @@ def backup(self, force=False): else: self.config.setCurrentHashId(hash_id) + self._check_included_sources_exist_on_take_snapshot(self.config) include_folders = self.config.include() if not include_folders: @@ -3178,6 +3196,25 @@ def lastSnapshot(cfg): return sids[0] +def has_missing_includes(included): + """ + Check if there are missing files or folders in a snapshot. + + Args: + included (list): list of tuples (item, info) + + Returns: + tuple: (bool, str) where bool is ``True`` if there are + missing files or folders and str is a message + describing the missing files or folders + """ + not_found = [] + for path, _ in included: + if not os.path.exists(path): + not_found.append(path) + return not_found + + if __name__ == '__main__': config = config.Config() snapshots = Snapshots(config) diff --git a/qt/app.py b/qt/app.py index d1d8cb8e3..fdac09647 100644 --- a/qt/app.py +++ b/qt/app.py @@ -1300,12 +1300,27 @@ def updateTimeLine(self, refreshSnapshotsList=True): item = self.timeLine.addSnapshot(sid) self.timeLine.checkSelection() + def validate_on_take_snapshot(self): + missing = snapshots.has_missing_includes(self.config.include()) + if missing: + msg_missing = '\n'.join(missing) + msg = _('The following directories are missing: {dirs} Do you want to proceed?').format( + dirs=f'\n{msg_missing}\n\n') + answer = messagebox.warningYesNo(self, msg) + return answer == QMessageBox.StandardButton.Yes + return True + def btnTakeSnapshotClicked(self): - backintime.takeSnapshotAsync(self.config) - self.updateTakeSnapshot(True) + self._take_snapshot_clicked(checksum=False) def btnTakeSnapshotChecksumClicked(self): - backintime.takeSnapshotAsync(self.config, checksum = True) + self._take_snapshot_clicked(checksum=True) + + def _take_snapshot_clicked(self, checksum): + if not self.validate_on_take_snapshot(): + return + + backintime.takeSnapshotAsync(self.config, checksum=checksum) self.updateTakeSnapshot(True) def btnStopTakeSnapshotClicked(self):