diff --git a/home_backup/__main__.py b/home_backup/__main__.py index c1f3672..31aeaa7 100644 --- a/home_backup/__main__.py +++ b/home_backup/__main__.py @@ -17,8 +17,9 @@ def main(args): parser_remote_add.add_argument("--target", nargs="?", type=str, default=None, help="The target of the remote (required by borgbackup).") parser_remote_add.add_argument("name", nargs=1, type=str, help="The name of the remote.") parser_remote_list = sub_parser.add_parser("remote-list", help="List remotes.") - parser_remote_list.add_argument("name", nargs="?", type=str, default=[None], help="Name of the backup to show details.") + parser_remote_list.add_argument("name", nargs="?", type=str, default=None, help="Name of the backup to show details.") parser_remote_delete = sub_parser.add_parser("remote-delete", help="Delete remote.") + parser_remote_delete.add_argument("name", nargs="+", type=str, help="The name of the remote to remove.") parser_system_server = sub_parser.add_parser("system-server", help="Run system service (root required).") parser_system_server.add_argument("--fork", action="store_const", const=True, default=False, help="Makes a deamon through forking.") @@ -62,6 +63,8 @@ def main(args): client.run_command(client.remote_add_gen(name=result.name[0], rtype=remote_type, info=info)) elif result.action == "remote-list": client.run_command(client.remote_list_gen(name=result.name)) + elif result.action == "remote-delete": + client.run_command(client.remote_delete_gen(result.name)) # Not found action elif result.action is None: diff --git a/home_backup/client.py b/home_backup/client.py index 2ecff9c..9b38523 100644 --- a/home_backup/client.py +++ b/home_backup/client.py @@ -54,4 +54,26 @@ def remote_list_gen(name:str=None): max_length = max(map(lambda x: len(x), result["data"][name].keys())) + 1 for iID, i in sorted(result["data"][name].items(), key=lambda x: x[0]): print("%s:%s%s" % (iID, " " * (max_length - len(iID)), str(i))) - return remote_list \ No newline at end of file + return remote_list + + +def remote_delete_gen(names:list): + # Check + if isinstance(names, str): + raise TypeError("names have to be a listing type.") + names = list(names) + for i in names: + if not isinstance(i, str): + raise TypeError("names list have to contain string.") + + # Procedure + async def remote_delete(con:utils.Connection): + for i in names: + result = await con.call({"operation": "remote-delete", "name": i}) + if result["status"] != "success": + if result["status"] == "failed-not-existing": + print("Remote %s doesn't exists." % i) + exit(1) + else: + raise RuntimeError("Wasn't able to delete remote.") # TODO: Show error + return remote_delete \ No newline at end of file diff --git a/home_backup/sys_service/rpc.py b/home_backup/sys_service/rpc.py index 6f77adf..30cda93 100644 --- a/home_backup/sys_service/rpc.py +++ b/home_backup/sys_service/rpc.py @@ -1,4 +1,4 @@ -from . import btrfs, mounts +from . import snapshot from .. import defaults, utils @@ -10,9 +10,20 @@ async def callback_func(data, uid): if "operation" not in data: raise ValueError("'operation' isn't set.") operation = data["operation"] + del data["operation"] # Run operation - raise NotImplementedError() + if operation == "create_backup": + # Get args + name = data["name"] + del data["name"] + utils.valid_name_check(name) + + # Create snapshot + utils.check_empty_data_dict(data) + return await snapshot.create_snapshot(name, uid) + else: + raise NotImplementedError("%s isn't implemented." & repr(operation)) async def run_deamon(path:str=defaults.DEFAULT_PATH, fork:bool=False): diff --git a/home_backup/sys_service/snapshot.py b/home_backup/sys_service/snapshot.py new file mode 100644 index 0000000..3d721e4 --- /dev/null +++ b/home_backup/sys_service/snapshot.py @@ -0,0 +1,18 @@ +from . import btrfs, mounts +from .. import utils + + +async def create_snapshot(name:str, user:str): + # Find home path and mount + user_path = await utils.get_user_home(user) + candidates = [] + for i in await mounts.list_mounts(): + if user_path.startswith(i.target): + candidates.append(i) + candidates = sorted(candidates, key=lambda x: len(x.target)) + mount = candidates[-1] + + # List subvolumes + subvols = btrfs.list_path_subvolumes(mount, user_path) + print(repr(subvols)) + raise \ No newline at end of file diff --git a/home_backup/user_service/backups.py b/home_backup/user_service/backups.py index 2ffa2f9..b74d91b 100644 --- a/home_backup/user_service/backups.py +++ b/home_backup/user_service/backups.py @@ -1,3 +1,4 @@ +import asyncio from .. import utils @@ -6,7 +7,9 @@ class Backup(): periode:int blocked:set - def __init__(self, name:str, periode:int=None, blocked:list=[]): + to_backup:list + + def __init__(self, name:str, periode:int=None, blocked:list=[], to_backup:list=[]): # Check args utils.valid_name_check(name) if periode is not None and not isinstance(periode, int): @@ -16,11 +19,18 @@ class Backup(): blocked = set(blocked) for i in blocked: utils.valid_name_check(i) + to_backup = set(to_backup) + for i in to_backup: + if not isinstance(i, str): + raise TypeError("to_backup have to be a string.") + if ":" in i: + raise ValueError(": isn't allowed a char.") # Set values self.name = name self.periode = periode self.blocks = blocked + self.to_backup = to_backup def get_next_scedule(self, latest, zero): if self.periode is not None: @@ -35,6 +45,8 @@ class Backup(): result["blocked"] = ",".join(self.blocked) if self.periode is not None: result["periode"] = str(self.periode) + if self.to_backup: + result["to_backup"] = ":".join(self.to_backup) return result @staticmethod @@ -51,8 +63,16 @@ class Backup(): if "blocked" in config: blocked = config["blocked"].split(",") del config["blocked"] + + to_backup = [] + if "to_backup" in config: + to_backup = config["to_backup"].split(":") + del config["to_backup"] # Generate backup utils.check_empty_data_dict(config) - return Backup(name=name, periode=periode, blocked=blocked) \ No newline at end of file + return Backup(name=name, periode=periode, blocked=blocked, to_backup=to_backup) + + async def run_backup(self, subvolumes:list): + print("Subvolumes: %s" % repr(subvolumes)) \ No newline at end of file diff --git a/home_backup/user_service/remotes.py b/home_backup/user_service/remotes.py index 16706d6..7204584 100644 --- a/home_backup/user_service/remotes.py +++ b/home_backup/user_service/remotes.py @@ -84,4 +84,28 @@ async def remote_list(data:dict): result[iID] = i.dump_config() # Return result - return {"status": "success", "data": result} \ No newline at end of file + return {"status": "success", "data": result} + + +async def remote_delete(data:dict): + # Import config + from . import config + + # Check and delete + name = data["name"] + del data["name"] + + utils.check_empty_data_dict(data) + + async with config.config_lock: + # Check + if name not in config.remotes: + return {"status": "failed-not-existing"} + # TODO: Check if backup still used by backup + + # Remove backup + del config.remotes[name] + await config.save_config() + + # Return result + return {"status": "success"} \ No newline at end of file diff --git a/home_backup/user_service/rpc.py b/home_backup/user_service/rpc.py index cc0915f..2df1f12 100644 --- a/home_backup/user_service/rpc.py +++ b/home_backup/user_service/rpc.py @@ -40,6 +40,8 @@ def gen_callback_func(master:BackupManager): return await remotes.add_remote(data) elif operation == "remote-list": return await remotes.remote_list(data) + elif operation == "remote-delete": + return await remotes.remote_delete(data) else: raise NotImplementedError("%s isn't a supported operation." % repr(operation)) return callback_func