cura-te-ipsum/spqr/curateipsum/backup.py

114 lines
3.9 KiB
Python
Raw Normal View History

"""
Module with backup functions.
"""
import logging
import os
2021-06-21 05:27:18 +00:00
import shutil
2021-06-21 07:22:37 +00:00
import time
from datetime import datetime
from typing import Optional
2021-06-21 07:20:45 +00:00
import spqr.curateipsum.fs as fs
BACKUP_ENT_FMT = "%y%m%d_%H%M"
2021-11-08 21:43:18 +00:00
DELTA_DIR = "_delta"
_lg = logging.getLogger(__name__)
2021-11-08 21:43:18 +00:00
def _is_backup_entity(entity_path: str) -> bool:
""" Check if entity_path is a single backup dir. """
2021-10-24 18:20:05 +00:00
if not os.path.isdir(entity_path):
return False
try:
2021-11-08 21:43:18 +00:00
datetime.strptime(os.path.basename(entity_path), BACKUP_ENT_FMT)
return True
except ValueError:
return False
2021-11-08 21:43:18 +00:00
def _get_latest_backup(backup_dir: str) -> Optional[str]:
""" Returns path to latest backup created in backup_dir or None. """
backups = sorted(os.listdir(backup_dir), reverse=True)
for b_ent in backups:
2021-11-08 21:43:18 +00:00
b_ent_abs = os.path.join(backup_dir, b_ent)
if not _is_backup_entity(b_ent_abs):
continue
if not os.listdir(b_ent_abs):
2021-11-08 21:43:18 +00:00
_lg.info("Removing empty backup entity: %s", os.path.basename(b_ent_abs))
os.rmdir(b_ent_abs)
continue
return b_ent_abs
return None
2021-11-08 21:43:18 +00:00
def process_backed_entry(backup_dir: str, entry_relpath: str, action: fs.Actions):
_lg.debug("%s %s", action, entry_relpath)
if action is not fs.Actions.delete:
fs.nest_hardlink(src_dir=backup_dir, src_relpath=entry_relpath,
dst_dir=os.path.join(backup_dir, DELTA_DIR))
2021-06-23 17:11:42 +00:00
def initiate_backup(sources,
2021-11-08 21:43:18 +00:00
backup_dir: str,
2021-06-23 17:11:42 +00:00
dry_run: bool = False,
2021-10-24 18:20:05 +00:00
external_rsync: bool = False,
external_hardlink: bool = False):
""" Main backup function """
2021-06-21 07:22:37 +00:00
start_time = time.time()
start_time_fmt = datetime.fromtimestamp(start_time).strftime(BACKUP_ENT_FMT)
2021-11-08 21:43:18 +00:00
cur_backup = os.path.join(backup_dir, start_time_fmt)
cur_backup_name = os.path.basename(cur_backup)
_lg.debug("Current backup dir: %s", cur_backup)
latest_backup = _get_latest_backup(backup_dir)
if cur_backup == latest_backup:
2021-06-21 07:25:13 +00:00
_lg.warning("Latest backup %s was created less than minute ago, exiting",
2021-11-08 21:43:18 +00:00
os.path.basename(latest_backup))
return
if latest_backup is None:
2021-11-08 21:43:18 +00:00
_lg.info("Creating empty directory for current backup: %s", cur_backup_name)
os.mkdir(cur_backup)
else:
2021-06-21 07:25:13 +00:00
_lg.info("Copying data from latest backup %s to current backup %s",
2021-11-08 21:43:18 +00:00
os.path.basename(latest_backup), cur_backup_name)
2021-06-19 12:28:42 +00:00
2021-10-24 18:20:05 +00:00
hl_res = fs.hardlink_dir(src_dir=latest_backup, dst_dir=cur_backup,
use_external=external_hardlink)
2021-06-21 07:20:45 +00:00
if not hl_res:
_lg.error("Something went wrong during copying data from latest backup,"
2021-11-08 21:43:18 +00:00
" removing created %s", cur_backup_name)
2021-06-21 07:20:45 +00:00
shutil.rmtree(cur_backup, ignore_errors=True)
return
2021-06-21 07:24:53 +00:00
2021-11-08 21:43:18 +00:00
# clean up delta dir from copied backup
shutil.rmtree(os.path.join(cur_backup, DELTA_DIR), ignore_errors=True)
2021-06-23 17:11:42 +00:00
rsync_func = fs.rsync_ext if external_rsync else fs.rsync
2021-06-21 07:24:53 +00:00
for src in sources:
2021-11-08 21:43:18 +00:00
src_abs = os.path.abspath(src)
src_name = os.path.basename(src_abs)
dst_abs = os.path.join(cur_backup, src_name)
_lg.info("Backing up directory %s to %s backup", src_abs, cur_backup_name)
for entry_relpath, action in rsync_func(src_abs, dst_abs, dry_run=dry_run):
2021-11-09 19:50:28 +00:00
if latest_backup is not None:
process_backed_entry(
backup_dir=cur_backup,
entry_relpath=os.path.join(src_name, entry_relpath),
action=action
)
2021-06-21 05:27:18 +00:00
if dry_run:
2021-11-08 21:43:18 +00:00
_lg.info("Dry-run, removing created backup: %s", cur_backup_name)
2021-06-21 05:27:18 +00:00
shutil.rmtree(cur_backup, ignore_errors=True)
else:
2021-11-08 21:43:18 +00:00
_lg.info("Backup created: %s", cur_backup_name)
2021-06-21 07:22:37 +00:00
end_time = time.time()
spend_time = end_time - start_time
_lg.info("Finished, time spent: %.3fs", spend_time)