2019-12-05 12:57:59 +00:00
|
|
|
import logging
|
2019-09-06 18:41:43 +00:00
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import time
|
2022-01-30 15:27:37 +00:00
|
|
|
from typing import Optional
|
2020-08-25 20:17:58 +00:00
|
|
|
from urllib.parse import urlparse
|
2019-09-06 18:41:43 +00:00
|
|
|
|
2022-01-30 15:27:37 +00:00
|
|
|
from cached_property import cached_property_with_ttl
|
2019-09-06 18:41:43 +00:00
|
|
|
import requests
|
|
|
|
|
|
2023-09-16 08:27:46 +00:00
|
|
|
from dsc import const
|
2023-09-14 23:22:02 +00:00
|
|
|
from dsc.misc import create_dir, hide_password
|
2019-09-06 18:41:43 +00:00
|
|
|
|
2022-01-30 15:27:37 +00:00
|
|
|
_lg = logging.getLogger(__name__)
|
2019-12-05 12:57:59 +00:00
|
|
|
|
|
|
|
|
|
2019-09-06 18:41:43 +00:00
|
|
|
class SeafileClient:
|
2023-09-16 08:27:46 +00:00
|
|
|
def __init__(self,
|
|
|
|
|
host: str,
|
|
|
|
|
user: str,
|
|
|
|
|
passwd: str,
|
|
|
|
|
app_dir: str = const.DEFAULT_APP_DIR):
|
2020-08-25 20:17:58 +00:00
|
|
|
up = urlparse(requests.get(f"http://{host}").url)
|
|
|
|
|
self.url = f"{up.scheme}://{up.netloc}"
|
2019-09-06 18:41:43 +00:00
|
|
|
self.user = user
|
|
|
|
|
self.password = passwd
|
2023-09-16 08:27:46 +00:00
|
|
|
self.app_dir = os.path.abspath(app_dir)
|
2019-09-06 18:41:43 +00:00
|
|
|
self.__token = None
|
|
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
|
return f"SeafileClient({self.user}@{self.url})"
|
|
|
|
|
|
2023-09-16 08:27:46 +00:00
|
|
|
def __gen_cmd(self, cmd: str) -> list:
|
|
|
|
|
return ["su", "-", const.DEFAULT_USERNAME, "-c", cmd]
|
|
|
|
|
|
2019-09-06 18:41:43 +00:00
|
|
|
@property
|
|
|
|
|
def token(self):
|
|
|
|
|
if self.__token is None:
|
|
|
|
|
url = f"{self.url}/api2/auth-token/"
|
2023-09-14 23:22:02 +00:00
|
|
|
_lg.info("Fetching token: %s", url)
|
2023-09-16 08:27:46 +00:00
|
|
|
r = requests.post(url, data={"username": self.user, "password": self.password})
|
2019-09-06 18:41:43 +00:00
|
|
|
if r.status_code != 200:
|
|
|
|
|
raise RuntimeError(f"Can't get token: {r.text}")
|
2023-09-16 08:27:46 +00:00
|
|
|
self.__token = r.json()["token"]
|
2019-09-06 18:41:43 +00:00
|
|
|
return self.__token
|
|
|
|
|
|
2022-01-30 15:27:37 +00:00
|
|
|
@cached_property_with_ttl(ttl=60)
|
|
|
|
|
def remote_libraries(self) -> dict:
|
|
|
|
|
url = f"{self.url}/api2/repos/"
|
2023-09-14 23:22:02 +00:00
|
|
|
_lg.info("Fetching remote libraries: %s", url)
|
2019-09-06 18:41:43 +00:00
|
|
|
auth_header = {"Authorization": f"Token {self.token}"}
|
|
|
|
|
r = requests.get(url, headers=auth_header)
|
|
|
|
|
if r.status_code != 200:
|
|
|
|
|
raise RuntimeError(r.text)
|
2022-01-30 15:27:37 +00:00
|
|
|
r_libs = {lib["id"]: lib["name"] for lib in r.json()}
|
|
|
|
|
return r_libs
|
|
|
|
|
|
2023-09-16 08:27:46 +00:00
|
|
|
@property
|
|
|
|
|
def config_initialized(self) -> bool:
|
|
|
|
|
return os.path.isdir(os.path.join(self.app_dir, ".ccnet"))
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def daemon_ready(self) -> bool:
|
|
|
|
|
cmd = "seaf-cli status"
|
|
|
|
|
_lg.info("Checking seafile daemon status: %s", cmd)
|
|
|
|
|
proc = subprocess.run(
|
|
|
|
|
self.__gen_cmd(cmd),
|
|
|
|
|
stdout=subprocess.DEVNULL,
|
|
|
|
|
stderr=subprocess.DEVNULL,
|
|
|
|
|
)
|
|
|
|
|
return proc.returncode == 0
|
|
|
|
|
|
|
|
|
|
def init_config(self):
|
|
|
|
|
if self.config_initialized:
|
|
|
|
|
return
|
|
|
|
|
cmd = "seaf-cli init -d %s" % self.app_dir
|
|
|
|
|
_lg.info("Initializing seafile config: %s", cmd)
|
|
|
|
|
subprocess.run(self.__gen_cmd(cmd))
|
|
|
|
|
|
|
|
|
|
def start_daemon(self):
|
|
|
|
|
cmd = "seaf-cli start"
|
|
|
|
|
_lg.info("Starting seafile daemon: %s", cmd)
|
|
|
|
|
subprocess.run(self.__gen_cmd(cmd))
|
|
|
|
|
_lg.info("Waiting for seafile daemon to start")
|
|
|
|
|
|
|
|
|
|
while True:
|
|
|
|
|
if self.daemon_ready:
|
|
|
|
|
break
|
|
|
|
|
time.sleep(5)
|
|
|
|
|
|
|
|
|
|
_lg.info("Seafile daemon is ready")
|
|
|
|
|
|
2022-01-30 15:27:37 +00:00
|
|
|
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
|
2019-09-06 18:41:43 +00:00
|
|
|
|
2023-09-16 08:27:46 +00:00
|
|
|
def sync_lib(self, lib_id: str, parent_dir: str = const.DEFAULT_LIBS_DIR):
|
2022-01-30 15:27:37 +00:00
|
|
|
lib_name = self.remote_libraries[lib_id]
|
2023-09-16 08:27:46 +00:00
|
|
|
lib_dir = os.path.join(parent_dir, lib_name.replace(" ", "_"))
|
2019-09-06 18:41:43 +00:00
|
|
|
create_dir(lib_dir)
|
2023-09-16 08:27:46 +00:00
|
|
|
cmd = [
|
|
|
|
|
"seaf-cli",
|
|
|
|
|
"sync",
|
|
|
|
|
"-l", lib_id,
|
|
|
|
|
"-s", self.url,
|
|
|
|
|
"-d", lib_dir,
|
|
|
|
|
"-u", self.user,
|
|
|
|
|
"-p", self.password,
|
|
|
|
|
]
|
|
|
|
|
_lg.info(
|
|
|
|
|
"Syncing library %s: %s", lib_name,
|
|
|
|
|
" ".join(hide_password(cmd, self.password)),
|
|
|
|
|
)
|
|
|
|
|
subprocess.run(self.__gen_cmd(" ".join(cmd)))
|
2019-09-06 18:41:43 +00:00
|
|
|
|
|
|
|
|
def get_status(self):
|
2023-09-16 08:27:46 +00:00
|
|
|
cmd = "seaf-cli status"
|
2023-09-14 23:22:02 +00:00
|
|
|
_lg.debug("Fetching seafile client status: %s", cmd)
|
2023-09-16 08:27:46 +00:00
|
|
|
out = subprocess.check_output(self.__gen_cmd(cmd))
|
2019-09-06 18:41:43 +00:00
|
|
|
out = out.decode().splitlines()
|
|
|
|
|
|
|
|
|
|
statuses = dict()
|
|
|
|
|
for line in out:
|
2023-09-16 08:27:46 +00:00
|
|
|
if line.startswith("#") or not line.strip():
|
2019-09-06 18:41:43 +00:00
|
|
|
continue
|
2023-09-16 08:27:46 +00:00
|
|
|
lib, status = line.split(sep="\t", maxsplit=1)
|
2019-12-05 12:57:59 +00:00
|
|
|
lib = lib.strip()
|
|
|
|
|
status = " ".join(status.split())
|
2019-09-06 18:41:43 +00:00
|
|
|
statuses[lib] = status
|
|
|
|
|
return statuses
|
|
|
|
|
|
|
|
|
|
def watch_status(self):
|
|
|
|
|
prev_status = dict()
|
|
|
|
|
while True:
|
2023-09-16 08:27:46 +00:00
|
|
|
time.sleep(const.STATUS_POLL_PERIOD)
|
2019-09-06 18:41:43 +00:00
|
|
|
cur_status = self.get_status()
|
|
|
|
|
for folder, state in cur_status.items():
|
|
|
|
|
if state != prev_status.get(folder):
|
2022-01-30 15:27:37 +00:00
|
|
|
logging.info("Library %s:\t%s", folder, state)
|
2019-09-06 18:41:43 +00:00
|
|
|
prev_status[folder] = cur_status[folder]
|
|
|
|
|
|
2022-01-30 15:27:37 +00:00
|
|
|
def get_local_libraries(self) -> set:
|
2023-09-16 08:27:46 +00:00
|
|
|
cmd = "seaf-cli list"
|
2023-09-14 23:22:02 +00:00
|
|
|
_lg.info("Listing local libraries: %s", cmd)
|
2023-09-16 08:27:46 +00:00
|
|
|
out = subprocess.check_output(self.__gen_cmd(cmd))
|
|
|
|
|
out = out.decode().splitlines()[1:] # first line is a header
|
2022-01-30 15:27:37 +00:00
|
|
|
|
|
|
|
|
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
|