#!/usr/bin/env python ''' 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 download: download a library from seafile server sync: synchronize an existing folder with a library in seafile server desync: desynchronize a library with seafile server Usage: init ---- Initialize seafile client. This command create sub-directories `seafile-data` and `seafile` under `parent-dir`. usage: seaf-cli init -d start ----- Start seafile client. This command start `ccnet` and `seaf-daemon`, `ccnet` is the network part of seafile client, `seaf-daemon` manages the files. usage: seaf-cli start stop ---- Stop seafile client. usage: seaf-cli stop list ---- List local libraries clone ----- clone a library from seafile server usage: seaf-cli clone -l -h -d -u -p sync ---- Synchronize a library with an existing folder. usage: seaf-cli sync -l -h -d -u -p desync ------ Desynchronize a library from seafile server usage: seaf-cli desync -d ''' import argparse import os import simplejson as json import subprocess import sys import time import ccnet import seafile DEFAULT_CONF_DIR = "%s/.ccnet" % os.environ['HOME'] seafile_datadir = None seafile_worktree = None # check python version if sys.version_info.major == 3: print 'Python 3 not support yet. Quit now' sys.exit(1) if sys.version_info.minor < 6: print 'Python 2.6 or above is required. Quit now' sys.exit(1) def _check_seafile(): ''' Check ccnet and seafile 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 = os.path.join(d, prog) if os.path.exists(path): return True progs = [ 'ccnet', 'ccnet-init', '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 _config_valid(conf): ''' Check config directory valid ''' if not os.path.exists(conf) or not os.path.isdir(conf): print "%s not exists" % conf return False config_conf = conf + "/ccnet.conf" seafile_ini = conf + "/seafile.ini" if not os.path.exists(config_conf): print "Can not load %s" % config_conf return False if not os.path.exists(seafile_ini): print "Can 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 = os.path.join( os.path.dirname(seafile_datadir), "seafile") break if not seafile_datadir or not seafile_worktree: print "Can not load seafile_datadir and seafile_worktree" return False return True def seaf_init(args): ''' initialize config directorys''' ccnet_conf_dir = DEFAULT_CONF_DIR if args.confdir: ccnet_conf_dir = args.confdir if args.dir: seafile_path = args.dir seafile_path = os.path.abspath(seafile_path) if os.path.exists(ccnet_conf_dir): print "%s has existsed" % ccnet_conf_dir sys.exit(0) import socket node_name = socket.gethostname() cmd = [ "ccnet-init", "-c", ccnet_conf_dir, "-n", node_name ] subprocess.call(cmd) if not os.path.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" fp = open(seafile_ini, 'w') fp.write(seafile_data) fp.close() print "Writen seafile data directory %s to %s" % (seafile_data, seafile_ini) def seaf_start_all(args): ''' start ccnet and seafile daemon ''' seaf_start_ccnet(args) # wait ccnet process time.sleep(1) seaf_start_seafile(args) def seaf_start_ccnet(args): ''' start ccnet daemon ''' conf_dir = DEFAULT_CONF_DIR if args.confdir: conf_dir = args.confdir conf_dir = os.path.abspath(conf_dir) if not _config_valid(conf_dir): print "Config directory invalid" sys.exit(1) print "starting ccnet daemon ..." cmd = [ "ccnet", "--daemon", "-c", conf_dir ] subprocess.call(cmd) print "started ccnet daemon ..." def seaf_start_seafile(args): ''' start seafile daemon ''' conf_dir = DEFAULT_CONF_DIR if args.confdir: conf_dir = args.confdir conf_dir = os.path.abspath(conf_dir) if not _config_valid(conf_dir): print "config directory invalid" sys.exit(1) print "starting seafile daemon ..." cmd = [ "seaf-daemon", "--daemon", "-c", conf_dir, "-d", seafile_datadir, "-w", seafile_worktree ] subprocess.call(cmd) print "started seafile daemon ..." def seaf_list(args): '''List local libraries''' conf_dir = DEFAULT_CONF_DIR if args.confdir: conf_dir = args.confdir conf_dir = os.path.abspath(conf_dir) if not _config_valid(conf_dir): print "Config directory invalid" sys.exit(1) pool = ccnet.ClientPool(conf_dir) ccnet_rpc = ccnet.CcnetRpcClient(pool, req_pool=True) seafile_rpc = seafile.RpcClient(pool, req_pool=True) repos = seafile_rpc.get_repo_list(-1, -1) print "Name\tID\tPath" for repo in repos: print repo.name, repo.id, repo.worktree def seaf_download(args): '''Download a library from seafile server ''' conf_dir = DEFAULT_CONF_DIR if args.confdir: conf_dir = args.confdir conf_dir = os.path.abspath(conf_dir) if not _config_valid(conf_dir): print "config directory invalid" sys.exit(1) repo = args.library if not repo: print "Library id is required" sys.exit(1) url = args.server if not url: print "Seafile server url need to be presented" sys.exit(1) download_dir = seafile_worktree if args.dir: download_dir = os.path.abspath(args.dir) pool = ccnet.ClientPool(conf_dir) ccnet_rpc = ccnet.CcnetRpcClient(pool, req_pool=True) seafile_rpc = seafile.RpcClient(pool, req_pool=True) username = args.name if not username: username = raw_input("Enter username: ") password = args.password if not password: import getpass password = getpass.getpass("Enter password for user %s " % username) # curl -d 'username=XXX&password=YYY' http://127.0.0.1:8000/api2/auth-token cmd = [ "curl", "-k", "-d", "username=%s&password=%s" % (username, password), "%s/api2/auth-token/" % url ] token_json = subprocess.check_output(cmd) tmp = json.loads(token_json) token = tmp['token'] cmd = "curl -k -H 'Authorization: Token %s' -H 'Accept: application/json; indent=4' %s/api2/repos/%s/download-info/" % (token, url, repo) repo_info = os.popen(cmd).read() # print repo_info tmp = json.loads(repo_info) encrypted = tmp['encrypted'] clone_token = tmp['token'] relay_id = tmp['relay_id'] relay_addr = tmp['relay_addr'] relay_port = tmp['relay_port'] email = tmp['email'] repo_name = tmp['repo_name'] print "starting to download ..." print "Library %s will be downloaded to %s" % (repo, download_dir) seafile_rpc.download(repo, relay_id, repo_name.encode('utf-8'), download_dir.encode('utf-8'), clone_token, None, None, relay_addr, relay_port, email) def seaf_sync(args): ''' synchronize a library from seafile server ''' conf_dir = DEFAULT_CONF_DIR if args.confdir: conf_dir = args.confdir conf_dir = os.path.abspath(conf_dir) if not _config_valid(conf_dir): print "Config directory invalid" sys.exit(1) repo = args.library if not repo: print "Library id is required" sys.exit(1) url = args.server if not url: print "Seafile server url is required" sys.exit(1) folder = args.folder if not folder: print "The local directory is required" folder = os.path.abspath(folder) if not os.path.exists(folder): print "The local directory does not exists" pool = ccnet.ClientPool(conf_dir) ccnet_rpc = ccnet.CcnetRpcClient(pool, req_pool=True) seafile_rpc = seafile.RpcClient(pool, req_pool=True) username = args.name if not username: username = raw_input("Enter username: ") password = args.password if not password: import getpass password = getpass.getpass("Enter password for user %s " % username) cmd = [ "curl", "-k", "-d", "username=%s&password=%s" % (username, password), "%s/api2/auth-token/" % url ] token_json = subprocess.check_output(cmd) tmp = json.loads(token_json) token = tmp['token'] cmd = "curl -k -H 'Authorization: Token %s' -H 'Accept: application/json; indent=4' %s/api2/repos/%s/download-info/" % (token, url, repo) repo_info = os.popen(cmd).read() # print repo_info tmp = json.loads(repo_info) encrypted = tmp['encrypted'] clone_token = tmp['token'] relay_id = tmp['relay_id'] relay_addr = tmp['relay_addr'] relay_port = tmp['relay_port'] email = tmp['email'] repo_name = tmp['repo_name'] print "starting to download ..." seafile_rpc.clone(repo, relay_id, repo_name.encode('utf-8'), folder.encode('utf-8'), clone_token, None, None, relay_addr, relay_port, email) def seaf_desync(args): '''Desynchronize a library from seafile server''' conf_dir = DEFAULT_CONF_DIR if args.confdir: conf_dir = args.confdir conf_dir = os.path.abspath(conf_dir) if not _config_valid(conf_dir): print "config directory invalid" sys.exit(1) repo_path = args.folder if not repo_path: print "Must specify the local path of the library" sys.exit(1) repo_path = os.path.abspath(repo_path) pool = ccnet.ClientPool(conf_dir) ccnet_rpc = ccnet.CcnetRpcClient(pool, req_pool=True) seafile_rpc = seafile.RpcClient(pool, req_pool=True) repos = seafile_rpc.get_repo_list(-1, -1) repo = None for r in repos: if r.worktree == repo_path: repo = r break if repo: print "Desynchronize %s" % repo.name seafile_rpc.remove_repo(repo.id) else: print "%s is not a library" % args.folder def seaf_status(args): '''Show status''' conf_dir = DEFAULT_CONF_DIR if args.confdir: conf_dir = args.confdir conf_dir = os.path.abspath(conf_dir) if not _config_valid(conf_dir): print "config directory invalid" sys.exit(1) pool = ccnet.ClientPool(conf_dir) ccnet_rpc = ccnet.CcnetRpcClient(pool, req_pool=True) seafile_rpc = seafile.RpcClient(pool, req_pool=True) tasks = seafile_rpc.get_clone_tasks() print "# Name\tStatus\tProgress" for task in tasks: if task.state == "fetch": tx_task = seafile_rpc.find_transfer_task(task.repo_id) print "%s\t%s\t%d/%d, %.1fKB/s" % (task.repo_name, "downloading", tx_task.block_done, tx_task.block_total, tx_task.rate/1024.0) elif task.state == "checkout": checkout_task = seafile_rpc.get_checkout_task(task.repo_id) print "%s\t%s\t%d/%d" % (task.repo_name, "checkout", checkout_task.finished_files, checkout_task.total_files) elif task.state == "error": tx_task = seafile_rpc.find_transfer_task(task.repo_id) print "%s\t%s\t%s" % (task.repo_name, "error", tx_task.error_str) else: print "%s\t%s" % (task.repo_name, "unknown") # show repo status print "" print "# Name\tStatus" repos = seafile_rpc.get_repo_list(-1, -1) for repo in repos: relay = ccnet_rpc.get_peer(repo.props.relay_id) t = seafile_rpc.get_repo_sync_task(repo.id) if not relay.is_ready: print "%s\t%s" % (repo.name, "connecting server") else: if t.state == "error": print "%s\t%s" % (repo.name, t.error) else: print "%s\t%s" % (repo.name, t.state) def main(): ''' Main entry ''' _check_seafile() parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(title='subcommands', description='') # 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) 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 ccnet and seafile daemon') parser_start.set_defaults(func=seaf_start_all) parser_start.add_argument('-c', '--confdir', help='the config directory', type=str) parser_start = subparsers.add_parser('start', help='Start ccnet and seafile daemon') parser_start.set_defaults(func=seaf_start_all) parser_start.add_argument('-c', '--confdir', help='the config directory', type=str) # 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) # 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) # 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) 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('-n', '--name', help='username', type=str) parser_download.add_argument('-p', '--password', help='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) 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('-n', '--name', help='username', type=str) parser_sync.add_argument('-p', '--password', help='password', type=str) parser_sync.add_argument('-d', '--folder', help='the existing local folder', 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) parser_desync.add_argument('-d', '--folder', help='the local folder', type=str) args = parser.parse_args() args.func(args) if __name__ == '__main__': main()