148 lines
4.8 KiB
Python
148 lines
4.8 KiB
Python
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() |