Compare commits

..

5 Commits

Author SHA1 Message Date
addbdb87df main : adds subcommands, move to Path and improve
Multiple changes to the main file, after this unisync becomes kind of
usable.
Add subcommands : this uses the 2 previous commits to add the
subcommands to unisync it is now possible to sync, add and mount.
pathlib : move from PosixPath to Path
Remove unused imports
Rename base_namespace to cli_args
Add some comments and TODOs
2026-01-01 23:19:57 +01:00
885050cf84 argparser : adds subcommands to the argparser
this adds subcommands to the argparser using subparsers, we also set a
default value for func depending on which of the subcommands is
selected.
Also change the formatting of the epilog so it is on two lines.
2026-01-01 23:18:52 +01:00
bd65c623a4 runners : Create runners file and basic runnners
This adds runners.py, it contains a set of functions that peform all the
various task that unisync can do (sync, add and mount for now).
They are simple function that put together all the rest.
2026-01-01 23:15:02 +01:00
89fa5bca70 paths : fixes write_new_paths writing of the file
I was writing the file using 'w' instead of 'a' so the old paths were
deleted use 'a'.
2026-01-01 18:51:45 +01:00
5af5374f77 config : fallback to 22 instead of None
The configparser fallback option was None set it to use 22 instead as
None doesn't make sense
2026-01-01 17:30:28 +01:00
10 changed files with 33 additions and 67 deletions

View File

@@ -23,4 +23,5 @@ The issue is that you need to know what data is stored on the server to avoid co
# Developement
Unisync was at first a simple bash script but as it grew more complex I started struggling to maintain it which is why I am porting it to python. It will make everything more robust, easier to maintain and to add functionalities.
I am in the early stages of the developement process, this should be usable someday (hopefully).
I am in the early stages of the developement process, this should be usable in the upcoming weeks.
Help will be welcome in the future but is not desirable right now as I want to shape this the way I want to.

View File

@@ -10,9 +10,6 @@ requires-python = ">=3.13"
dependencies = [
]
[project.scripts]
unisync = "unisync.main:main"
[tool.poetry]
packages = [{include = "unisync", from = "src"}]

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2025-2026 Paul Retourné
# Copyright (C) 2025 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later
import argparse
@@ -11,7 +11,7 @@ def create_argparser(sync_function, add_function, mount_function) -> argparse.Ar
parser = argparse.ArgumentParser(
prog='unisync',
description='File synchronisation application',
epilog="Copyright © 2025-2026 Paul Retourné.\n"
epilog="Copyright © 2025 Paul Retourné.\n"
"License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.",
formatter_class=argparse.RawDescriptionHelpFormatter
)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2025-2026 Paul Retourné
# Copyright (C) 2025 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later
from configparser import UNNAMED_SECTION
@@ -7,18 +7,16 @@ from pathlib import Path
import ipaddress
import configparser
from unisync.defaults import *
@dataclass
class ServerConfig:
"""
Dataclass keeping the config for connecting to the server
"""
user: str
sshargs: str
hostname: str
ip: str
port: int
sshargs: str = ""
hostname: str = ""
ip: str = ""
port: int = 22
def __post_init__(self):
"""
@@ -46,15 +44,15 @@ class UnisonConfig:
"""
Dataclass keeping unison specific configurations
"""
bools: list
values: dict
bools: list = field(default_factory=list)
values: dict = field(default_factory=dict)
@dataclass
class OtherConfig:
"""
Dataclass keeping miscellanous configuration options
"""
cache_dir_path: Path
cache_dir_path: Path = Path("~/.unisync").expanduser()
@dataclass
class Config:
@@ -64,7 +62,7 @@ class Config:
server: ServerConfig
roots: RootsConfig
unison: UnisonConfig
other: OtherConfig
other: OtherConfig = field(default_factory=OtherConfig)
def load_config(config_path:str) -> Config:
@@ -81,22 +79,18 @@ def load_config(config_path:str) -> Config:
# Check if sections are provided
server_section = "Server" if "Server" in config.sections() else UNNAMED_SECTION
roots_section = "Roots" if "Roots" in config.sections() else UNNAMED_SECTION
other_section = "Other" if "Other" in config.sections() else UNNAMED_SECTION
server_config = ServerConfig(
config.get(server_section, "user"),
config.get(server_section, "sshargs", fallback=DEFAULT_SERVER_SSHARGS),
config.get(server_section, "hostname", fallback=DEFAULT_SERVER_HOSTNAME),
config.get(server_section, "ip", fallback=DEFAULT_SERVER_IP),
config.getint(server_section, "port", fallback=DEFAULT_SERVER_PORT)
config.get(server_section, "sshargs", fallback=""),
config.get(server_section, "hostname", fallback=""),
config.get(server_section, "ip", fallback=""),
config.getint(server_section, "port", fallback=22)
)
roots_config = RootsConfig(
config.get(roots_section, "local", fallback=DEFAULT_ROOTS_LOCAL),
config.get(roots_section, "local"),
config.get(roots_section, "remote")
)
other_config = OtherConfig(
Path(config.get(other_section, "cache_dir_path", fallback=DEFAULT_MISC_CACHE_DIR_PATH)).expanduser()
)
args_bool = list()
args_val = dict()
@@ -110,4 +104,4 @@ def load_config(config_path:str) -> Config:
args_val[key] = val
unison_config = UnisonConfig(args_bool, args_val)
return Config(server_config, roots_config, unison_config, other_config)
return Config(server_config, roots_config, unison_config)

View File

@@ -1,18 +0,0 @@
# copyright (c) 2026 paul retourné
# spdx-license-identifier: gpl-3.0-or-later
from pathlib import Path
# Commented out values are part of the config but are required so there is no defaults.
# This allows this file to be a list of all the config options.
# DEFAULT_SERVER_USER: str = ""
DEFAULT_SERVER_SSHARGS: str = ""
DEFAULT_SERVER_HOSTNAME: str = ""
DEFAULT_SERVER_IP: str = ""
DEFAULT_SERVER_PORT: int = 22
DEFAULT_ROOTS_LOCAL: str = str(Path("~/files").expanduser())
# DEFAULT_ROOTS_REMOTE: str = ""
DEFAULT_MISC_CACHE_DIR_PATH: Path = Path("~/.unisync").expanduser()

View File

@@ -1,6 +1,3 @@
# Copyright (C) 2025-2026 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later
class RemoteMountedError(BaseException):
pass

View File

@@ -1,14 +1,13 @@
# Copyright (C) 2025-2026 Paul Retourné
# Copyright (C) 2025 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later
import os
from argparser import create_argparser
from runners import unisync_sync, unisync_add, unisync_mount
from config import load_config
from synchroniser import Synchroniser
from pathlib import Path
from unisync.argparser import create_argparser
from unisync.runners import unisync_sync, unisync_add, unisync_mount
from unisync.config import load_config
from unisync.synchroniser import Synchroniser
from unisync.paths import *
from paths import *
def main():
parser = create_argparser(unisync_sync, unisync_add, unisync_mount)

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2025-2026 Paul Retourné
# Copyright (C) 2025 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later

View File

@@ -1,8 +1,5 @@
# Copyright (C) 2025-2026 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later
from unisync.synchroniser import Synchroniser
from unisync.paths import PathsManager
from synchroniser import Synchroniser
from paths import PathsManager
def unisync_sync(synchroniser:Synchroniser, paths_manager:PathsManager):
@@ -12,10 +9,9 @@ def unisync_sync(synchroniser:Synchroniser, paths_manager:PathsManager):
print("Connected to the remote.")
synchroniser.sync_files(paths_manager.get_paths_to_sync())
synchroniser.sync_links(paths_manager.get_paths_to_sync())
synchroniser.update_links(background=False)
# TODO check the config options and do or don't do the following
synchroniser.update_links()
# TODO check the config options
#synchroniser.mount_remote_dir()
synchroniser.close_ssh_master_connection()

View File

@@ -1,4 +1,4 @@
# Copyright (C) 2025-2026 Paul Retourné
# Copyright (C) 2025 Paul Retourné
# SPDX-License-Identifier: GPL-3.0-or-later
import subprocess
@@ -9,7 +9,7 @@ import logging
from pathlib import Path
from unisync.errors import RemoteMountedError, InvalidMountError
from errors import RemoteMountedError, InvalidMountError
logger = logging.getLogger(__name__)