From 724166b4a481a48677c4da7177c537cb1f802196 Mon Sep 17 00:00:00 2001 From: Maks Snegov Date: Sun, 30 Jan 2022 18:27:37 +0300 Subject: [PATCH] Support library names in configuration --- Dockerfile | 4 +-- README.md | 2 +- docker-compose.example.yml | 4 +-- {seafile_client => dsc}/__init__.py | 0 {seafile_client => dsc}/client.py | 48 ++++++++++++++++++++--------- {seafile_client => dsc}/consts.py | 1 + {seafile_client => dsc}/misc.py | 6 +--- requirements.txt | 3 +- start.py | 26 +++++++++++++--- 9 files changed, 64 insertions(+), 30 deletions(-) rename {seafile_client => dsc}/__init__.py (100%) rename {seafile_client => dsc}/client.py (61%) rename {seafile_client => dsc}/consts.py (55%) rename {seafile_client => dsc}/misc.py (87%) diff --git a/Dockerfile b/Dockerfile index 1ef7b82..75c9655 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3-slim +FROM python:3.8.12-slim-buster RUN apt-get update && apt-get install gnupg curl -y && rm -rf /var/lib/apt/lists/* RUN curl https://linux-clients.seafile.com/seafile.asc | apt-key add - && \ @@ -11,7 +11,7 @@ WORKDIR /seafile-client COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt -COPY seafile_client ./seafile_client/ +COPY dsc ./dsc/ COPY start.py ./start.py RUN chmod +x /seafile-client/start.py && \ diff --git a/README.md b/README.md index 5e14b0a..140fc47 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Inside container libraries' content will be put in `/data` directory, so map you Also you could check [docker-compose example](docker-compose.example.yml). ## Environment variables: - - `LIBRARY_ID=` ID of library to sync; multiple libraries could be separated by colon `:`. + - `LIBRARY_ID=` Library to sync, ID or name; multiple libraries could be separated by colon `:`. - `SERVER_HOST=` Hostname of your seafile server, eg: `seafile.example.com`. If you're using non-standart port, specify it here, eg: `seafile.example.com:8080`. - `USERNAME=` Seafile account username. - `PASSWORD=` Seafile account password. diff --git a/docker-compose.example.yml b/docker-compose.example.yml index 86544f9..8383bd5 100644 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -5,7 +5,7 @@ services: restart: always image: snegov/seafile-client environment: - - LIBRARY_ID="79867cbf-2944-488d-9105-859463ecdf9e:8078e3ff-b2a0-450a-b4dd-c1ed9ef18294" + - LIBRARY_ID="79867cbf-2944-488d-9105-859463ecdf9e:test_library" - SERVER_HOST=seafile.example.com - USERNAME=user - PASSWORD=password @@ -15,7 +15,7 @@ services: volumes: - seafile-data:/seafile-client/seafile-data - /home/johndow/seafile:/data - + container_name: seafile-client volumes: seafile-data: diff --git a/seafile_client/__init__.py b/dsc/__init__.py similarity index 100% rename from seafile_client/__init__.py rename to dsc/__init__.py diff --git a/seafile_client/client.py b/dsc/client.py similarity index 61% rename from seafile_client/client.py rename to dsc/client.py index 0dcac48..0f614fd 100644 --- a/seafile_client/client.py +++ b/dsc/client.py @@ -2,16 +2,16 @@ import logging import os import subprocess import time +from typing import Optional from urllib.parse import urlparse +from cached_property import cached_property_with_ttl import requests -from .consts import DEFAULT_USERNAME -from .misc import create_dir +from dsc import consts +from dsc.misc import create_dir - -logging.basicConfig(format="%(asctime)s %(message)s", - level=logging.INFO) +_lg = logging.getLogger(__name__) class SeafileClient: @@ -36,16 +36,24 @@ class SeafileClient: self.__token = r.json()['token'] return self.__token - def get_lib_name(self, lib_id: str) -> str: - url = f"{self.url}/api2/repos/{lib_id}" + @cached_property_with_ttl(ttl=60) + def remote_libraries(self) -> dict: + url = f"{self.url}/api2/repos/" auth_header = {"Authorization": f"Token {self.token}"} r = requests.get(url, headers=auth_header) if r.status_code != 200: raise RuntimeError(r.text) - return r.json()['name'] + r_libs = {lib["id"]: lib["name"] for lib in r.json()} + return r_libs + + def get_library_id(self, library) -> Optional[str]: + for lib_id, lib_name in self.remote_libraries.items(): + if library in (lib_id, lib_name): + return lib_id + return None def sync_lib(self, lib_id: str, data_dir: str): - lib_name = self.get_lib_name(lib_id) + lib_name = self.remote_libraries[lib_id] lib_dir = os.path.join(data_dir, lib_name.replace(' ', '_')) create_dir(lib_dir) cmd = ['seaf-cli', 'sync', @@ -55,11 +63,11 @@ class SeafileClient: '-u', self.user, '-p', self.password] cmd = ' '.join(cmd) - subprocess.run(['su', '-', DEFAULT_USERNAME, '-c', cmd]) + subprocess.run(['su', '-', consts.DEFAULT_USERNAME, '-c', cmd]) def get_status(self): cmd = 'seaf-cli status' - out = subprocess.check_output(['su', '-', DEFAULT_USERNAME, '-c', cmd]) + out = subprocess.check_output(['su', '-', consts.DEFAULT_USERNAME, '-c', cmd]) out = out.decode().splitlines() statuses = dict() @@ -75,15 +83,25 @@ class SeafileClient: def watch_status(self): prev_status = dict() while True: - time.sleep(5) + time.sleep(consts.STATUS_POLL_PERIOD) cur_status = self.get_status() for folder, state in cur_status.items(): if state != prev_status.get(folder): - logging.info(f"Library {folder}:\t{state}") + logging.info("Library %s:\t%s", folder, state) prev_status[folder] = cur_status[folder] + def get_local_libraries(self) -> set: + cmd = 'seaf-cli list' + out = subprocess.check_output(['su', '-', consts.DEFAULT_USERNAME, '-c', cmd]) + out = out.decode().splitlines()[1:] # first line is a header + + local_libs = set() + for line in out: + lib_name, lib_id, lib_path = line.rsplit(maxsplit=3) + local_libs.add(lib_id) + return local_libs + def start_seaf_daemon(): cmd = 'seaf-cli start' - subprocess.run(['su', '-', DEFAULT_USERNAME, '-c', cmd]) - time.sleep(5) + subprocess.run(['su', '-', consts.DEFAULT_USERNAME, '-c', cmd]) diff --git a/seafile_client/consts.py b/dsc/consts.py similarity index 55% rename from seafile_client/consts.py rename to dsc/consts.py index 2402e27..f75d56b 100644 --- a/seafile_client/consts.py +++ b/dsc/consts.py @@ -1 +1,2 @@ DEFAULT_USERNAME = "seafile" +STATUS_POLL_PERIOD = 1 diff --git a/seafile_client/misc.py b/dsc/misc.py similarity index 87% rename from seafile_client/misc.py rename to dsc/misc.py index f6f37a7..26e2528 100644 --- a/seafile_client/misc.py +++ b/dsc/misc.py @@ -2,7 +2,7 @@ import os import pwd import subprocess -from .consts import DEFAULT_USERNAME +from dsc.consts import DEFAULT_USERNAME def setup_uid(uid: int, gid: int): @@ -19,7 +19,3 @@ def create_dir(dir_path: str): else: if not os.path.isdir(dir_path): raise RuntimeError(f"Data dir {dir_path} is not a directory") - - -def tail_f(fpath): - os.system(f"tail -f {fpath}") diff --git a/requirements.txt b/requirements.txt index f229360..f27d2b7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests +cached_property==1.5.2 +requests==2.27.1 diff --git a/start.py b/start.py index d4fef9b..fbe4a30 100755 --- a/start.py +++ b/start.py @@ -1,15 +1,20 @@ #!/usr/bin/env python3 import argparse +import logging import os import os.path import sys -from seafile_client import SeafileClient, start_seaf_daemon -from seafile_client.misc import setup_uid, create_dir +from dsc import SeafileClient, start_seaf_daemon +from dsc.misc import setup_uid, create_dir + +_lg = logging.getLogger('dsc') def main(): + logging.basicConfig(format="%(asctime)s %(message)s", level=logging.INFO) + parser = argparse.ArgumentParser() parser.add_argument("--uid", default=os.getenv("SEAFILE_UID", default=1000), type=int) parser.add_argument("--gid", default=os.getenv("SEAFILE_GID", default=100), type=int) @@ -21,10 +26,23 @@ def main(): args = parser.parse_args() setup_uid(args.uid, args.gid) - start_seaf_daemon() create_dir(args.data_dir) + start_seaf_daemon() + + libs_to_sync = set() + client = SeafileClient(args.host, args.username, args.password) - for lib_id in args.libs.split(sep=":"): + for arg_lib in args.libs.split(sep=":"): + lib_id = client.get_library_id(arg_lib) + if lib_id: + libs_to_sync.add(lib_id) + else: + _lg.warning("Library %s is not found on server %s", arg_lib, args.host) + + # don't start to sync libraries already in sync + libs_to_sync -= client.get_local_libraries() + + for lib_id in libs_to_sync: client.sync_lib(lib_id, args.data_dir) client.watch_status()