import aiofile import asyncio import configparser import io import os import time 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 if not isinstance(name, str): raise TypeError("Name has to be a string.") for i in filter(lambda x: not("a" <= x <= "z" or "A" <= x <= "Z" or "0" <= x <= "9" or x in ("_", "-")), i): raise ValueError("%s isn't a valid char in a backup name." % i) 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: if not isinstance(i, str): raise TypeError("Blocked have to be a list of strings.") for j in filter(lambda x: not("a" <= x <= "z" or "A" <= x <= "Z" or "0" <= x <= "9" or x in ("_", "-")), i): raise ValueError("%s isn't a valid char in a block name." % 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 = [] backups_lock = asyncio.Lock() if os.path.exists(CONFIG_FILE): def _parse_config(): # Load config config = configparser.ConfigParser() config.read(CONFIG_FILE) # Create backups backups = [] for iID, i in filter(lambda x: x[0].startswith("BACKUP|"), config.items()): 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)) return backups backups = _parse_config() async def save_config(): # Write config config = configparser.ConfigParser() 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 # Write data tmp = io.StringIO() config.write(tmp) async with aiofile.AIOFile(CONFIG_FILE, "w") as f: await f.write(tmp.read()) 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 backups_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()