mirror of
https://github.com/haiwen/seafile.git
synced 2025-01-07 03:17:13 +08:00
71b85d5785
Co-authored-by: 杨赫然 <heran.yang@seafile.com>
1054 lines
35 KiB
Python
Executable File
1054 lines
35 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#-*- coding:utf-8 -*-
|
|
# pylint: disable=E1121
|
|
|
|
'''
|
|
|
|
seaf-cli is command line interface for seafile client.
|
|
|
|
Subcommands:
|
|
|
|
init: create config files for seafile client
|
|
start: start and run seafile client as daemon
|
|
stop: stop seafile client
|
|
list: list local libraries
|
|
status: show syncing status
|
|
download: download a library from seafile server
|
|
(using libary id)
|
|
download-by-name: download a library from seafile server
|
|
(using library name)
|
|
sync: synchronize an existing folder with a library in
|
|
seafile server
|
|
desync: desynchronize a library with seafile server
|
|
create: create a new library
|
|
|
|
|
|
Detail
|
|
======
|
|
|
|
Seafile client stores all its configure information in a config dir. The default location is `~/.ccnet`. All the commands below accept an option `-c <config-dir>`.
|
|
|
|
init
|
|
----
|
|
Initialize seafile client. This command initializes the config dir. It also creates sub-directories `seafile-data` and `seafile` under `parent-dir`. `seafile-data` is used to store internal data, while `seafile` is used as the default location put downloaded libraries.
|
|
|
|
seaf-cli init [-c <config-dir>] -d <parent-dir>
|
|
|
|
start
|
|
-----
|
|
Start seafile client. This command starts `seaf-daemon`, which manages all the files.
|
|
|
|
seaf-cli start [-c <config-dir>]
|
|
|
|
stop
|
|
----
|
|
Stop seafile client.
|
|
|
|
seaf-cli stop [-c <config-dir>]
|
|
|
|
|
|
Download by id
|
|
--------
|
|
Download a library from seafile server (using library id)
|
|
|
|
seaf-cli download -l <library-id> -s <seahub-server-url> -d <parent-directory> -u <username> -p <password> [-a <2fa-code>]
|
|
|
|
|
|
Download by name
|
|
--------
|
|
Download a library from seafile server (using library name)
|
|
|
|
seaf-cli download -L <library-name> -s <seahub-server-url> -d <parent-directory> -u <username> -p <password> [-a <2fa-code>]
|
|
|
|
|
|
sync
|
|
----
|
|
Synchronize a library with an existing folder.
|
|
|
|
seaf-cli sync -l <library-id> -s <seahub-server-url> -d <existing-folder> -u <username> -p <password> [-a <2fa-code>]
|
|
|
|
desync
|
|
------
|
|
Desynchronize a library from seafile server
|
|
|
|
seaf-cli desync -d <existing-folder>
|
|
|
|
create
|
|
------
|
|
Create a new library
|
|
|
|
seaf-cli create -s <seahub-server-url> -n <library-name> -u <username> -p <password> [-a <2fa-code>] -t <description> [-e <library-password>]
|
|
|
|
'''
|
|
import argparse
|
|
import os
|
|
import json
|
|
import subprocess
|
|
import re
|
|
import sys
|
|
import time
|
|
import getpass
|
|
import random
|
|
import urllib.request, urllib.parse, urllib.error
|
|
import urllib.request, urllib.error, urllib.parse
|
|
from urllib.parse import urlparse
|
|
|
|
from os.path import abspath, dirname, exists, isdir, join
|
|
|
|
import seafile
|
|
|
|
if 'HOME' in os.environ:
|
|
DEFAULT_CONF_DIR = "%s/.ccnet" % os.environ['HOME']
|
|
DEFAULT_USER_CONF_DIR = "%s/.seafile.conf" % os.environ['HOME']
|
|
else:
|
|
DEFAULT_CONF_DIR = None
|
|
DEFAULT_USER_CONF_DIR = None
|
|
|
|
seafile_datadir = None
|
|
seafile_worktree = None
|
|
|
|
|
|
def _check_seafile():
|
|
''' Check seafile daemon have been installed '''
|
|
|
|
dirs = os.environ['PATH'].split(':')
|
|
def exist_in_path(prog):
|
|
''' Check whether 'prog' exists in system path '''
|
|
for d in dirs:
|
|
if d == '':
|
|
continue
|
|
path = join(d, prog)
|
|
if exists(path):
|
|
return True
|
|
|
|
progs = ['seaf-daemon']
|
|
|
|
for prog in progs:
|
|
if not exist_in_path(prog):
|
|
print("%s not found in PATH. Have you installed seafile?" % prog)
|
|
sys.exit(1)
|
|
|
|
def _check_daemon_running():
|
|
import fcntl
|
|
pidfile = os.path.join(seafile_datadir, 'seaf-daemon.pid')
|
|
with open(pidfile, 'w') as f:
|
|
try:
|
|
fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
fcntl.flock(f, fcntl.LOCK_UN)
|
|
except:
|
|
print("The seafile data directory %s is already used by another Seafile client instance. Please use another seafile data directory."%seafile_datadir)
|
|
sys.exit(1)
|
|
|
|
def get_rpc_client(confdir):
|
|
return seafile.RpcClient(join(seafile_datadir, 'seafile.sock'))
|
|
|
|
def _config_valid(conf):
|
|
''' Check config directory valid '''
|
|
|
|
if not exists(conf) or not isdir(conf):
|
|
print("%s not exists" % conf)
|
|
return False
|
|
|
|
seafile_ini = conf + "/seafile.ini"
|
|
if not exists(seafile_ini):
|
|
print("Could not load %s" % seafile_ini)
|
|
return False
|
|
|
|
with open(seafile_ini) as f:
|
|
for line in f:
|
|
global seafile_datadir, seafile_worktree
|
|
seafile_datadir = line.strip()
|
|
seafile_worktree = join(
|
|
dirname(seafile_datadir), "seafile")
|
|
break
|
|
|
|
if not seafile_datadir or not seafile_worktree:
|
|
print("Could not load seafile_datadir and seafile_worktree")
|
|
return False
|
|
return True
|
|
|
|
|
|
def _conf_dir(args):
|
|
''' Determine and return the value of conf_dir '''
|
|
conf_dir = DEFAULT_CONF_DIR
|
|
if args.confdir:
|
|
conf_dir = args.confdir
|
|
conf_dir = abspath(conf_dir)
|
|
|
|
if not _config_valid(conf_dir):
|
|
print("Invalid config directory")
|
|
sys.exit(1)
|
|
else:
|
|
get_device_id(conf_dir)
|
|
return conf_dir
|
|
|
|
def _user_config_valid(conf):
|
|
if exists(conf):
|
|
return True
|
|
return False
|
|
|
|
def _parse_user_config(conf):
|
|
try:
|
|
from configparser import ConfigParser
|
|
from configparser import NoOptionError
|
|
except ImportError:
|
|
from ConfigParser import ConfigParser
|
|
from ConfigParser import NoOptionError
|
|
|
|
cfg = ConfigParser()
|
|
cfg.read(conf)
|
|
if len(cfg.sections()) < 1 or cfg.sections()[0] != 'account':
|
|
return None, None
|
|
try:
|
|
server = cfg.get('account', 'server')
|
|
user = cfg.get('account', 'user')
|
|
return server,user
|
|
except NoOptionError:
|
|
return None, None
|
|
|
|
def run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False):
|
|
'''Run a program and wait it to finish, and return its exit code. The
|
|
standard output of this program is supressed.
|
|
|
|
'''
|
|
with open(os.devnull, 'w') as devnull:
|
|
if suppress_stdout:
|
|
stdout = devnull
|
|
else:
|
|
stdout = sys.stdout
|
|
|
|
if suppress_stderr:
|
|
stderr = devnull
|
|
else:
|
|
stderr = sys.stderr
|
|
|
|
proc = subprocess.Popen(argv,
|
|
cwd=cwd,
|
|
stdout=stdout,
|
|
stderr=stderr,
|
|
env=env)
|
|
return proc.wait()
|
|
|
|
def get_env():
|
|
env = dict(os.environ)
|
|
ld_library_path = os.environ.get('SEAFILE_LD_LIBRARY_PATH', '')
|
|
if ld_library_path:
|
|
env['LD_LIBRARY_PATH'] = ld_library_path
|
|
|
|
return env
|
|
|
|
def urlopen(url, data=None, headers=None):
|
|
if data:
|
|
data = urllib.parse.urlencode(data).encode('utf-8')
|
|
headers = headers or {}
|
|
req = urllib.request.Request(url, data=data, headers=headers)
|
|
resp = urllib.request.urlopen(req)
|
|
|
|
return resp.read()
|
|
|
|
SEAF_CLI_VERSION = ""
|
|
|
|
def randstring(size):
|
|
random.seed(time.time())
|
|
s = ''
|
|
while len(s) < size:
|
|
s += '%x' % random.randint(0, 255)
|
|
return s[:size]
|
|
|
|
device_id = None
|
|
def get_device_id(conf_dir):
|
|
global device_id
|
|
if device_id:
|
|
return device_id
|
|
|
|
idfile = join(seafile_datadir, 'id')
|
|
ccnet_conf = join(conf_dir, 'ccnet.conf')
|
|
if exists(idfile):
|
|
with open(idfile, 'r') as fp:
|
|
device_id = fp.read().strip()
|
|
return device_id
|
|
|
|
# Id file doesn't exist. We either migrate it from ccnet.conf ID
|
|
# (for existing data), or create it.
|
|
|
|
if exists(ccnet_conf):
|
|
# migrate from existing ccnet.conf ID
|
|
with open(ccnet_conf, 'r') as fp:
|
|
for line in fp:
|
|
m = re.search('ID = (.*)', line)
|
|
if m:
|
|
device_id = m.group(1)
|
|
print('Migrating device id from ccnet conf')
|
|
break
|
|
if not device_id:
|
|
# create a new id
|
|
print('New device id created')
|
|
device_id = randstring(40)
|
|
with open(idfile, 'w') as fp:
|
|
fp.write(device_id)
|
|
return device_id
|
|
|
|
def get_token(url, username, password, tfa, conf_dir):
|
|
platform = 'linux'
|
|
device_id = get_device_id(conf_dir)
|
|
device_name = 'terminal-' + os.uname()[1][:25]
|
|
client_version = SEAF_CLI_VERSION
|
|
platform_version = ''
|
|
data = {
|
|
'username': username,
|
|
'password': password,
|
|
'platform': platform,
|
|
'device_id': device_id,
|
|
'device_name': device_name,
|
|
'client_version': client_version,
|
|
'platform_version': platform_version,
|
|
}
|
|
if tfa:
|
|
headers = {
|
|
'X-SEAFILE-OTP': tfa,
|
|
}
|
|
else:
|
|
headers = None
|
|
token_json = urlopen("%s/api2/auth-token/" % url, data=data, headers=headers)
|
|
tmp = json.loads(token_json.decode('utf8'))
|
|
token = tmp['token']
|
|
return token
|
|
|
|
def get_repo_download_info(url, token):
|
|
headers = { 'Authorization': 'Token %s' % token }
|
|
repo_info = urlopen(url, headers=headers)
|
|
return json.loads(repo_info.decode('utf8'))
|
|
|
|
def get_value_from_user_config(args, section, key):
|
|
user_config = args.C
|
|
if not user_config:
|
|
user_config = DEFAULT_USER_CONF_DIR
|
|
else:
|
|
user_config = abspath(user_config)
|
|
if _user_config_valid(user_config):
|
|
try:
|
|
from configparser import ConfigParser
|
|
from configparser import NoOptionError
|
|
except ImportError:
|
|
from ConfigParser import ConfigParser
|
|
from ConfigParser import NoOptionError
|
|
|
|
cfg = ConfigParser()
|
|
cfg.read(user_config)
|
|
if len(cfg.sections()) < 1 or cfg.sections()[0] != 'account':
|
|
return None
|
|
try:
|
|
token = cfg.get(section, key)
|
|
return token
|
|
except NoOptionError:
|
|
return None
|
|
else:
|
|
return None
|
|
|
|
def get_token_from_args_or_config(args):
|
|
if args.token:
|
|
return args.token
|
|
else:
|
|
return get_value_from_user_config(args, 'account', 'token')
|
|
|
|
def seaf_init(args):
|
|
''' Initialize config directories'''
|
|
|
|
ccnet_conf_dir = DEFAULT_CONF_DIR
|
|
if args.confdir:
|
|
ccnet_conf_dir = args.confdir
|
|
if args.dir:
|
|
seafile_path = args.dir
|
|
else:
|
|
print("Must specify the parent path for put seafile-data")
|
|
sys.exit(0)
|
|
seafile_path = abspath(seafile_path)
|
|
|
|
if exists(ccnet_conf_dir):
|
|
print("%s already exists" % ccnet_conf_dir)
|
|
sys.exit(0)
|
|
|
|
os.mkdir(ccnet_conf_dir)
|
|
logsdir = join(ccnet_conf_dir, 'logs')
|
|
if not exists(logsdir):
|
|
os.mkdir(logsdir)
|
|
|
|
if not exists(seafile_path):
|
|
print("%s not exists" % seafile_path)
|
|
sys.exit(0)
|
|
seafile_ini = ccnet_conf_dir + "/seafile.ini"
|
|
seafile_data = seafile_path + "/seafile-data"
|
|
with open(seafile_ini, 'w') as fp:
|
|
fp.write(seafile_data)
|
|
if not exists(seafile_data):
|
|
os.mkdir(seafile_data)
|
|
print("Writen seafile data directory %s to %s" % (seafile_data, seafile_ini))
|
|
|
|
|
|
def seaf_start_all(args):
|
|
''' Start seafile daemon '''
|
|
seaf_start_seafile(args)
|
|
|
|
def seaf_start_seafile(args):
|
|
''' start seafile daemon '''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
_check_daemon_running()
|
|
|
|
print("Starting seafile daemon ...")
|
|
|
|
cmd = [ "seaf-daemon", "--daemon", "-c", conf_dir, "-d", seafile_datadir,
|
|
"-w", seafile_worktree ]
|
|
if run_argv(cmd, env=get_env()) != 0:
|
|
print('Failed to start seafile daemon')
|
|
sys.exit(1)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
i = 0
|
|
while True:
|
|
try:
|
|
seafile_rpc.seafile_set_config_int("delete_confirm_threshold", 1000000)
|
|
except Exception as e:
|
|
if i > 3:
|
|
break;
|
|
i += 1
|
|
time.sleep(1)
|
|
continue
|
|
else:
|
|
break
|
|
|
|
print("Started: seafile daemon ...")
|
|
|
|
def seaf_stop(args):
|
|
'''Stop seafile daemon '''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
try:
|
|
# TODO: add shutdown rpc in seaf-daemon
|
|
seafile_rpc.shutdown()
|
|
except:
|
|
# ignore NetworkError("Failed to read from socket")
|
|
pass
|
|
|
|
|
|
def seaf_list(args):
|
|
'''List local libraries'''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
repos = seafile_rpc.get_repo_list(-1, -1)
|
|
|
|
if args.json:
|
|
repo_list = []
|
|
for repo in repos:
|
|
repo_dict = {'name':repo.name, 'id':repo.id, 'path':repo.worktree}
|
|
repo_list.append(repo_dict)
|
|
json_str = json.dumps(repo_list, ensure_ascii=False)
|
|
print(json_str)
|
|
else:
|
|
print("Name\tID\tPath")
|
|
for repo in repos:
|
|
print(repo.name, repo.id, repo.worktree)
|
|
|
|
|
|
def seaf_list_remote(args):
|
|
'''List remote libraries'''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
server_from_config = get_value_from_user_config(args, 'account', 'server')
|
|
user_from_config = get_value_from_user_config(args, 'account', 'user')
|
|
|
|
url = args.server
|
|
if not url and server_from_config:
|
|
url = server_from_config
|
|
if not url:
|
|
print("Seafile server url need to be presented")
|
|
sys.exit(1)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
|
|
username = args.username
|
|
if not username and user_from_config:
|
|
username = user_from_config
|
|
if not username:
|
|
username = input("Enter username: ")
|
|
|
|
token = get_token_from_args_or_config(args)
|
|
password = None
|
|
tfa = args.tfa
|
|
if not token:
|
|
password = args.password
|
|
if not password:
|
|
password = getpass.getpass("Enter password for user %s : " % username)
|
|
# curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token
|
|
token = get_token(url, username, password, tfa, conf_dir)
|
|
|
|
repos = get_repo_download_info("%s/api2/repos/" % (url), token)
|
|
|
|
printed = {}
|
|
if args.json:
|
|
repo_list = []
|
|
for repo in repos:
|
|
if repo['id'] in printed:
|
|
continue
|
|
printed[repo['id']] = repo['id']
|
|
repo_dict = {'name': repo['name'], 'id':repo['id']}
|
|
repo_list.append(repo_dict)
|
|
json_str = json.dumps(repo_list, ensure_ascii=False)
|
|
print(json_str)
|
|
else:
|
|
print("Name\tID")
|
|
for repo in repos:
|
|
if repo['id'] in printed:
|
|
continue
|
|
|
|
printed[repo['id']] = repo['id']
|
|
print(repo['name'], repo['id'])
|
|
|
|
|
|
def get_base_url(url):
|
|
parse_result = urlparse(url)
|
|
scheme = parse_result.scheme
|
|
netloc = parse_result.netloc
|
|
|
|
if scheme and netloc:
|
|
return '%s://%s' % (scheme, netloc)
|
|
|
|
return None
|
|
|
|
def seaf_download(args):
|
|
'''Download a library from seafile server '''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
repo = args.library
|
|
if not repo:
|
|
print("Library id is required")
|
|
sys.exit(1)
|
|
|
|
server_from_config = get_value_from_user_config(args, 'account', 'server')
|
|
user_from_config = get_value_from_user_config(args, 'account', 'user')
|
|
|
|
url = args.server
|
|
if not url and server_from_config:
|
|
url = server_from_config
|
|
if not url:
|
|
print("Seafile server url need to be presented")
|
|
sys.exit(1)
|
|
|
|
download_dir = seafile_worktree
|
|
if args.dir:
|
|
download_dir = abspath(args.dir)
|
|
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
|
|
username = args.username
|
|
if not username and user_from_config:
|
|
username = user_from_config
|
|
if not username:
|
|
username = input("Enter username: ")
|
|
token = get_token_from_args_or_config(args)
|
|
password = None
|
|
tfa = args.tfa
|
|
if not token:
|
|
password = args.password
|
|
if not password:
|
|
password = getpass.getpass("Enter password for user %s : " % username)
|
|
# curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token
|
|
token = get_token(url, username, password, tfa, conf_dir)
|
|
|
|
tmp = get_repo_download_info("%s/api2/repos/%s/download-info/" % (url, repo), token)
|
|
|
|
encrypted = tmp['encrypted']
|
|
magic = tmp.get('magic', None)
|
|
enc_version = tmp.get('enc_version', None)
|
|
random_key = tmp.get('random_key', None)
|
|
|
|
clone_token = tmp['token']
|
|
email = tmp['email']
|
|
repo_name = tmp['repo_name']
|
|
version = tmp.get('repo_version', 0)
|
|
repo_salt = tmp.get('salt', None)
|
|
permission = tmp.get('permission', None)
|
|
|
|
is_readonly = 0
|
|
if permission == 'r':
|
|
is_readonly = 1
|
|
|
|
more_info = None
|
|
more_info_dict = {}
|
|
base_url = get_base_url(url)
|
|
if base_url:
|
|
more_info_dict.update({'server_url': base_url})
|
|
if repo_salt:
|
|
more_info_dict.update({'repo_salt': repo_salt})
|
|
more_info_dict.update({'is_readonly': is_readonly})
|
|
more_info = json.dumps(more_info_dict)
|
|
|
|
print("Starting to download ...")
|
|
print("Library %s will be downloaded to %s" % (repo, download_dir))
|
|
if encrypted == 1:
|
|
repo_passwd = args.libpasswd if args.libpasswd else getpass.getpass("Enter password for the library: ")
|
|
else:
|
|
repo_passwd = None
|
|
|
|
seafile_rpc.download(repo,
|
|
version,
|
|
repo_name,
|
|
download_dir,
|
|
clone_token,
|
|
repo_passwd, magic,
|
|
email, random_key, enc_version, more_info)
|
|
|
|
|
|
def seaf_download_by_name(args):
|
|
'''Download a library defined by name from seafile server'''
|
|
id = None
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
libraryname = args.libraryname
|
|
if not libraryname:
|
|
print("Library name is required")
|
|
sys.exit(1)
|
|
|
|
server_from_config = get_value_from_user_config(args, 'account', 'server')
|
|
user_from_config = get_value_from_user_config(args, 'account', 'user')
|
|
|
|
url = args.server
|
|
if not url and server_from_config:
|
|
url = server_from_config
|
|
if not url:
|
|
print("Seafile server url need to be presented")
|
|
sys.exit(1)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
|
|
username = args.username
|
|
if not username and user_from_config:
|
|
username = user_from_config;
|
|
if not username:
|
|
username = input("Enter username: ")
|
|
args.username = username
|
|
|
|
token = get_token_from_args_or_config(args)
|
|
password = None
|
|
tfa = args.tfa
|
|
if not token:
|
|
password = args.password
|
|
if not password:
|
|
password = getpass.getpass("Enter password for user %s : " % username)
|
|
args.password = password
|
|
# curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token
|
|
token = get_token(url, username, password, tfa, conf_dir)
|
|
|
|
tmp = get_repo_download_info("%s/api2/repos/" % (url), token)
|
|
|
|
for i in tmp:
|
|
if libraryname == i['name']:
|
|
id = i['id']
|
|
|
|
if not id:
|
|
print("Defined library name not found")
|
|
sys.exit(1)
|
|
|
|
args.library = id
|
|
seaf_download(args)
|
|
|
|
|
|
def seaf_sync(args):
|
|
''' synchronize a library from seafile server '''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
repo = args.library
|
|
if not repo:
|
|
print("Library id is required")
|
|
sys.exit(1)
|
|
|
|
server_from_config = get_value_from_user_config(args, 'account', 'server')
|
|
user_from_config = get_value_from_user_config(args, 'account', 'user')
|
|
|
|
url = args.server
|
|
if not url and server_from_config:
|
|
url = server_from_config
|
|
if not url:
|
|
print("Seafile server url is required")
|
|
sys.exit(1)
|
|
|
|
folder = args.folder
|
|
if not folder:
|
|
print("The local directory is required")
|
|
sys.exit(1)
|
|
|
|
folder = abspath(folder)
|
|
if not exists(folder):
|
|
print("The local directory does not exists")
|
|
sys.exit(1)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
|
|
username = args.username
|
|
if not username and user_from_config:
|
|
username = user_from_config;
|
|
if not username:
|
|
username = input("Enter username: ")
|
|
|
|
password = None
|
|
token = get_token_from_args_or_config(args)
|
|
tfa = args.tfa
|
|
if not token:
|
|
password = args.password
|
|
if not password:
|
|
password = getpass.getpass("Enter password for user %s : " % username)
|
|
token = get_token(url, username, password, tfa, conf_dir)
|
|
|
|
tmp = get_repo_download_info("%s/api2/repos/%s/download-info/" % (url, repo), token)
|
|
|
|
encrypted = tmp['encrypted']
|
|
magic = tmp.get('magic', None)
|
|
enc_version = tmp.get('enc_version', None)
|
|
random_key = tmp.get('random_key', None)
|
|
|
|
clone_token = tmp['token']
|
|
email = tmp['email']
|
|
repo_name = tmp['repo_name']
|
|
version = tmp.get('repo_version', 0)
|
|
repo_salt = tmp.get('salt', None)
|
|
permission = tmp.get('permission', None)
|
|
|
|
is_readonly = 0
|
|
if permission == 'r':
|
|
is_readonly = 1
|
|
|
|
more_info = None
|
|
more_info_dict = {}
|
|
base_url = get_base_url(url)
|
|
if base_url:
|
|
more_info_dict.update({'server_url': base_url})
|
|
if repo_salt:
|
|
more_info_dict.update({'repo_salt': repo_salt})
|
|
more_info_dict.update({'is_readonly': is_readonly})
|
|
more_info = json.dumps(more_info_dict)
|
|
|
|
print("Starting to download ...")
|
|
if encrypted == 1:
|
|
repo_passwd = args.libpasswd if args.libpasswd else getpass.getpass("Enter password for the library: ")
|
|
else:
|
|
repo_passwd = None
|
|
|
|
seafile_rpc.clone(repo,
|
|
version,
|
|
repo_name,
|
|
folder,
|
|
clone_token,
|
|
repo_passwd, magic,
|
|
email, random_key, enc_version, more_info)
|
|
|
|
|
|
def seaf_desync(args):
|
|
'''Desynchronize a library from seafile server'''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
repo_path = args.folder
|
|
if not repo_path:
|
|
print("Must specify the local path of the library")
|
|
sys.exit(1)
|
|
repo_path = abspath(repo_path)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
|
|
repos = seafile_rpc.get_repo_list(-1, -1)
|
|
repo = None
|
|
for r in repos:
|
|
if r.worktree == repo_path:
|
|
repo = r
|
|
break
|
|
|
|
if repo is not None:
|
|
if sys.version_info[0] == 2:
|
|
repo.name = repo.name.encode('utf8')
|
|
print("Desynchronize %s" % repo.name)
|
|
seafile_rpc.remove_repo(repo.id)
|
|
else:
|
|
print("%s is not a library" % args.folder)
|
|
|
|
|
|
def seaf_config(args):
|
|
'''Configure the seafile client'''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
config_key = args.key
|
|
if not config_key:
|
|
print("Must specify configuration key")
|
|
sys.exit(1)
|
|
|
|
config_value = args.value
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
|
|
if config_value:
|
|
# set configuration key
|
|
seafile_rpc.seafile_set_config(config_key, config_value)
|
|
else:
|
|
# print configuration key
|
|
val = seafile_rpc.seafile_get_config(config_key)
|
|
print("%s = %s" % (config_key, val))
|
|
|
|
|
|
def seaf_status(args):
|
|
'''Show status'''
|
|
|
|
conf_dir = _conf_dir(args)
|
|
|
|
seafile_rpc = get_rpc_client(conf_dir)
|
|
|
|
tasks = seafile_rpc.get_clone_tasks()
|
|
print('# {:<50s}\t{:<20s}\t{:<20s}'.format('Name', 'Status', 'Progress'))
|
|
for task in tasks:
|
|
if sys.version_info[0] == 2:
|
|
task.repo_name = task.repo_name.encode('utf8')
|
|
if task.state == "fetch":
|
|
tx_task = seafile_rpc.find_transfer_task(task.repo_id)
|
|
try:
|
|
print('{:<50s}\t{:<20s}\t{:<.1f}%, {:<.1f}KB/s'.format(task.repo_name, 'downloading',
|
|
tx_task.block_done / tx_task.block_total * 100,
|
|
tx_task.rate / 1024.0))
|
|
except ZeroDivisionError: pass
|
|
elif task.state == "error":
|
|
err = seafile_rpc.sync_error_id_to_str(task.error)
|
|
print('{:<50s}\t{:<20s}\t{:<20s}'.format(task.repo_name, 'error', err))
|
|
elif task.state == 'done':
|
|
# will be shown in repo status
|
|
pass
|
|
else:
|
|
print('{:<50s}\t{:<20s}'.format(task.repo_name, task.state))
|
|
|
|
repos = seafile_rpc.get_repo_list(-1, -1)
|
|
for repo in repos:
|
|
auto_sync_enabled = seafile_rpc.is_auto_sync_enabled()
|
|
if not auto_sync_enabled or not repo.auto_sync:
|
|
print('{:<50s}\t{:<20s}'.format(repo.name, 'auto sync disabled'))
|
|
continue
|
|
|
|
task = seafile_rpc.get_repo_sync_task(repo.id)
|
|
if task is None:
|
|
print('{:<50s}\t{:<20s}'.format(repo.name, 'waiting for sync'))
|
|
elif task.state == 'uploading':
|
|
tx_task = seafile_rpc.find_transfer_task(repo.id)
|
|
try:
|
|
print('{:<50s}\t{:<20s}\t{:<.1f}%, {:<.1f}KB/s'.format(repo.name, 'uploading',
|
|
tx_task.block_done / tx_task.block_total * 100,
|
|
tx_task.rate / 1024.0))
|
|
except ZeroDivisionError: pass
|
|
elif task.state == 'downloading':
|
|
tx_task = seafile_rpc.find_transfer_task(repo.id)
|
|
try:
|
|
if tx_task.rt_state == 'data':
|
|
print('{:<50s}\t{:<20s}\t{:<.1f}%, {:<.1f}KB/s'.format(repo.name, 'downloading files',
|
|
tx_task.block_done / tx_task.block_total * 100,
|
|
tx_task.rate / 1024.0))
|
|
if tx_task.rt_state == 'fs':
|
|
print('{:<50s}\t{:<20s}\t{:<.1f}%'.format(repo.name, 'downloading file list',
|
|
tx_task.fs_objects_done / tx_task.fs_objects_total * 100))
|
|
except ZeroDivisionError: pass
|
|
elif task.state == 'error':
|
|
err = seafile_rpc.sync_error_id_to_str(task.error)
|
|
print('{:<50s}\t{:<20s}\t{:<20s}'.format(repo.name, 'error', err))
|
|
else:
|
|
print('{:<50s}\t{:<20s}'.format(repo.name, task.state))
|
|
|
|
def create_repo(url, token, args):
|
|
headers = { 'Authorization': 'Token %s' % token }
|
|
data = {
|
|
'name': args.name,
|
|
'desc': args.desc,
|
|
}
|
|
if args.libpasswd:
|
|
data['passwd'] = args.libpasswd
|
|
repo_info_json = urlopen(url, data=data, headers=headers)
|
|
repo_info = json.loads(repo_info_json.decode('utf8'))
|
|
return repo_info['repo_id']
|
|
|
|
def seaf_create(args):
|
|
'''Create a library'''
|
|
conf_dir = _conf_dir(args)
|
|
|
|
server_from_config = get_value_from_user_config(args, 'account', 'server')
|
|
user_from_config = get_value_from_user_config(args, 'account', 'user')
|
|
|
|
# check username and password
|
|
username = args.username
|
|
if not username and user_from_config:
|
|
username = user_from_config;
|
|
if not username:
|
|
username = input("Enter username: ")
|
|
|
|
url = args.server
|
|
if not url and server_from_config:
|
|
url = server_from_config
|
|
if not url:
|
|
print("Seafile server url need to be presented")
|
|
sys.exit(1)
|
|
|
|
token = get_token_from_args_or_config(args)
|
|
password = None
|
|
tfa = args.tfa
|
|
if not token:
|
|
password = args.password
|
|
if not password:
|
|
password = getpass.getpass("Enter password for user %s " % username)
|
|
# curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token
|
|
token = get_token(url, username, password, tfa, conf_dir)
|
|
|
|
repo_id = create_repo("%s/api2/repos/" % (url), token, args)
|
|
print(repo_id)
|
|
|
|
|
|
def main():
|
|
''' Main entry '''
|
|
|
|
_check_seafile()
|
|
|
|
parser = argparse.ArgumentParser()
|
|
subparsers = parser.add_subparsers(title='subcommands', description='')
|
|
|
|
confdir_required = DEFAULT_CONF_DIR is None
|
|
|
|
# init
|
|
parser_init = subparsers.add_parser('init', help='Initialize config directory')
|
|
parser_init.set_defaults(func=seaf_init)
|
|
parser_init.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_init.add_argument('-d', '--dir', help='the parent directory to put seafile-data', type=str)
|
|
|
|
# start
|
|
parser_start = subparsers.add_parser('start',
|
|
help='Start seafile daemon')
|
|
parser_start.set_defaults(func=seaf_start_all)
|
|
parser_start.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
|
|
# stop
|
|
parser_stop = subparsers.add_parser('stop',
|
|
help='Stop seafile daemon')
|
|
parser_stop.set_defaults(func=seaf_stop)
|
|
parser_stop.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
|
|
# list
|
|
parser_list = subparsers.add_parser('list', help='List local libraries')
|
|
parser_list.set_defaults(func=seaf_list)
|
|
parser_list.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_list.add_argument('--json', help='output json format result', action='store_true')
|
|
|
|
# list-remote
|
|
parser_download = subparsers.add_parser('list-remote', help='List remote libraries')
|
|
parser_download.set_defaults(func=seaf_list_remote)
|
|
parser_download.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_download.add_argument('-C', help='the user config directory', type=str)
|
|
parser_download.add_argument('-s', '--server', help='URL for seafile server', type=str)
|
|
parser_download.add_argument('-u', '--username', help='username', type=str)
|
|
parser_download.add_argument('-p', '--password', help='password', type=str)
|
|
parser_download.add_argument('-T', '--token', help='token', type=str)
|
|
parser_download.add_argument('-a', '--tfa', help='two-factor authentication', type=str)
|
|
parser_download.add_argument('--json', help='output json format result', action='store_true')
|
|
|
|
# status
|
|
parser_status = subparsers.add_parser('status', help='Show syncing status')
|
|
parser_status.set_defaults(func=seaf_status)
|
|
parser_status.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
|
|
# download
|
|
parser_download = subparsers.add_parser('download',
|
|
help='Download a library from seafile server')
|
|
parser_download.set_defaults(func=seaf_download)
|
|
parser_download.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_download.add_argument('-C', help='the user config directory', type=str)
|
|
parser_download.add_argument('-l', '--library', help='library id', type=str)
|
|
parser_download.add_argument('-s', '--server', help='URL for seafile server', type=str)
|
|
parser_download.add_argument('-d', '--dir', help='the directory to put the library', type=str)
|
|
parser_download.add_argument('-u', '--username', help='username', type=str)
|
|
parser_download.add_argument('-p', '--password', help='password', type=str)
|
|
parser_download.add_argument('-T', '--token', help='token', type=str)
|
|
parser_download.add_argument('-a', '--tfa', help='two-factor authentication', type=str)
|
|
parser_download.add_argument('-e', '--libpasswd', help='library password', type=str)
|
|
|
|
# download-by-name
|
|
parser_download = subparsers.add_parser('download-by-name',
|
|
help='Download a library defined by name from seafile server')
|
|
parser_download.set_defaults(func=seaf_download_by_name)
|
|
parser_download.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_download.add_argument('-C', help='the user config directory', type=str)
|
|
parser_download.add_argument('-L', '--libraryname', help='library name', type=str)
|
|
parser_download.add_argument('-s', '--server', help='URL for seafile server', type=str)
|
|
parser_download.add_argument('-d', '--dir', help='the directory to put the library', type=str)
|
|
parser_download.add_argument('-u', '--username', help='username', type=str)
|
|
parser_download.add_argument('-p', '--password', help='password', type=str)
|
|
parser_download.add_argument('-T', '--token', help='token', type=str)
|
|
parser_download.add_argument('-a', '--tfa', help='two-factor authentication', type=str)
|
|
parser_download.add_argument('-e', '--libpasswd', help='library password', type=str)
|
|
|
|
|
|
# sync
|
|
parser_sync = subparsers.add_parser('sync',
|
|
help='Sync a library with an existing foler')
|
|
parser_sync.set_defaults(func=seaf_sync)
|
|
parser_sync.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_sync.add_argument('-C', help='the user config directory', type=str)
|
|
parser_sync.add_argument('-l', '--library', help='library id', type=str)
|
|
parser_sync.add_argument('-s', '--server', help='URL for seafile server', type=str)
|
|
parser_sync.add_argument('-u', '--username', help='username', type=str)
|
|
parser_sync.add_argument('-p', '--password', help='password', type=str)
|
|
parser_sync.add_argument('-T', '--token', help='token', type=str)
|
|
parser_sync.add_argument('-a', '--tfa', help='two-factor authentication', type=str)
|
|
parser_sync.add_argument('-d', '--folder', help='the existing local folder', type=str)
|
|
parser_sync.add_argument('-e', '--libpasswd', help='library password', type=str)
|
|
|
|
# desync
|
|
parser_desync = subparsers.add_parser('desync',
|
|
help='Desync a library with seafile server')
|
|
parser_desync.set_defaults(func=seaf_desync)
|
|
parser_desync.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_desync.add_argument('-d', '--folder', help='the local folder', type=str)
|
|
|
|
# create
|
|
parser_create = subparsers.add_parser('create',
|
|
help='Create a library')
|
|
parser_create.set_defaults(func=seaf_create)
|
|
parser_create.add_argument('-n', '--name', help='library name', type=str)
|
|
parser_create.add_argument('-t', '--desc', help='library description', type=str)
|
|
parser_create.add_argument('-e', '--libpasswd', help='library password', type=str)
|
|
parser_create.add_argument('-s', '--server', help='URL for seafile server', type=str)
|
|
parser_create.add_argument('-u', '--username', help='username', type=str)
|
|
parser_create.add_argument('-p', '--password', help='password', type=str)
|
|
parser_create.add_argument('-T', '--token', help='token', type=str)
|
|
parser_create.add_argument('-a', '--tfa', help='two-factor authentication', type=str)
|
|
parser_create.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_create.add_argument('-C', help='the user config directory', type=str)
|
|
|
|
# config
|
|
parser_config = subparsers.add_parser('config',
|
|
help='Configure seafile client')
|
|
parser_config.set_defaults(func=seaf_config)
|
|
parser_config.add_argument('-c', '--confdir', help='the config directory', type=str, required=confdir_required)
|
|
parser_config.add_argument('-k', '--key', help='configuration key', type=str)
|
|
parser_config.add_argument('-v', '--value', help='configuration value (if provided, key is set to this value)', type=str, required=False)
|
|
|
|
if len(sys.argv) == 1:
|
|
print(parser.format_help())
|
|
return
|
|
|
|
args = parser.parse_args()
|
|
args.func(args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|
|
# print('device id is {}'.format(get_device_id(DEFAULT_CONF_DIR)))
|