Compare commits

..

2 Commits

Author SHA1 Message Date
7dd7b57e1f synchroniser : Use a consistent docstring format.
Edit the docstrings so they use a consistent format.
Also add a short module docstring.
2026-01-04 14:31:16 +01:00
b10ed69d59 defaults : change type of MISC_CACHE_DIR_PATH to str
DEFAULT_MISC_CACHE_DIR_PATH was a Path but the fallbacks of config.get
in config.py will be converted to a string so make it a string instead
and do the conversion later
2026-01-04 12:22:21 +01:00
2 changed files with 95 additions and 36 deletions

View File

@@ -15,4 +15,4 @@ DEFAULT_SERVER_PORT: int = 22
DEFAULT_ROOTS_LOCAL: str = str(Path("~/files").expanduser()) DEFAULT_ROOTS_LOCAL: str = str(Path("~/files").expanduser())
# DEFAULT_ROOTS_REMOTE: str = "" # DEFAULT_ROOTS_REMOTE: str = ""
DEFAULT_MISC_CACHE_DIR_PATH: Path = Path("~/.unisync").expanduser() DEFAULT_MISC_CACHE_DIR_PATH: str = "~/.unisync"

View File

@@ -1,6 +1,12 @@
# Copyright (C) 2025-2026 Paul Retourné # Copyright (C) 2025-2026 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
"""Exports the Synchroniser class.
This class is used to perform all the actions that require a connection to
the remote.
"""
import subprocess import subprocess
import os import os
import sys import sys
@@ -14,9 +20,37 @@ from unisync.errors import RemoteMountedError, InvalidMountError
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Synchroniser: class Synchroniser:
"""Synchroniser used to synchronise with a server.
It is used to perform every action needing a connection to the remote.
Create an ssh connection.
Perform the various synchronisation steps (files, links).
Update the links on the remote.
Mount the remote directory.
Close the ssh connection.
Attributes:
remote: The directory to synchronise to on the remote.
local: The directory to synchronise from locally.
user: The user on the remote server.
ip: The ip of the remote server.
port: The ssh port on the remote.
args_bool:
A list of boolean arguments for unison.
They will be passed directly to unison when calling it.
For example : auto will be passed as -auto
args_value:
Same as args_bool but for key value arguments.
Will be passed to unison as "-key value".
ssh_settings:
Settings to pass to the underlying ssh connection.
Currently unused.
"""
def __init__(self, remote:str, local:str, user:str, ip:str, def __init__(self, remote:str, local:str, user:str, ip:str,
port:int=22, args_bool:list=[], args_value:dict={}, ssh_settings:dict={}): port:int=22, args_bool:list=[], args_value:dict={}, ssh_settings:dict={}):
"""Initialises an instance of Synchroniser.
"""
self.remote_dir:str = remote self.remote_dir:str = remote
self.local:str = local self.local:str = local
self.args_bool:list[str] = args_bool self.args_bool:list[str] = args_bool
@@ -27,14 +61,21 @@ class Synchroniser:
self.remote_port:int = port self.remote_port:int = port
def create_ssh_master_connection(self, control_path:str="~/.ssh/control_%C", connection_timeout:int=60) -> int: def create_ssh_master_connection(self, control_path:str="~/.ssh/control_%C", connection_timeout:int=60) -> int:
""" """Creates an ssh master connection.
Creates an ssh master connection so the user only has to authenticate once to the remote server.
The subsequent connections will be made through this master connection which speeds up connecting. It is used so the user only has to authenticate once to the remote server.
@control_path: Set the location of the ssh control socket The subsequent connections will be made through this master connection
@connection_timeout: which speeds up connnection.
Time given to the user to authenticate to the remote server. The users only have to enter their password once per synchronisation.
On slow connections one might want to increase this.
Returns 0 on success. Args:
control_path: Set the location of the ssh control socket
connection_timeout:
Time given to the user to authenticate to the remote server.
On slow connections one might want to increase this.
Returns:
An error code (0 success, 1 TimeoutExpired, 2 KeyboardInterrupt).
TODO change that to raising the exception.
""" """
self.control_path = os.path.expanduser(control_path) self.control_path = os.path.expanduser(control_path)
command = [ command = [
@@ -61,8 +102,10 @@ class Synchroniser:
def close_ssh_master_connection(self) -> int: def close_ssh_master_connection(self) -> int:
""" """Closes the ssh master connection.
Close the ssh master connection.
Returns:
The return code of the ssh call.
""" """
command = [ command = [
"/usr/bin/ssh", "/usr/bin/ssh",
@@ -75,8 +118,14 @@ class Synchroniser:
return close.wait() return close.wait()
def sync_files(self, paths:list, force:bool=False) -> int: def sync_files(self, paths:list, force:bool=False) -> int:
""" """Synchronises the files.
Synchronises the files.
Args:
paths: List of paths to synchronise.
force: Force the changes from remote to local.
Returns:
The return code of sync.
""" """
return self.sync( return self.sync(
f"ssh://{self.remote_user}@{self.remote_ip}/{self.remote_dir}/.data", f"ssh://{self.remote_user}@{self.remote_ip}/{self.remote_dir}/.data",
@@ -86,8 +135,13 @@ class Synchroniser:
) )
def sync_links(self, ignore:list) -> int: def sync_links(self, ignore:list) -> int:
""" """Synchronises the links, they must exist already.
Synchronises the links, they must exist already.
Args:
ignore: List of paths to ignore.
Returns:
The return code of sync.
""" """
return self.sync( return self.sync(
f"ssh://{self.remote_user}@{self.remote_ip}/{self.remote_dir}/links", f"ssh://{self.remote_user}@{self.remote_ip}/{self.remote_dir}/links",
@@ -97,17 +151,20 @@ class Synchroniser:
def sync(self, remote_root:str, local_root:str, def sync(self, remote_root:str, local_root:str,
paths:list=[], ignore:list=[], force:bool=False) -> int: paths:list=[], ignore:list=[], force:bool=False) -> int:
""" """Performs the synchronisation by calling unison.
Perform the synchronisation by calling unison.
@remote_root: The remote root, must be a full root usable by unison. Args:
@local_root: The local root, must be a full root usable by unison. remote_root: The remote root, must be a full root usable by unison.
@paths: List of paths to synchronise local_root: The local root, must be a full root usable by unison.
@ignore: List of paths to ignore paths: List of paths to synchronise
The paths and everything under them will be ignored. ignore: List of paths to ignore
If you need to ignore some specific files use the arguments. The paths and everything under them will be ignored.
@force: Force all changes from remote to local. If you need to ignore some specific files use the arguments.
Used mostly when replacing a link by the file. force: Force all changes from remote to local.
Returns: the unison return code see section 6.11 of the documentation Used mostly when replacing a link by the file.
Returns:
the unison return code see section 6.11 of the documentation
""" """
command = [ "/usr/bin/unison", "-root", remote_root, "-root", local_root ] command = [ "/usr/bin/unison", "-root", remote_root, "-root", local_root ]
for arg in self.args_bool: for arg in self.args_bool:
@@ -140,12 +197,13 @@ class Synchroniser:
return ret_code return ret_code
def update_links(self, background:bool=True): def update_links(self, background:bool=True):
""" """Updates the links on the remote.
Update the links on the remote.
First calls cleanlinks to remove deadlinks and empty directories. First calls cleanlinks to remove deadlinks and empty directories.
Then calls lndir to create the new links. Then calls lndir to create the new links.
Args: Args:
- background: controls if the update is done in the background or waited for background: controls if the update is done in the background or waited for.
""" """
link_update_script = (f"cd {self.remote_dir}/links && " link_update_script = (f"cd {self.remote_dir}/links && "
@@ -173,13 +231,14 @@ class Synchroniser:
print("Done") print("Done")
def mount_remote_dir(self): def mount_remote_dir(self):
""" """Mounts the remote directory to make the local links work.
Mount the remote directory to make the local links work.
This is achieved using sshfs. This is achieved using sshfs which may fail.
Raise:
- RemoteMountedError: The .data directory is already a mount point Raises:
- InvalidMountError: .data is either not a directory or not empty RemoteMountedError: The .data directory is already a mount point.
- subprocess.CalledProcessError: An error occured with sshfs InvalidMountError: .data is either not a directory or not empty.
subprocess.CalledProcessError: An error occured with sshfs.
""" """
# Get the absolute path to the correct .data directory resolving symlinks # Get the absolute path to the correct .data directory resolving symlinks
path_to_mount:Path = Path(f"{self.local}/../.data").resolve() path_to_mount:Path = Path(f"{self.local}/../.data").resolve()