1'''
2
3seaf-cli is command line interface for seafile client.
4
5Subcommands:
6
7    init:           create config files for seafile client
8    start:          start and run seafile client as daemon
9    stop:           stop seafile client
10    list:           list local liraries
11    status:         show syncing status
12    download:       download a library from seafile server
13    sync:           synchronize an existing folder with a library in
14                        seafile server
15    desync:         desynchronize a library with seafile server
16    create:         create a new library
17
18
19Detail
20======
21
22Seafile 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>`.
23
24init
25----
26Initialize 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.
27
28    seaf-cli init [-c <config-dir>] -d <parent-dir>
29
30start
31-----
32Start seafile client. This command start `ccnet` and `seaf-daemon`, `ccnet` is the network part of seafile client, `seaf-daemon` manages the files.
33
34    seaf-cli start [-c <config-dir>]
35
36stop
37----
38Stop seafile client.
39
40    seaf-cli stop [-c <config-dir>]
41
42
43Download
44--------
45Download a library from seafile server
46
47    seaf-cli download -l <library-id> -s <seahub-server-url> -d <parent-directory> -u <username> -p <password>
48
49
50sync
51----
52Synchronize a library with an existing folder.
53
54    seaf-cli sync -l <library-id> -s <seahub-server-url> -d <existing-folder> -u <username> -p <password>
55
56desync
57------
58Desynchronize a library from seafile server
59
60    seaf-cli desync -d <existing-folder>
61
62create
63------
64Create a new library
65
66    seaf-cli create -s <seahub-server-url> -n <library-name> -u <username> -p <password> -t <description> [-e <library-password>]
67
68'''
69
70import os
71import json
72import subprocess
73import sys
74import time
75import urllib
76import urllib2
77import httplib
78from urlparse import urlparse
79
80import ccnet
81import seafile
82
83def _check_seafile():
84    ''' Check ccnet and seafile have been installed '''
85
86    sep = ':' if os.name != 'nt' else ';'
87    dirs = os.environ['PATH'].split(sep)
88    def exist_in_path(prog):
89        ''' Check whether 'prog' exists in system path '''
90        for d in dirs:
91            if d == '':
92                continue
93            path = os.path.join(d, prog)
94            if os.path.exists(path):
95                return True
96
97    progs = [ 'ccnet', 'seaf-daemon' ]
98
99    for prog in progs:
100        if os.name == 'nt':
101            prog += '.exe'
102        if not exist_in_path(prog):
103            print "%s not found in PATH. Have you installed seafile?" % prog
104            sys.exit(1)
105
106def run_argv(argv, cwd=None, env=None, suppress_stdout=False, suppress_stderr=False, wait=True):
107    '''Run a program and wait it to finish, and return its exit code. The
108    standard output of this program is supressed.
109
110    '''
111    with open(os.devnull, 'w') as devnull:
112        if suppress_stdout:
113            stdout = devnull
114        else:
115            stdout = sys.stdout
116
117        if suppress_stderr:
118            stderr = devnull
119        else:
120            stderr = sys.stderr
121
122        proc = subprocess.Popen(argv,
123                                cwd=cwd,
124                                stdout=stdout,
125                                stderr=stderr,
126                                env=env)
127        if wait:
128            return proc.wait()
129        return 0
130
131def get_env():
132    env = dict(os.environ)
133    ld_library_path = os.environ.get('SEAFILE_LD_LIBRARY_PATH', '')
134    if ld_library_path:
135        env['LD_LIBRARY_PATH'] = ld_library_path
136
137    return env
138
139class NoRedirect(urllib2.HTTPRedirectHandler):
140    def redirect_request(self, req, fp, code, msg, hdrs, newurl):
141        pass
142
143def urlopen(url, data=None, headers=None, follow_redirect=True):
144    if data:
145        data = urllib.urlencode(data)
146    headers = headers or {}
147    req = urllib2.Request(url, data=data, headers=headers)
148    if follow_redirect:
149        resp = urllib2.urlopen(req)
150    else:
151        try:
152            opener = urllib2.build_opener(NoRedirect())
153            resp = opener.open(req)
154        except urllib2.HTTPError:
155            return None
156    return resp.read()
157
158def get_token(url, username, password):
159    data = {
160        'username': username,
161        'password': password,
162    }
163    token_json = urlopen("%s/api2/auth-token/" % url, data=data)
164    tmp = json.loads(token_json)
165    token = tmp['token']
166    return token
167
168def get_repo_downlod_info(url, token):
169    headers = { 'Authorization': 'Token %s' % token }
170    repo_info = urlopen(url, headers=headers)
171    return json.loads(repo_info)
172
173def seaf_init(conf_dir):
174    ''' initialize config directorys'''
175
176    _check_seafile()
177
178    seafile_ini = os.path.join(conf_dir, "seafile.ini")
179    seafile_data = os.path.join(conf_dir, "seafile-data")
180    fp = open(seafile_ini, 'w')
181    fp.write(seafile_data)
182    fp.close()
183
184    print 'Init ccnet config success.'
185
186def seaf_start_all(conf_dir):
187    ''' start ccnet and seafile daemon '''
188
189    seaf_start_ccnet(conf_dir)
190    # wait ccnet process
191    time.sleep(1)
192    seaf_start_seafile(conf_dir)
193
194    print 'Start ccnet, seafile daemon success.'
195
196def seaf_start_ccnet(conf_dir):
197    ''' start ccnet daemon '''
198
199    cmd = [ "ccnet", "--daemon", "-c", conf_dir ]
200    wait = False if os.name == 'nt' else True
201    if run_argv(cmd, env=get_env(), suppress_stdout=True, wait=wait) != 0:
202        print 'Failed to start ccnet daemon.'
203        sys.exit(1)
204
205def seaf_start_seafile(conf_dir):
206    ''' start seafile daemon '''
207
208    cmd = [ "seaf-daemon", "--daemon", "-c", conf_dir,
209            "-d", os.path.join(conf_dir, 'seafile-data'),
210            "-w", os.path.join(conf_dir, 'seafile') ]
211    wait = False if os.name == 'nt' else True
212    if run_argv(cmd, env=get_env(), suppress_stdout=True, wait=wait) != 0:
213        print 'Failed to start seafile daemon'
214        sys.exit(1)
215
216def seaf_stop(conf_dir):
217    '''stop seafile daemon '''
218
219    pool = ccnet.ClientPool(conf_dir)
220    client = pool.get_client()
221    try:
222        client.send_cmd("shutdown")
223    except:
224        # ignore NetworkError("Failed to read from socket")
225        pass
226
227    print 'Stop ccnet, seafile daemon success.'
228
229def get_base_url(url):
230    parse_result = urlparse(url)
231    scheme = parse_result.scheme
232    netloc = parse_result.netloc
233
234    if scheme and netloc:
235        return '%s://%s' % (scheme, netloc)
236
237    return None
238
239def get_netloc(url):
240    parse_result = urlparse(url)
241    return parse_result.netloc
242
243def seaf_sync(conf_dir, server_url, repo_id, worktree, username, passwd):
244    ''' synchronize a library from seafile server '''
245
246    pool = ccnet.ClientPool(conf_dir)
247    seafile_rpc = seafile.RpcClient(pool, req_pool=False)
248
249    token = get_token(server_url, username, passwd)
250    tmp = get_repo_downlod_info("%s/api2/repos/%s/download-info/" % (server_url, repo_id), token)
251
252    encrypted = tmp['encrypted']
253    magic = tmp.get('magic', None)
254    enc_version = tmp.get('enc_version', None)
255    random_key = tmp.get('random_key', None)
256
257    clone_token = tmp['token']
258    relay_id = tmp['relay_id']
259    relay_addr = tmp['relay_addr']
260    relay_port = str(tmp['relay_port'])
261    email = tmp['email']
262    repo_name = tmp['repo_name']
263    version = tmp.get('repo_version', 0)
264
265    more_info = None
266    base_url = get_base_url(server_url)
267    if base_url:
268        more_info = json.dumps({'server_url': base_url})
269
270    if encrypted == 1:
271        repo_passwd = 's123'
272    else:
273        repo_passwd = None
274
275    seafile_rpc.clone(repo_id,
276                      version,
277                      relay_id,
278                      repo_name.encode('utf-8'),
279                      worktree,
280                      clone_token,
281                      repo_passwd, magic,
282                      relay_addr,
283                      relay_port,
284                      email, random_key, enc_version, more_info)
285
286    print 'Synchronize repo test success.'
287
288def seaf_desync(conf_dir, repo_path):
289    '''Desynchronize a library from seafile server'''
290
291    pool = ccnet.ClientPool(conf_dir)
292    seafile_rpc = seafile.RpcClient(pool, req_pool=False)
293
294    repos = seafile_rpc.get_repo_list(-1, -1)
295    repo = None
296    for r in repos:
297        if r.worktree.replace('/', '\\') == repo_path.decode('utf-8').replace('/', '\\'):
298            repo = r
299            break
300
301    if repo:
302        print "Desynchronize repo test success."
303        seafile_rpc.remove_repo(repo.id)
304    else:
305        print "%s is not a library worktree" % repo_path
306
307def seaf_create(conf_dir, server_url, username, passwd, enc_repo):
308    '''Create a library'''
309
310    # curl -d 'username=<USERNAME>&password=<PASSWORD>' http://127.0.0.1:8000/api2/auth-token
311    token = get_token(server_url, username, passwd)
312
313    headers = { 'Authorization': 'Token %s' % token }
314    data = {
315        'name': 'test',
316        'desc': 'test',
317    }
318    if enc_repo:
319        data['passwd'] = 's123'
320
321    repo_info_json =  urlopen("%s/api2/repos/" % server_url, data=data, headers=headers)
322    repo_info = json.loads(repo_info_json)
323
324    if enc_repo:
325        print 'Create encrypted repo test success.'
326    else:
327        print 'Create non encrypted repo test success.'
328
329    return repo_info['repo_id']
330
331def seaf_delete(conf_dir, server_url, username, passwd, repo_id):
332    '''Delete a library'''
333
334    token = get_token(server_url, username, passwd)
335    headers = { 'Authorization': 'Token %s' % token }
336
337    conn = httplib.HTTPConnection(get_netloc(server_url))
338    conn.request('DELETE', '/api2/repos/%s/' % repo_id, None, headers)
339    resp = conn.getresponse()
340    if resp.status == 200:
341        print 'Delete repo test success.'
342    else:
343        print 'Delete repo test failed: %s.' % resp.reason
344
345def seaf_get_repo(conf_dir, repo_id):
346    pool = ccnet.ClientPool(conf_dir)
347    seafile_rpc = seafile.RpcClient(pool, req_pool=False)
348    return seafile_rpc.seafile_get_repo(repo_id)
349