|
36 | 36 | import progress |
37 | 37 | import snapshotlog |
38 | 38 | import flock |
| 39 | +import bitbase |
39 | 40 | from inhibitsuspend import InhibitSuspend |
40 | 41 | from applicationinstance import ApplicationInstance |
41 | | -from exceptions import MountException, LastSnapshotSymlink |
| 42 | +from exceptions import MountException |
42 | 43 | from uniquenessset import UniquenessSet |
43 | 44 |
|
| 45 | + |
44 | 46 | class Snapshots: |
45 | 47 | """ |
46 | 48 | Collection of take-snapshot and restore commands. |
@@ -2288,32 +2290,39 @@ def errorHandler(fn, path, excinfo): |
2288 | 2290 | os.remove(full_path) |
2289 | 2291 | os.chmod(dirname, dir_st.st_mode) |
2290 | 2292 |
|
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``. |
2294 | 2295 |
|
2295 | 2296 | Args: |
2296 | | - sid (SID): snapshot that should be linked. |
| 2297 | + sid: Snapshot that should be linked. |
2297 | 2298 |
|
2298 | 2299 | Returns: |
2299 | | - bool: ``True`` if successful |
| 2300 | + bool: ``True`` if successful. |
2300 | 2301 | """ |
2301 | 2302 | if sid is None: |
2302 | 2303 | return |
| 2304 | + |
2303 | 2305 | symlink = self.config.lastSnapshotSymlink() |
| 2306 | + |
2304 | 2307 | try: |
2305 | 2308 | if os.path.islink(symlink): |
2306 | 2309 | if os.path.basename(os.path.realpath(symlink)) == sid.sid: |
2307 | 2310 | return True |
| 2311 | + |
2308 | 2312 | os.remove(symlink) |
| 2313 | + |
2309 | 2314 | 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) |
2311 | 2316 | return False |
2312 | | - logger.debug('Create symlink %s => %s' %(symlink, sid), self) |
| 2317 | + |
| 2318 | + logger.debug(f'Create symlink {symlink} => {sid}', self) |
2313 | 2319 | os.symlink(sid.sid, symlink) |
| 2320 | + |
2314 | 2321 | 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 | + |
2317 | 2326 | return False |
2318 | 2327 |
|
2319 | 2328 | def rsyncSuffix(self, includeFolders=None, excludeFolders=None): |
@@ -2537,7 +2546,9 @@ def __init__(self, date, cfg): |
2537 | 2546 | self.date = datetime.datetime(*self.split()) |
2538 | 2547 |
|
2539 | 2548 | elif date == 'last_snapshot': |
2540 | | - raise LastSnapshotSymlink() |
| 2549 | + # Undefined state |
| 2550 | + raise RuntimeError( |
| 2551 | + 'This class can not handle last-snapshot symlinks.') |
2541 | 2552 |
|
2542 | 2553 | else: |
2543 | 2554 | raise ValueError("'date' must be in snapshot ID format " |
@@ -3030,15 +3041,14 @@ def withoutTag(self): |
3030 | 3041 |
|
3031 | 3042 |
|
3032 | 3043 | class NewSnapshot(GenericNonSnapshot): |
3033 | | - """ |
3034 | | - Snapshot ID object for 'new_snapshot' folder |
| 3044 | + """Snapshot ID object for 'new_snapshot' folder. |
3035 | 3045 |
|
3036 | 3046 | Args: |
3037 | | - cfg (config.Config): current config |
| 3047 | + cfg (config.Config): Current config |
3038 | 3048 | """ |
3039 | 3049 |
|
3040 | | - NEWSNAPSHOT = 'new_snapshot' |
3041 | | - SAVETOCONTINUE = 'save_to_continue' |
| 3050 | + NEWSNAPSHOT = bitbase.DIR_NAME_NEWSNAPSHOT |
| 3051 | + SAVETOCONTINUE = bitbase.DIR_NAME_SAVETOCONTINUE |
3042 | 3052 |
|
3043 | 3053 | def __init__(self, cfg): |
3044 | 3054 | self.config = cfg |
@@ -3178,45 +3188,48 @@ def path(self, *path, use_mode = []): |
3178 | 3188 | return os.path.join(os.sep, *path) |
3179 | 3189 |
|
3180 | 3190 |
|
3181 | | -def iterSnapshots(cfg, includeNewSnapshot=False): |
| 3191 | +def iterSnapshots(cfg: config.Config, includeNewSnapshot: bool = False): |
3182 | 3192 | """A generator to iterate over snapshots in current snapshot path. |
3183 | 3193 |
|
3184 | 3194 | 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 |
3187 | 3197 | 'new_snapshot' directory is available (default: False). |
3188 | 3198 |
|
3189 | 3199 | Yields: |
3190 | 3200 | SID: Snapshot IDs |
3191 | 3201 | """ |
3192 | | - path = cfg.snapshotsFullPath() |
| 3202 | + path = Path(cfg.snapshotsFullPath()) |
3193 | 3203 |
|
3194 | | - if not os.path.exists(path): |
| 3204 | + if not path.exists(): |
3195 | 3205 | return None |
3196 | 3206 |
|
3197 | | - for item in os.listdir(path): |
| 3207 | + for item in path.iterdir(): |
3198 | 3208 |
|
3199 | | - if item == NewSnapshot.NEWSNAPSHOT: |
3200 | | - newSid = NewSnapshot(cfg) |
| 3209 | + if item.name == bitbase.DIR_NAME_NEWSNAPSHOT: |
| 3210 | + sid = NewSnapshot(cfg) |
3201 | 3211 |
|
3202 | | - if newSid.exists() and includeNewSnapshot: |
3203 | | - yield newSid |
| 3212 | + if includeNewSnapshot and sid.exists(): |
| 3213 | + yield sid |
3204 | 3214 |
|
3205 | 3215 | continue |
3206 | 3216 |
|
| 3217 | + elif item.name == bitbase.DIR_NAME_LAST_SNAPSHOT: |
| 3218 | + # Ignore last snapshot symlink |
| 3219 | + continue |
| 3220 | + |
3207 | 3221 | try: |
3208 | | - sid = SID(item, cfg) |
| 3222 | + sid = SID(item.name, cfg) |
3209 | 3223 |
|
3210 | 3224 | if sid.exists(): |
3211 | 3225 | yield sid |
3212 | 3226 |
|
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=}') |
3220 | 3233 |
|
3221 | 3234 |
|
3222 | 3235 | def listSnapshots(cfg, includeNewSnapshot=False, reverse=True): |
|
0 commit comments