1from .service import seafserv_threaded_rpc, ccnet_threaded_rpc
2from pysearpc import SearpcError
3import json
4
5"""
6General rules for return values and exception handling of Seafile python API:
7- Read operations return corresponding values. Raises exceptions on parameter errors
8  or I/O errors in seaf-server.
9- Write or set operations return 0 on success, -1 on error. On error, an exceptioin
10  will be raised.
11
12All paths in parameters can be in absolute path format (like '/test') or
13relative path format (like 'test'). The API can handle both formats.
14"""
15
16REPO_STATUS_NORMAL = 0
17REPO_STATUS_READ_ONLY = 1
18
19class SeafileAPI(object):
20
21    def __init__(self):
22        pass
23
24    # fileserver token
25
26    def get_fileserver_access_token(self, repo_id, obj_id, op, username, use_onetime=True):
27        """Generate token for access file/dir in fileserver
28
29        op: the operation, can be 'view', 'download', 'download-dir', 'downloadblks',
30            'upload', 'update', 'upload-blks-api', 'upload-blks-aj',
31            'update-blks-api', 'update-blks-aj'
32
33        Return: the access token in string
34        """
35        onetime = 1 if bool(use_onetime) else 0
36        return seafserv_threaded_rpc.web_get_access_token(repo_id, obj_id, op, username,
37                                                          onetime)
38
39    def query_fileserver_access_token(self, token):
40        """Get the WebAccess object
41
42        token: the access token in string
43
44        Return: the WebAccess object (lib/webaccess.vala)
45        """
46        return seafserv_threaded_rpc.web_query_access_token(token)
47
48    def query_zip_progress(self, token):
49        """Query zip progress for download-dir, download-multi
50        token: obtained by get_fileserver_access_token
51        Return: json formated string `{"zipped":, "total":}`, otherwise None.
52        """
53        return seafserv_threaded_rpc.query_zip_progress(token)
54
55    def cancel_zip_task(self, token):
56        return seafserv_threaded_rpc.cancel_zip_task(token)
57
58    # password
59
60    def is_password_set(self, repo_id, username):
61        """
62        Return non-zero if True, otherwise 0.
63        """
64        return seafserv_threaded_rpc.is_passwd_set(repo_id, username)
65
66    def get_decrypt_key(self, repo_id, username):
67        """
68        Return: a CryptKey object (lib/crypt.vala)
69        """
70        return seafserv_threaded_rpc.get_decrypt_key(repo_id, username)
71
72    def change_repo_passwd(self, repo_id, old_passwd, new_passwd, user):
73        return seafserv_threaded_rpc.change_repo_passwd(repo_id, old_passwd,
74                                                        new_passwd, user)
75    def check_passwd(self, repo_id, magic):
76        return seafserv_threaded_rpc.check_passwd(repo_id, magic)
77
78    def set_passwd(self, repo_id, user, passwd):
79        return seafserv_threaded_rpc.set_passwd(repo_id, user, passwd)
80
81    def unset_passwd(self, repo_id, user):
82        return seafserv_threaded_rpc.unset_passwd(repo_id, user)
83
84    def generate_magic_and_random_key(self, enc_version, repo_id, password):
85        return seafserv_threaded_rpc.generate_magic_and_random_key(enc_version, repo_id, password)
86
87    # repo manipulation
88
89    def create_repo(self, name, desc, username, passwd=None, enc_version=2, storage_id=None):
90        return seafserv_threaded_rpc.create_repo(name, desc, username, passwd, enc_version)
91
92    def create_enc_repo(self, repo_id, name, desc, username, magic, random_key, salt, enc_version):
93        return seafserv_threaded_rpc.create_enc_repo(repo_id, name, desc, username, magic, random_key, salt, enc_version)
94
95    def get_repo(self, repo_id):
96        """
97        Return: a Repo object (lib/repo.vala)
98        """
99        return seafserv_threaded_rpc.get_repo(repo_id)
100
101    def remove_repo(self, repo_id):
102        return seafserv_threaded_rpc.remove_repo(repo_id)
103
104    def get_repo_list(self, start, limit, order_by=None):
105        """
106        Return: a list of Repo objects (lib/repo.vala)
107        """
108        return seafserv_threaded_rpc.get_repo_list(start, limit, order_by)
109
110    def count_repos(self):
111        return seafserv_threaded_rpc.count_repos()
112
113    def edit_repo(self, repo_id, name, description, username):
114        return seafserv_threaded_rpc.edit_repo(repo_id, name, description, username)
115
116    def is_repo_owner(self, username, repo_id):
117        """
118        Return 1 if True, otherwise 0.
119        """
120        return seafserv_threaded_rpc.is_repo_owner(username, repo_id)
121
122    def set_repo_owner(self, email, repo_id):
123        return seafserv_threaded_rpc.set_repo_owner(email, repo_id)
124
125    def get_repo_owner(self, repo_id):
126        """
127        Return: repo owner in string
128        """
129        return seafserv_threaded_rpc.get_repo_owner(repo_id)
130
131    def get_owned_repo_list(self, username, ret_corrupted=False, start=-1, limit=-1):
132        """
133        Return: a list of Repo objects
134        """
135        return seafserv_threaded_rpc.list_owned_repos(username,
136                                                      1 if ret_corrupted else 0,
137                                                      start, limit)
138
139    def search_repos_by_name(self, name):
140        return seafserv_threaded_rpc.search_repos_by_name(name)
141
142    def get_orphan_repo_list(self):
143        return seafserv_threaded_rpc.get_orphan_repo_list()
144
145    def get_repo_size(self, repo_id):
146        return seafserv_threaded_rpc.server_repo_size(repo_id)
147
148    def revert_repo(self, repo_id, commit_id, username):
149        return seafserv_threaded_rpc.revert_on_server(repo_id, commit_id, username)
150
151    def diff_commits(self, repo_id, old_commit, new_commit, fold_dir_diff = 1):
152        """
153        Return: a list of DiffEntry objects (lib/repo.vala)
154        """
155        return seafserv_threaded_rpc.get_diff(repo_id, old_commit, new_commit, fold_dir_diff)
156
157    def get_commit_list(self, repo_id, offset, limit):
158        """
159        Return: a list of Commit objects (lib/commit.vala)
160        """
161        return seafserv_threaded_rpc.get_commit_list(repo_id, offset, limit)
162
163    def get_commit(self, repo_id, repo_version, cmt_id):
164        """ Get a commit. """
165        try:
166            ret = seafserv_threaded_rpc.get_commit(repo_id, repo_version, cmt_id)
167        except SearpcError:
168            ret = None
169        return ret
170
171    def get_system_default_repo_id (self):
172        return seafserv_threaded_rpc.get_system_default_repo_id()
173
174    def get_org_id_by_repo_id (self, repo_id):
175        return seafserv_threaded_rpc.get_org_id_by_repo_id(repo_id)
176
177    def set_repo_status (self, repo_id, status):
178        return seafserv_threaded_rpc.set_repo_status(repo_id, status)
179
180    def get_repo_status (self, repo_id):
181        return seafserv_threaded_rpc.get_repo_status(repo_id)
182
183    # File property and dir listing
184
185    def is_valid_filename(self, repo_id, filename):
186        """
187        Return: 0 on invalid; 1 on valid.
188        """
189        return seafserv_threaded_rpc.is_valid_filename(repo_id, filename)
190
191    def get_file_size(self, store_id, version, file_id):
192        return seafserv_threaded_rpc.get_file_size(store_id, version, file_id)
193
194    def get_dir_size(self, store_id, version, dir_id):
195        """
196        Return the size of a dir. It needs to recursively calculate the size
197        of the dir. It can cause great delay before returning. Use with caution!
198        """
199        return seafserv_threaded_rpc.get_dir_size(store_id, version, dir_id)
200
201    def get_file_id_by_path(self, repo_id, path):
202        """
203        Returns None if path not found. Only raise exception on parameter or IO error.
204        """
205        return seafserv_threaded_rpc.get_file_id_by_path(repo_id, path)
206
207    def get_file_id_by_commit_and_path(self, repo_id, commit_id, path):
208        return seafserv_threaded_rpc.get_file_id_by_commit_and_path(repo_id,
209                                                                    commit_id,
210                                                                    path)
211
212    def get_dirent_by_path(self, repo_id, path):
213        """
214        Return: a Dirent object (lib/dirent.vala)
215        """
216        return seafserv_threaded_rpc.get_dirent_by_path(repo_id, path)
217
218    def list_file_by_file_id(self, repo_id, file_id, offset=-1, limit=-1):
219        # deprecated, use list_blocks_by_file_id instead.
220        return seafserv_threaded_rpc.list_file_blocks(repo_id, file_id, offset, limit)
221
222    def list_blocks_by_file_id(self, repo_id, file_id, offset=-1, limit=-1):
223        """
224        list block ids of a file.
225        Return: a string containing block list. Each id is seperated by '\n'
226        """
227        return seafserv_threaded_rpc.list_file_blocks(repo_id, file_id, offset, limit)
228
229    def get_dir_id_by_path(self, repo_id, path):
230        return seafserv_threaded_rpc.get_dir_id_by_path(repo_id, path)
231
232    def list_dir_by_dir_id(self, repo_id, dir_id, offset=-1, limit=-1):
233        """
234        Return: a list of Dirent objects. The objects are sorted as follows:
235                - Directories are always before files
236                - Entries are sorted by names in ascending order
237        """
238        return seafserv_threaded_rpc.list_dir(repo_id, dir_id, offset, limit)
239
240    def list_dir_by_path(self, repo_id, path, offset=-1, limit=-1):
241        dir_id = seafserv_threaded_rpc.get_dir_id_by_path(repo_id, path)
242        if dir_id is None:
243            return None
244        return seafserv_threaded_rpc.list_dir(repo_id, dir_id, offset, limit)
245
246    def list_dir_by_commit_and_path(self, repo_id,
247                                    commit_id, path, offset=-1, limit=-1):
248        dir_id = seafserv_threaded_rpc.get_dir_id_by_commit_and_path(repo_id, commit_id, path)
249        if dir_id is None:
250            return None
251        return seafserv_threaded_rpc.list_dir(repo_id, dir_id, offset, limit)
252
253    def get_dir_id_by_commit_and_path(self, repo_id, commit_id, path):
254        return seafserv_threaded_rpc.get_dir_id_by_commit_and_path(repo_id, commit_id, path)
255
256    def list_dir_with_perm(self, repo_id, dir_path, dir_id, user, offset=-1, limit=-1):
257        return seafserv_threaded_rpc.list_dir_with_perm (repo_id, dir_path, dir_id, user, offset, limit)
258
259    def mkdir_with_parents (self, repo_id, parent_dir, relative_path, username):
260        return seafserv_threaded_rpc.mkdir_with_parents(repo_id, parent_dir, relative_path, username)
261
262    def get_file_count_info_by_path(self, repo_id, path):
263        return seafserv_threaded_rpc.get_file_count_info_by_path(repo_id, path)
264
265    def get_total_storage (self):
266        return seafserv_threaded_rpc.get_total_storage()
267
268    def get_total_file_number (self):
269        return seafserv_threaded_rpc.get_total_file_number()
270
271    # file/dir operations
272
273    def post_file(self, repo_id, tmp_file_path, parent_dir, filename, username):
274        """Add a file to a directory"""
275        return seafserv_threaded_rpc.post_file(repo_id, tmp_file_path, parent_dir,
276                                               filename, username)
277
278    def post_empty_file(self, repo_id, parent_dir, filename, username):
279        return seafserv_threaded_rpc.post_empty_file(repo_id, parent_dir,
280                                                     filename, username)
281
282    def put_file(self, repo_id, tmp_file_path, parent_dir, filename,
283                 username, head_id):
284        """Update an existing file
285
286        head_id: the original commit id of the old file
287        """
288        return seafserv_threaded_rpc.put_file(repo_id, tmp_file_path, parent_dir,
289                                              filename, username, head_id)
290
291    '''
292    If you want to delete multiple files in a batch, @filename should be in
293    the following format: 'filename1\tfilename2\tfilename3'
294    '''
295    def del_file(self, repo_id, parent_dir, filename, username):
296        return seafserv_threaded_rpc.del_file(repo_id, parent_dir, filename, username)
297
298    '''
299    If you want to move or copy multiple files in a batch, @src_filename and @dst_filename
300    should be in the following format: 'filename1\tfilename2\tfilename3',make sure the number of files
301    in @src_filename and @dst_filename parameters match
302    '''
303    def copy_file(self, src_repo, src_dir, src_filename, dst_repo,
304                  dst_dir, dst_filename, username, need_progress, synchronous=0):
305        return seafserv_threaded_rpc.copy_file(src_repo, src_dir, src_filename,
306                                               dst_repo, dst_dir, dst_filename,
307                                               username, need_progress, synchronous)
308
309    def move_file(self, src_repo, src_dir, src_filename, dst_repo, dst_dir,
310                  dst_filename, replace, username, need_progress, synchronous=0):
311        return seafserv_threaded_rpc.move_file(src_repo, src_dir, src_filename,
312                                               dst_repo, dst_dir, dst_filename,
313                                               replace, username, need_progress, synchronous)
314
315    def get_copy_task(self, task_id):
316        return seafserv_threaded_rpc.get_copy_task(task_id)
317
318    def cancel_copy_task(self, task_id):
319        return seafserv_threaded_rpc.cancel_copy_task(task_id)
320
321    def rename_file(self, repo_id, parent_dir, oldname, newname, username):
322        return seafserv_threaded_rpc.rename_file(repo_id, parent_dir,
323                                                 oldname, newname, username)
324
325    def post_dir(self, repo_id, parent_dir, dirname, username):
326        """Add a directory"""
327        return seafserv_threaded_rpc.post_dir(repo_id, parent_dir, dirname, username)
328
329    def revert_file(self, repo_id, commit_id, path, username):
330        return seafserv_threaded_rpc.revert_file(repo_id, commit_id, path, username)
331
332    def revert_dir(self, repo_id, commit_id, path, username):
333        return seafserv_threaded_rpc.revert_dir(repo_id, commit_id, path, username)
334
335    def get_deleted(self, repo_id, show_days, path='/', scan_stat=None, limit=100):
336        """
337        Get list of deleted paths.
338
339        @show_days: return deleted path in the last @show_days
340        @path: return deleted files under this path. The path will be recursively traversed.
341        @scan_stat: An opaque status returned by the last call. In the first call, None
342                    must be passed. The last entry of the result list contains a 'scan_stat'
343                    attribute. In the next call, pass in the returned 'scan_stat'.
344        @limit: Advisory maximum number of commits to traverse. Sometimes more than @limit
345                commits will be traversed.
346
347        Return a list of DeletedEntry objects (lib/repo.vala).
348        If no more deleted entries can be returned within the given time frame (specified by
349        @show_days) or all deleted entries in the history have been returned, a list with a
350        single entry will be returned. The 'scan_stat' attribute of this entry is set to
351        None.
352        """
353        return seafserv_threaded_rpc.get_deleted(repo_id, show_days, path, scan_stat, limit)
354
355    def get_file_revisions(self, repo_id, commit_id, path, limit):
356        """
357        Get revisions of a file.
358
359        @commit_id: start traversing from this commit
360        @limit: maximum number of commits to traverse when looking for revisions
361
362        Return a list of Commit objects (lib/commit.vala) related to the revisions.
363        A few special attributes are added to the commit object:
364        @rev_file_id: id of the file revision
365        @rev_file_size: size of the file revision
366        @rev_renamed_old_path: set if this revision is made by a rename operation.
367                               It's set to the old path before rename.
368        @next_start_commit: commit_id for next page. An extra commit which only contains @next_start_commit
369                            will be appended to the list.
370        """
371        return seafserv_threaded_rpc.list_file_revisions(repo_id, commit_id, path, limit)
372
373    # This api is slow and should only be used for version 0 repos.
374    def get_files_last_modified(self, repo_id, parent_dir, limit):
375        """Get last modification time for files in a dir
376
377        limit: the max number of commits to analyze
378        """
379        return seafserv_threaded_rpc.calc_files_last_modified(repo_id,
380                                                              parent_dir, limit)
381
382    def get_repo_history_limit(self, repo_id):
383        """
384        Return repo history limit in days. Returns -1 if it's unlimited.
385        """
386        return seafserv_threaded_rpc.get_repo_history_limit(repo_id)
387
388    def set_repo_history_limit(self, repo_id, days):
389        """
390        Set repo history limit in days. Pass -1 if set to unlimited.
391        """
392        return seafserv_threaded_rpc.set_repo_history_limit(repo_id, days)
393
394    def check_repo_blocks_missing(self, repo_id, blklist):
395        return seafserv_threaded_rpc.check_repo_blocks_missing(repo_id, blklist)
396
397    def get_upload_tmp_file_offset (self, repo_id, file_path):
398        return seafserv_threaded_rpc.get_upload_tmp_file_offset (repo_id, file_path)
399
400    # file lock
401    def check_file_lock(self, repo_id, path, user):
402        """
403        Always return 0 since CE doesn't support file locking.
404        """
405        return 0
406
407    # share repo to user
408    def share_repo(self, repo_id, from_username, to_username, permission):
409        return seafserv_threaded_rpc.add_share(repo_id, from_username,
410                                               to_username, permission)
411
412    def remove_share(self, repo_id, from_username, to_username):
413        return seafserv_threaded_rpc.remove_share(repo_id, from_username,
414                                                  to_username)
415
416    def set_share_permission(self, repo_id, from_username, to_username, permission):
417        return seafserv_threaded_rpc.set_share_permission(repo_id, from_username,
418                                                          to_username, permission)
419
420    def share_subdir_to_user(self, repo_id, path, owner, share_user, permission, passwd=''):
421        return seafserv_threaded_rpc.share_subdir_to_user(repo_id, path, owner,
422                                                          share_user, permission, passwd)
423
424    def unshare_subdir_for_user(self, repo_id, path, owner, share_user):
425        return seafserv_threaded_rpc.unshare_subdir_for_user(repo_id, path, owner,
426                                                             share_user)
427
428    def update_share_subdir_perm_for_user(self, repo_id, path, owner,
429                                          share_user, permission):
430        return seafserv_threaded_rpc.update_share_subdir_perm_for_user(repo_id, path, owner,
431                                                                       share_user, permission)
432
433    def get_shared_repo_by_path(self, repo_id, path, shared_to, is_org=False):
434        """
435        If path is NULL, 'repo_id' represents for the repo we want,
436        otherwise, 'repo_id' represents for the origin repo, return virtual repo
437        """
438        return seafserv_threaded_rpc.get_shared_repo_by_path(repo_id, path, shared_to, 1 if is_org else 0)
439
440    def get_share_out_repo_list(self, username, start, limit):
441        """
442        Get repo list shared by this user.
443        Return: a list of Repo objects
444        """
445        return seafserv_threaded_rpc.list_share_repos(username, "from_email",
446                                                      start, limit)
447
448    def get_share_in_repo_list(self, username, start, limit):
449        """
450        Get repo list shared to this user.
451        """
452        return seafserv_threaded_rpc.list_share_repos(username, "to_email",
453                                                      start, limit)
454
455    def list_repo_shared_to(self, from_user, repo_id):
456        """
457        Get user list this repo is shared to.
458        Return: a list of SharedUser objects (lib/repo.vala)
459        """
460        return seafserv_threaded_rpc.list_repo_shared_to(from_user, repo_id)
461
462    def repo_has_been_shared(self, repo_id, including_groups=False):
463        return True if seafserv_threaded_rpc.repo_has_been_shared(repo_id, 1 if including_groups else 0) else False
464
465    # share repo to group
466    def group_share_repo(self, repo_id, group_id, username, permission):
467        # deprecated, use ``set_group_repo``
468        return seafserv_threaded_rpc.group_share_repo(repo_id, group_id,
469                                                      username, permission)
470
471    def set_group_repo(self, repo_id, group_id, username, permission):
472        return seafserv_threaded_rpc.group_share_repo(repo_id, group_id,
473                                                      username, permission)
474
475    def group_unshare_repo(self, repo_id, group_id, username):
476        # deprecated, use ``unset_group_repo``
477        return seafserv_threaded_rpc.group_unshare_repo(repo_id, group_id, username)
478
479    def unset_group_repo(self, repo_id, group_id, username):
480        return seafserv_threaded_rpc.group_unshare_repo(repo_id, group_id, username)
481
482    def get_shared_group_ids_by_repo(self, repo_id):
483        group_ids = seafserv_threaded_rpc.get_shared_groups_by_repo(repo_id)
484
485        if not group_ids:
486            return []
487
488        ret = []
489        for group_id in group_ids.split('\n'):
490            if not group_id:
491                continue
492            ret.append(group_id)
493        return ret
494
495    def list_repo_shared_group(self, from_user, repo_id):
496        # deprecated, use list_repo_shared_group_by_user instead.
497        return seafserv_threaded_rpc.list_repo_shared_group(from_user, repo_id)
498
499    def get_group_shared_repo_by_path (self, repo_id, path, group_id, is_org=False):
500        """
501        If path is NULL, 'repo_id' represents for the repo we want,
502        otherwise, 'repo_id' represents for the origin repo, return virtual repo
503        """
504        return seafserv_threaded_rpc.get_group_shared_repo_by_path(repo_id, path, group_id, 1 if is_org else 0)
505
506    def get_group_repos_by_user (self, user):
507        """
508        Return all the repos in all groups that the @user belongs to.
509        """
510        return seafserv_threaded_rpc.get_group_repos_by_user(user)
511
512    def get_org_group_repos_by_user (self, user, org_id):
513        return seafserv_threaded_rpc.get_org_group_repos_by_user(user, org_id)
514
515    def list_repo_shared_group_by_user(self, from_user, repo_id):
516        """
517        Return: a list of SharedGroup objects (lib/repo.vala)
518        """
519        return seafserv_threaded_rpc.list_repo_shared_group(from_user, repo_id)
520
521    def share_subdir_to_group(self, repo_id, path, owner, share_group, permission, passwd=''):
522        return seafserv_threaded_rpc.share_subdir_to_group(repo_id, path, owner,
523                                                           share_group, permission, passwd)
524
525    def unshare_subdir_for_group(self, repo_id, path, owner, share_group):
526        return seafserv_threaded_rpc.unshare_subdir_for_group(repo_id, path, owner,
527                                                              share_group)
528
529    def update_share_subdir_perm_for_group(self, repo_id, path, owner,
530                                           share_group, permission):
531        return seafserv_threaded_rpc.update_share_subdir_perm_for_group(repo_id, path, owner,
532                                                                        share_group, permission)
533
534    def get_group_repoids(self, group_id):
535        """
536        Return the list of group repo ids
537        """
538        repo_ids = seafserv_threaded_rpc.get_group_repoids(group_id)
539        if not repo_ids:
540            return []
541        l = []
542        for repo_id in repo_ids.split("\n"):
543            if repo_id == '':
544                continue
545            l.append(repo_id)
546        return l
547
548    def get_group_repo_list(self, group_id):
549        # deprecated, use get_repos_by_group instead.
550        ret = []
551        for repo_id in self.get_group_repoids(group_id):
552            r = self.get_repo(repo_id)
553            if r is None:
554                continue
555            ret.append(r)
556        return ret
557
558    def get_repos_by_group(self, group_id):
559        """
560        Return: a list of Repo objects
561        """
562        return seafserv_threaded_rpc.get_repos_by_group(group_id)
563
564    def get_group_repos_by_owner(self, username):
565        """
566        Get all repos a user share to any group
567        Return: a list of Repo objects
568        """
569        return seafserv_threaded_rpc.get_group_repos_by_owner(username)
570
571    def remove_group_repos_by_owner(self, group_id, username):
572        """
573        Unshare all repos a user shared to a group.
574        """
575        return seafserv_threaded_rpc.remove_repo_group(group_id, username)
576
577    def remove_group_repos(self, group_id):
578        """
579        Remove all repos under group.
580        Return: 0 success; -1 failed
581        """
582        return seafserv_threaded_rpc.remove_repo_group(group_id, None)
583
584    def set_group_repo_permission(self, group_id, repo_id, permission):
585        return seafserv_threaded_rpc.set_group_repo_permission(group_id, repo_id,
586                                                               permission)
587
588    def get_shared_users_for_subdir(self, repo_id, path, from_user):
589        """
590        Get all users a path is shared to.
591        Return: a list of SharedUser objects.
592        """
593        return seafserv_threaded_rpc.get_shared_users_for_subdir(repo_id, path, from_user)
594
595    def get_shared_groups_for_subdir(self, repo_id, path, from_user):
596        """
597        Get all groups a path is shared to.
598        Return: a list of SharedGroup objects.
599        """
600        return seafserv_threaded_rpc.get_shared_groups_for_subdir(repo_id, path, from_user)
601
602    def get_shared_users_by_repo(self, repo_id):
603        users = []
604        # get users that the repo is shared to
605        shared_users = seafserv_threaded_rpc.get_shared_users_by_repo (repo_id)
606        for user in shared_users:
607            users.append(user.user)
608
609        # get users in groups that the repo is shared to
610        group_ids = seafserv_threaded_rpc.get_shared_groups_by_repo(repo_id)
611        if not group_ids:
612            return users
613
614        ids = []
615        for group_id in group_ids.split('\n'):
616            if not group_id:
617                continue
618            ids.append(int(group_id))
619
620        json_ids = json.dumps(ids)
621        group_users = ccnet_threaded_rpc.get_groups_members(json_ids)
622
623        for user in group_users:
624            if user.user_name not in users:
625                users.append(user.user_name)
626
627        return users
628
629    # organization wide repo
630    def add_inner_pub_repo(self, repo_id, permission):
631        return seafserv_threaded_rpc.set_inner_pub_repo(repo_id, permission)
632
633    def remove_inner_pub_repo(self, repo_id):
634        return seafserv_threaded_rpc.unset_inner_pub_repo(repo_id)
635
636    def get_inner_pub_repo_list(self):
637        """
638        Return: a list of Repo objects.
639        """
640        return seafserv_threaded_rpc.list_inner_pub_repos()
641
642    def list_inner_pub_repos_by_owner(self, repo_owner):
643        """
644        Return: a list of Repo objects.
645        """
646        return seafserv_threaded_rpc.list_inner_pub_repos_by_owner(repo_owner)
647
648    def count_inner_pub_repos(self):
649        return seafserv_threaded_rpc.count_inner_pub_repos()
650
651    def is_inner_pub_repo(self, repo_id):
652        return seafserv_threaded_rpc.is_inner_pub_repo(repo_id)
653
654    # permission checks
655    def check_permission(self, repo_id, user):
656        """
657        Check repo share permissions. Only check user share, group share and inner-pub
658        shares.
659        Return: 'r', 'rw', or None
660        """
661        return seafserv_threaded_rpc.check_permission(repo_id, user)
662
663    def check_permission_by_path(self, repo_id, path, user):
664        """
665        Check both repo share permission and sub-folder access permissions.
666        This function should be used when updating file/folder in a repo.
667        In CE, this function is equivalent to check_permission.
668        Return: 'r', 'rw', or None
669        """
670        return seafserv_threaded_rpc.check_permission_by_path(repo_id, path, user)
671
672    def is_repo_syncable(self, repo_id, user, repo_perm):
673        """
674        Check if the permission of the repo is syncable.
675        """
676        return '{"is_syncable":true}'
677
678    def is_dir_downloadable(self, repo_id, dir_path, user, repo_perm):
679        """
680        Check if the permission of the dir is downloadable.
681        {"is_downloadable": false, "undownloadable_path":"path"}
682        - is_downloadable: true if the dir is downloadable, false if not.
683        - undownloadable_path: the undownloadable path of the repo if the path is not downloadable.
684        """
685        return '{"is_downloadable":true}'
686
687    # token
688    def generate_repo_token(self, repo_id, username):
689        """Generate a token for sync a repo
690        """
691        return seafserv_threaded_rpc.generate_repo_token(repo_id, username)
692
693    def delete_repo_token(self, repo_id, token, user):
694        return seafserv_threaded_rpc.delete_repo_token(repo_id, token, user)
695
696    def list_repo_tokens(self, repo_id):
697        """
698        Return: a list of RepoTokenInfo objects.
699        """
700        return seafserv_threaded_rpc.list_repo_tokens(repo_id)
701
702    def list_repo_tokens_by_email(self, username):
703        return seafserv_threaded_rpc.list_repo_tokens_by_email(username)
704
705    def delete_repo_tokens_by_peer_id(self, email, peer_id):
706        return seafserv_threaded_rpc.delete_repo_tokens_by_peer_id(email, peer_id)
707
708    def delete_repo_tokens_by_email(self, email):
709        return seafserv_threaded_rpc.delete_repo_tokens_by_email(email)
710
711    # quota
712    def get_user_self_usage(self, username):
713        """Get the sum of repos' size of the user"""
714        return seafserv_threaded_rpc.get_user_quota_usage(username)
715
716    def get_user_share_usage(self, username):
717        # sum (repo_size * number_of_shares)
718        return seafserv_threaded_rpc.get_user_share_usage(username)
719
720    def get_user_quota(self, username):
721        """
722        Return: -2 if quota is unlimited; otherwise it must be number > 0.
723        """
724        return seafserv_threaded_rpc.get_user_quota(username)
725
726    def set_user_quota(self, username, quota):
727        return seafserv_threaded_rpc.set_user_quota(username, quota)
728
729    def get_user_share_quota(self, username):
730        return -2               # unlimited
731
732    def set_user_share_quota(self, username, quota):
733        pass
734
735    def check_quota(self, repo_id, delta=0):
736        return seafserv_threaded_rpc.check_quota(repo_id, delta)
737
738    def list_user_quota_usage(self):
739        return seafserv_threaded_rpc.list_user_quota_usage()
740
741    # virtual repo
742    def create_virtual_repo(self, origin_repo_id, path, repo_name, repo_desc, owner, passwd=''):
743        return seafserv_threaded_rpc.create_virtual_repo(origin_repo_id,
744                                                         path,
745                                                         repo_name,
746                                                         repo_desc,
747                                                         owner,
748                                                         passwd)
749
750    def get_virtual_repos_by_owner(self, owner):
751        return seafserv_threaded_rpc.get_virtual_repos_by_owner(owner)
752
753    def get_virtual_repo(self, origin_repo, path, owner):
754        return seafserv_threaded_rpc.get_virtual_repo(origin_repo, path, owner)
755
756    # Clean trash
757
758    def clean_up_repo_history(self, repo_id, keep_days):
759        return seafserv_threaded_rpc.clean_up_repo_history(repo_id, keep_days)
760
761    # Trashed repos
762    def get_trash_repo_list(self, start, limit):
763        return seafserv_threaded_rpc.get_trash_repo_list(start, limit)
764
765    def del_repo_from_trash(self, repo_id):
766        return seafserv_threaded_rpc.del_repo_from_trash(repo_id)
767
768    def restore_repo_from_trash(self, repo_id):
769        return seafserv_threaded_rpc.restore_repo_from_trash(repo_id)
770
771    def get_trash_repos_by_owner(self, owner):
772        return seafserv_threaded_rpc.get_trash_repos_by_owner(owner)
773
774    def get_trash_repo_owner (self, repo_id):
775        return seafserv_threaded_rpc.get_trash_repo_owner(repo_id)
776
777    def empty_repo_trash(self):
778        return seafserv_threaded_rpc.empty_repo_trash()
779
780    def empty_repo_trash_by_owner(self, owner):
781        return seafserv_threaded_rpc.empty_repo_trash_by_owner(owner)
782
783    # Server config
784    def get_server_config_int (self, group, key):
785        return seafserv_threaded_rpc.get_server_config_int (group, key)
786
787    def set_server_config_int (self, group, key, value):
788        return seafserv_threaded_rpc.set_server_config_int (group, key, value)
789
790    def get_server_config_int64 (self, group, key):
791        return seafserv_threaded_rpc.get_server_config_int64 (group, key)
792
793    def set_server_config_int64 (self, group, key, value):
794        return seafserv_threaded_rpc.set_server_config_int64 (group, key, value)
795
796    def get_server_config_string (self, group, key):
797        return seafserv_threaded_rpc.get_server_config_string (group, key)
798
799    def set_server_config_string (self, group, key, value):
800        return seafserv_threaded_rpc.set_server_config_string (group, key, value)
801
802    def get_server_config_boolean (self, group, key):
803        return bool(seafserv_threaded_rpc.get_server_config_boolean (group, key))
804
805    def set_server_config_boolean (self, group, key, value):
806        i_value = 1 if bool(value) else 0
807        return seafserv_threaded_rpc.set_server_config_boolean (group, key, i_value)
808
809    def del_org_group_repo(self, repo_id, org_id, group_id):
810        seafserv_threaded_rpc.del_org_group_repo(repo_id, org_id, group_id)
811
812    def org_get_shared_users_by_repo(self, org_id, repo_id):
813        users = []
814        # get users that the repo is shared to
815        shared_users = seafserv_threaded_rpc.org_get_shared_users_by_repo(org_id, repo_id)
816        for user in shared_users:
817            users.append(user.user)
818
819        # get users in groups that the repo is shared to
820        group_ids = seafserv_threaded_rpc.get_org_groups_by_repo(org_id, repo_id)
821        if not group_ids:
822            return users
823
824        ids = []
825        for group_id in group_ids.split('\n'):
826            if not group_id:
827                continue
828            ids.append(int(group_id))
829
830        json_ids = json.dumps(ids)
831        group_users = ccnet_threaded_rpc.get_groups_members(json_ids)
832
833        for user in group_users:
834            if user.user_name not in users:
835                users.append(user.user_name)
836
837        return users
838
839    def list_org_inner_pub_repos(self, org_id):
840        return seafserv_threaded_rpc.list_org_inner_pub_repos(org_id)
841
842    def convert_repo_path(self, repo_id, path, user, is_org=False):
843        return seafserv_threaded_rpc.convert_repo_path(repo_id, path, user, 1 if is_org else 0)
844
845    def publish_event(self, channel, content):
846        return seafserv_threaded_rpc.publish_event(channel, content)
847
848    def pop_event(self, channel):
849        return seafserv_threaded_rpc.pop_event(channel)
850
851    def search_files(self, repo_id, search_str):
852        return seafserv_threaded_rpc.search_files(repo_id, search_str)
853
854seafile_api = SeafileAPI()
855
856class CcnetAPI(object):
857
858    def __init__(self):
859        pass
860
861    # user management
862    def add_emailuser(self, email, passwd, is_staff, is_active):
863        return ccnet_threaded_rpc.add_emailuser(email, passwd, is_staff, is_active)
864
865    def remove_emailuser(self, source, email):
866        """
867        source can be 'DB' or 'LDAP'.
868        - 'DB': remove a user created in local database
869        - 'LDAP': remove a user imported from LDAP
870        """
871        return ccnet_threaded_rpc.remove_emailuser(source, email)
872
873    def validate_emailuser(self, email, passwd):
874        """
875        Verify user's password on login. Can be used to verify DB and LDAP users.
876        The function first verify password with LDAP, then local database.
877        """
878        return ccnet_threaded_rpc.validate_emailuser(email, passwd)
879
880    def get_emailuser(self, email):
881        """
882        Only return local database user or imported LDAP user.
883        It first lookup user from local database, if not found, lookup imported
884        LDAP user.
885        Return: a list of EmailUser objects (ccnet/lib/ccnetobj.vala)
886        The 'source' attribute of EmailUser object is set to 'LDAPImport' for LDAP
887        imported user, and 'DB' for local database user.
888        """
889        return ccnet_threaded_rpc.get_emailuser(email)
890
891    def get_emailuser_with_import(self, email):
892        """
893        The same as get_emailuser() but import the user from LDAP if it was not
894        imported yet.
895        """
896        return ccnet_threaded_rpc.get_emailuser_with_import(email)
897
898    def get_emailuser_by_id(self, user_id):
899        """
900        Get a user from local database with the db index id.
901        """
902        return ccnet_threaded_rpc.get_emailuser_by_id(user_id)
903
904    def get_emailusers(self, source, start, limit, is_active=None):
905        """
906        source:
907          - 'DB': return local db users
908          - 'LDAPImport': return imported LDAP users
909          - 'LDAP': retrieve users directly from LDAP server
910        start: offset to start retrieving, -1 to start from the beginning
911        limit: number of users to get, -1 to get all user from start
912        is_active: True to return only active users; False to return inactive users;
913                   None to return all users.
914        Return: a list of EmailUser objects.
915        """
916        if is_active is True:
917            status = "active"       # list active users
918        elif is_active is False:
919            status = "inactive"     # list inactive users
920        else:
921            status = ""             # list all users
922
923        return ccnet_threaded_rpc.get_emailusers(source, start, limit, status)
924
925    def search_emailusers(self, source, email_patt, start, limit):
926        """
927        Search for users whose name contains @email_patt.
928        source: 'DB' for local db users; 'LDAP' for imported LDAP users.
929                This function cannot search LDAP users directly in LDAP server.
930        """
931        return ccnet_threaded_rpc.search_emailusers(source, email_patt, start, limit)
932
933    def search_groups(self, group_patt, start, limit):
934        """
935        Search for groups whose name contains @group_patt.
936        """
937        return ccnet_threaded_rpc.search_groups(group_patt, start, limit)
938
939    def get_top_groups(self, including_org=False):
940        return ccnet_threaded_rpc.get_top_groups(1 if including_org else 0)
941
942    def get_child_groups(self, group_id):
943        return ccnet_threaded_rpc.get_child_groups(group_id)
944
945    def get_descendants_groups(self, group_id):
946        return ccnet_threaded_rpc.get_descendants_groups(group_id)
947
948    def get_ancestor_groups(self, group_id):
949        return ccnet_threaded_rpc.get_ancestor_groups(group_id)
950
951    def search_ldapusers(self, keyword, start, limit):
952        """
953        Search for users whose name contains @keyword directly from LDAP server.
954        """
955        return ccnet_threaded_rpc.search_ldapusers(keyword, start, limit)
956
957    def count_emailusers(self, source):
958        """
959        Return the number of active users by source.
960        source: 'DB' for local db users; 'LDAP' for imported LDAP users.
961        """
962        return ccnet_threaded_rpc.count_emailusers(source)
963
964    def count_inactive_emailusers(self, source):
965        """
966        Return the number of inactive users by source.
967        source: 'DB' for local db users; 'LDAP' for imported LDAP users.
968        """
969        return ccnet_threaded_rpc.count_inactive_emailusers(source)
970
971    def update_emailuser(self, source, user_id, password, is_staff, is_active):
972        """
973        source: 'DB' for local db user; 'LDAP' for imported LDAP user.
974        user_id: usually not changed.
975        password: new password in plain text. Only effective for DB users.
976                  If '!' is passed, the password won't be updated.
977        is_staff: change superuser status
978        is_active: activate or deactivate user
979        """
980        return ccnet_threaded_rpc.update_emailuser(source, user_id, password, is_staff, is_active)
981
982    def update_role_emailuser(self, email, role):
983        return ccnet_threaded_rpc.update_role_emailuser(email, role)
984
985    def get_superusers(self):
986        """
987        Return: a list of EmailUser objects.
988        """
989        return ccnet_threaded_rpc.get_superusers()
990
991    def get_emailusers_in_list(self, source, user_list):
992        """
993        @source: 'DB' or 'LDAP'
994        @user_list: json '[user1, user2, user3,...]'
995        """
996        return ccnet_threaded_rpc.get_emailusers_in_list(source, user_list)
997
998    def update_emailuser_id (self, old_email, new_email):
999        return ccnet_threaded_rpc.update_emailuser_id (old_email, new_email)
1000
1001    # group management
1002    def create_group(self, group_name, user_name, gtype=None, parent_group_id=0):
1003        """
1004        For CE, gtype is not used and should always be None.
1005        """
1006        return ccnet_threaded_rpc.create_group(group_name, user_name, gtype, parent_group_id)
1007
1008    def create_org_group(self, org_id, group_name, user_name, parent_group_id=0):
1009        return ccnet_threaded_rpc.create_org_group(org_id, group_name, user_name, parent_group_id)
1010
1011    def remove_group(self, group_id):
1012        """
1013        permission check should be done before calling this function.
1014        """
1015        return ccnet_threaded_rpc.remove_group(group_id)
1016
1017    def group_add_member(self, group_id, user_name, member_name):
1018        """
1019        user_name: unused.
1020        """
1021        return ccnet_threaded_rpc.group_add_member(group_id, user_name, member_name)
1022
1023    def group_remove_member(self, group_id, user_name, member_name):
1024        """
1025        user_name: unused.
1026        """
1027        return ccnet_threaded_rpc.group_remove_member(group_id, user_name, member_name)
1028
1029    def group_set_admin(self, group_id, member_name):
1030        """
1031        No effect if member_name is not in the group.
1032        """
1033        return ccnet_threaded_rpc.group_set_admin(group_id, member_name)
1034
1035    def group_unset_admin(self, group_id, member_name):
1036        """
1037        No effect if member_name is not in the group.
1038        """
1039        return ccnet_threaded_rpc.group_unset_admin(group_id, member_name)
1040
1041    def set_group_name(self, group_id, group_name):
1042        return ccnet_threaded_rpc.set_group_name(group_id, group_name)
1043
1044    def quit_group(self, group_id, user_name):
1045        return ccnet_threaded_rpc.quit_group(group_id, user_name)
1046
1047    def get_groups(self, user_name, return_ancestors=False):
1048        """
1049        Get all groups the user belongs to.
1050        Return: a list of Group objects (ccnet/lib/ccnetobj.vala)
1051        """
1052        return ccnet_threaded_rpc.get_groups(user_name, 1 if return_ancestors else 0)
1053
1054    def get_all_groups(self, start, limit, source=None):
1055        """
1056        For CE, source is not used and should alwasys be None.
1057        """
1058        return ccnet_threaded_rpc.get_all_groups(start, limit, source)
1059
1060    def get_group(self, group_id):
1061        return ccnet_threaded_rpc.get_group(group_id)
1062
1063    def get_group_members(self, group_id, start=-1, limit=-1):
1064        """
1065        Return a list of GroupUser objects (ccnet/lib/ccnetobj.vala)
1066        """
1067        return ccnet_threaded_rpc.get_group_members(group_id, start, limit)
1068
1069    def get_members_with_prefix (self, group_id, prefix=None):
1070        """
1071        Return a list of GroupUser objects
1072        """
1073        return ccnet_threaded_rpc.get_members_with_prefix(group_id, prefix)
1074
1075    def check_group_staff(self, group_id, username, in_structure=False):
1076        """
1077        Return non-zero value if true, 0 if not true
1078        """
1079        return ccnet_threaded_rpc.check_group_staff(group_id, username, 1 if in_structure else 0)
1080
1081    def remove_group_user(self, username):
1082        return ccnet_threaded_rpc.remove_group_user(username)
1083
1084    def is_group_user(self, group_id, user, in_structure=True):
1085        """
1086        Return non-zero value if true, 0 if not true
1087        If @in_structure is true, return whether user is in descendants groups and @group_id it self
1088        """
1089        return ccnet_threaded_rpc.is_group_user(group_id, user, 1 if in_structure else 0)
1090
1091    def set_group_creator(self, group_id, user_name):
1092        return ccnet_threaded_rpc.set_group_creator(group_id, user_name)
1093
1094    # organization management
1095    def create_org(self, org_name, url_prefix, creator):
1096        return ccnet_threaded_rpc.create_org(org_name, url_prefix, creator)
1097
1098    def remove_org(self, org_id):
1099        return ccnet_threaded_rpc.remove_org(org_id)
1100
1101    def get_all_orgs(self, start, limit):
1102        """
1103        Return a list of Organization objects (ccnet/lib/ccnetobj.vala)
1104        """
1105        return ccnet_threaded_rpc.get_all_orgs(start, limit)
1106
1107    def count_orgs(self):
1108        return ccnet_threaded_rpc.count_orgs()
1109
1110    def get_org_by_url_prefix(self, url_prefix):
1111        """
1112        Return an Organizaion object.
1113        """
1114        return ccnet_threaded_rpc.get_org_by_url_prefix(url_prefix)
1115
1116    def get_org_by_id(self, org_id):
1117        return ccnet_threaded_rpc.get_org_by_id(org_id)
1118
1119    def add_org_user(self, org_id, email, is_staff):
1120        return ccnet_threaded_rpc.add_org_user(org_id, email, is_staff)
1121
1122    def remove_org_user(self, org_id, email):
1123        return ccnet_threaded_rpc.remove_org_user(org_id, email)
1124
1125    def get_orgs_by_user(self, email):
1126        return ccnet_threaded_rpc.get_orgs_by_user(email)
1127
1128    def get_org_emailusers(self, url_prefix, start, limit):
1129        """
1130        Return a list of EmailUser objects.
1131        """
1132        return ccnet_threaded_rpc.get_org_emailusers(url_prefix, start, limit)
1133
1134    def add_org_group(self, org_id, group_id):
1135        return ccnet_threaded_rpc.add_org_group(org_id, group_id)
1136
1137    def remove_org_group(self, org_id, group_id):
1138        return ccnet_threaded_rpc.remove_org_group(org_id, group_id)
1139
1140    def is_org_group(self, group_id):
1141        """
1142        Return non-zero if True, otherwise 0.
1143        """
1144        return ccnet_threaded_rpc.is_org_group(group_id)
1145
1146    def get_org_id_by_group(self, group_id):
1147        return ccnet_threaded_rpc.get_org_id_by_group(group_id)
1148
1149    def get_org_groups(self, org_id, start, limit):
1150        """
1151        Return a list of int, each int is group id.
1152        """
1153        return ccnet_threaded_rpc.get_org_groups(org_id, start, limit)
1154
1155    def get_org_top_groups(self, org_id):
1156        return ccnet_threaded_rpc.get_org_top_groups(org_id)
1157
1158    def org_user_exists(self, org_id, email):
1159        """
1160        Return non-zero if True, otherwise 0.
1161        """
1162        return ccnet_threaded_rpc.org_user_exists(org_id, email)
1163
1164    def is_org_staff(self, org_id, user):
1165        """
1166        Return non-zero if True, otherwise 0.
1167        """
1168        return ccnet_threaded_rpc.is_org_staff(org_id, user)
1169
1170    def set_org_staff(self, org_id, user):
1171        return ccnet_threaded_rpc.set_org_staff(org_id, user)
1172
1173    def unset_org_staff(self, org_id, user):
1174        return ccnet_threaded_rpc.unset_org_staff(org_id, user)
1175
1176    def set_org_name(self, org_id, org_name):
1177        return ccnet_threaded_rpc.set_org_name(org_id, org_name)
1178
1179    def get_primary_id (self, email):
1180        return ccnet_threaded_rpc.get_primary_id(email)
1181
1182    def set_reference_id (self, primary_id, reference_id):
1183        return ccnet_threaded_rpc.set_reference_id(primary_id, reference_id)
1184
1185    def get_groups_members(self, group_ids):
1186        """
1187        @group_ids: json '[id1, id2, id3,...]'
1188        """
1189        return ccnet_threaded_rpc.get_groups_members(group_ids)
1190
1191ccnet_api = CcnetAPI()
1192