Skip to content

Commit 0f0aa42

Browse files
authored
refactor: Eliminate LastSnapshotSymlink exception
Related to #2130
1 parent 3f9cb45 commit 0f0aa42

File tree

5 files changed

+56
-40
lines changed

5 files changed

+56
-40
lines changed

common/bitbase.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
# message is displayed.
6060
ENCFS_MSG_STAGE = 2
6161

62+
# Names used for backup directories (or symlinks to them) indicating a specific
63+
# state.
64+
DIR_NAME_LAST_SNAPSHOT = 'last_snapshot'
65+
DIR_NAME_NEWSNAPSHOT = 'new_snapshot'
66+
DIR_NAME_SAVETOCONTINUE = 'save_to_continue'
67+
6268

6369
class TimeUnit(Enum):
6470
"""Describe time units used in context of scheduling.

common/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1355,7 +1355,7 @@ def restoreInstanceFile(self, profile_id=None):
13551355
"restore%s.lock" % self.fileId(profile_id))
13561356

13571357
def lastSnapshotSymlink(self, profile_id = None):
1358-
return os.path.join(self.snapshotsFullPath(profile_id), 'last_snapshot')
1358+
return os.path.join(self.snapshotsFullPath(profile_id), bitbase.DIR_NAME_LAST_SNAPSHOT)
13591359

13601360
def encfsconfigBackupFolder(self, profile_id = None):
13611361
return os.path.join(self._LOCAL_DATA_FOLDER, 'encfsconfig_backup_%s' % self.fileId(profile_id))

common/exceptions.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ class Timeout(BackInTimeException):
3838
pass
3939

4040

41-
class LastSnapshotSymlink(BackInTimeException):
42-
pass
43-
44-
4541
class InvalidChar(BackInTimeException):
4642
def __init__(self, msg):
4743
self.msg = msg

common/snapshots.py

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,13 @@
3636
import progress
3737
import snapshotlog
3838
import flock
39+
import bitbase
3940
from inhibitsuspend import InhibitSuspend
4041
from applicationinstance import ApplicationInstance
41-
from exceptions import MountException, LastSnapshotSymlink
42+
from exceptions import MountException
4243
from uniquenessset import UniquenessSet
4344

45+
4446
class Snapshots:
4547
"""
4648
Collection of take-snapshot and restore commands.
@@ -2288,32 +2290,39 @@ def errorHandler(fn, path, excinfo):
22882290
os.remove(full_path)
22892291
os.chmod(dirname, dir_st.st_mode)
22902292

2291-
def createLastSnapshotSymlink(self, sid):
2292-
"""
2293-
Create symlink 'last_snapshot' to snapshot ``sid``
2293+
def createLastSnapshotSymlink(self, sid: SID) -> bool:
2294+
"""Create symlink 'last_snapshot' to snapshot ``sid``.
22942295
22952296
Args:
2296-
sid (SID): snapshot that should be linked.
2297+
sid: Snapshot that should be linked.
22972298
22982299
Returns:
2299-
bool: ``True`` if successful
2300+
bool: ``True`` if successful.
23002301
"""
23012302
if sid is None:
23022303
return
2304+
23032305
symlink = self.config.lastSnapshotSymlink()
2306+
23042307
try:
23052308
if os.path.islink(symlink):
23062309
if os.path.basename(os.path.realpath(symlink)) == sid.sid:
23072310
return True
2311+
23082312
os.remove(symlink)
2313+
23092314
if os.path.exists(symlink):
2310-
logger.error('Could not remove symlink %s' %symlink, self)
2315+
logger.error(f'Could not remove symlink {symlink}', self)
23112316
return False
2312-
logger.debug('Create symlink %s => %s' %(symlink, sid), self)
2317+
2318+
logger.debug(f'Create symlink {symlink} => {sid}', self)
23132319
os.symlink(sid.sid, symlink)
2320+
23142321
return True
2315-
except Exception as e:
2316-
logger.error('Failed to create symlink %s: %s' %(symlink, str(e)), self)
2322+
2323+
except Exception as exc:
2324+
logger.error(f'Failed to create symlink {symlink}: {exc}', self)
2325+
23172326
return False
23182327

23192328
def rsyncSuffix(self, includeFolders=None, excludeFolders=None):
@@ -2537,7 +2546,9 @@ def __init__(self, date, cfg):
25372546
self.date = datetime.datetime(*self.split())
25382547

25392548
elif date == 'last_snapshot':
2540-
raise LastSnapshotSymlink()
2549+
# Undefined state
2550+
raise RuntimeError(
2551+
'This class can not handle last-snapshot symlinks.')
25412552

25422553
else:
25432554
raise ValueError("'date' must be in snapshot ID format "
@@ -3030,15 +3041,14 @@ def withoutTag(self):
30303041

30313042

30323043
class NewSnapshot(GenericNonSnapshot):
3033-
"""
3034-
Snapshot ID object for 'new_snapshot' folder
3044+
"""Snapshot ID object for 'new_snapshot' folder.
30353045
30363046
Args:
3037-
cfg (config.Config): current config
3047+
cfg (config.Config): Current config
30383048
"""
30393049

3040-
NEWSNAPSHOT = 'new_snapshot'
3041-
SAVETOCONTINUE = 'save_to_continue'
3050+
NEWSNAPSHOT = bitbase.DIR_NAME_NEWSNAPSHOT
3051+
SAVETOCONTINUE = bitbase.DIR_NAME_SAVETOCONTINUE
30423052

30433053
def __init__(self, cfg):
30443054
self.config = cfg
@@ -3178,45 +3188,48 @@ def path(self, *path, use_mode = []):
31783188
return os.path.join(os.sep, *path)
31793189

31803190

3181-
def iterSnapshots(cfg, includeNewSnapshot=False):
3191+
def iterSnapshots(cfg: config.Config, includeNewSnapshot: bool = False):
31823192
"""A generator to iterate over snapshots in current snapshot path.
31833193
31843194
Args:
3185-
cfg (config.Config): Current config instance.
3186-
includeNewSnapshot (bool): Include a NewSnapshot instance if
3195+
cfg: Current config instance.
3196+
includeNewSnapshot: Include a NewSnapshot instance if
31873197
'new_snapshot' directory is available (default: False).
31883198
31893199
Yields:
31903200
SID: Snapshot IDs
31913201
"""
3192-
path = cfg.snapshotsFullPath()
3202+
path = Path(cfg.snapshotsFullPath())
31933203

3194-
if not os.path.exists(path):
3204+
if not path.exists():
31953205
return None
31963206

3197-
for item in os.listdir(path):
3207+
for item in path.iterdir():
31983208

3199-
if item == NewSnapshot.NEWSNAPSHOT:
3200-
newSid = NewSnapshot(cfg)
3209+
if item.name == bitbase.DIR_NAME_NEWSNAPSHOT:
3210+
sid = NewSnapshot(cfg)
32013211

3202-
if newSid.exists() and includeNewSnapshot:
3203-
yield newSid
3212+
if includeNewSnapshot and sid.exists():
3213+
yield sid
32043214

32053215
continue
32063216

3217+
elif item.name == bitbase.DIR_NAME_LAST_SNAPSHOT:
3218+
# Ignore last snapshot symlink
3219+
continue
3220+
32073221
try:
3208-
sid = SID(item, cfg)
3222+
sid = SID(item.name, cfg)
32093223

32103224
if sid.exists():
32113225
yield sid
32123226

3213-
# REFACTOR!
3214-
# LastSnapshotSymlink is an exception instance and could be caught
3215-
# explicit. But not sure about its purpose.
3216-
except Exception as e:
3217-
if not isinstance(e, LastSnapshotSymlink):
3218-
logger.debug(
3219-
"'{}' is not a snapshot ID: {}".format(item, str(e)))
3227+
# Dev note (buhtz, 2025-05): I am not a friend of catching exceptions
3228+
# at this point. But previously all Exceptions where caught at this
3229+
# point. Now catching ValueError's only, is a compromise.
3230+
except ValueError as exc:
3231+
# Raised by SID.__init__() in case of invalid date format
3232+
logger.warning(f'"{item.name}" is not a snapshot ID. {exc=}')
32203233

32213234

32223235
def listSnapshots(cfg, includeNewSnapshot=False, reverse=True):

common/test/test_sid.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,10 +486,11 @@ def test_list_snapshot_without_backup(self):
486486
'20151219-010324-123'])
487487

488488
def test_list_invalid_snapshot(self):
489-
#invalid snapshot shouldn't be added
489+
# invalid snapshot shouldn't be added
490490
os.makedirs(os.path.join(self.snapshotPath,
491491
'20151219-000324-abc',
492492
'backup'))
493+
493494
l4 = snapshots.listSnapshots(self.cfg)
494495
self.assertListEqual(l4, ['20151219-040324-123',
495496
'20151219-030324-123',

0 commit comments

Comments
 (0)