home-backup/home_backup/user_service/config.py

160 lines
4.9 KiB
Python

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()