import aiofile import asyncio import configparser import io import os import time from .remotes import Remote from .. import utils # Config HOME_DIR = os.path.abspath(os.environ.get("HOME")) if os.environ.get("XDG_CONFIG_HOME", None) is not None: CONFIG_FILE = os.environ.get("XDG_CONFIG_HOME") else: CONFIG_FILE = os.path.join(HOME_DIR, ".config") CONFIG_FILE = os.path.join(CONFIG_FILE, "home-backup.conf") # TODO: Set own config dir class Backup(): name:str periode:int blocked:set def __init__(self, name:str, periode:int=None, blocked:list=[]): # Check args utils.valid_name_check(name) if periode is not None and not isinstance(periode, int): raise TypeError("Periode have to be an integer or null.") if periode is not None and periode < 0: raise ValueError("periode can't be negetive.") blocked = set(blocked) for i in blocked: utils.valid_name_check(i) # Set values self.name = name self.periode = periode self.blocks = blocked def get_next_scedule(self, latest, zero): if self.periode is not None: tmp = (latest - zero) // self.periode return zero + self.periode * (tmp + 1) else: raise NotImplementedError("No implemented types.") backups = [] remotes = {} config_lock = asyncio.Lock() if os.path.exists(CONFIG_FILE): def _parse_config(): # Load config config = configparser.ConfigParser() config.read(CONFIG_FILE) backups = [] remotes = {} for iID, i in filter(lambda x: x[0] != "DEFAULT", config.items()): if iID.startswith("BACKUP|"): # Parse backup config iID = iID[len("BACKUP|"):] periode = None if "periode" in i: periode = int(i["periode"]) blocked = [] if "blocked" in i: blocked = i["blocked"].split(",") backups.append(Backup(iID, periode=periode, blocked=blocked)) elif iID.startswith("REMOTE|"): # Parse remote config iID = iID[len("REMOTE|"):] tmp = Remote.load_remote(iID, i) remotes[tmp.name] = tmp else: raise ValueError("Unknown config part %s." % repr(iID)) return backups, remotes backups, remotes = _parse_config() async def save_config(): # Create config config = configparser.ConfigParser() # Add backups for i in backups: if not isinstance(i, Backup): raise ValueError("backups contains a non backup config entry.") backup_data = { "blocked": ",".join(i.blocked) } if i.periode is not None: backup_data["periode"] = str(i.periode) config["BACKUP|%s" % i.name] = backup_data # Add remotes for i in remotes.values(): if not isinstance(i, Remote): raise ValueError("remotes contains a non remote config entry.") config["REMOTE|%s" % i.name] = i.dump_config() # Write data tmp = io.StringIO() config.write(tmp) to_write = tmp.getvalue() async with aiofile.AIOFile(CONFIG_FILE, "w") as f: await f.write(to_write) await f.fsync() # Timer support class Timer(): __zero:int __latest:int def __init__(self): self.__zero = self.__latest = int(time.time()) async def run(self, async_callback): tasks = [] try: while True: # Delete finished futures to_delete = [] for i in tasks: if i.done() or i.cancelled(): to_delete.append(i) for i in to_delete: await i tasks.remove(i) # Check for new backup next_time = int(time.time()) candiates = {} candidate_name = set() async with config_lock: for i in backups: if self.__latest < i.get_next_scedule(self.__latest, self.__zero) <= next_time: candiates[i.name] = i candidate_name.add(i.name) self.__latest = next_time # Filter backups to_ignore = set() for iID, i in candiates.items(): if candidate_name.intersection(i.blocks): to_ignore.add(iID) # Generate backup task backups_to_run = [] for iID, i in filter(lambda x: x[0] not in to_ignore, candiates.items()): backups_to_run.append(i) tasks.append(asyncio.create_task(async_callback(backups_to_run))) # Wait for 1 min await asyncio.sleep(60) # Stop backup processes finally: for i in tasks: i.cancel()