diff --git a/seafileapi/files.py b/seafileapi/files.py index ed01e64..88bd996 100644 --- a/seafileapi/files.py +++ b/seafileapi/files.py @@ -2,33 +2,51 @@ import os import posixpath import re -from seafileapi.utils import querystr +from .utils import querystr, utf8lize, urlencode, urljoin ZERO_OBJ_ID = '0000000000000000000000000000000000000000' class _SeafDirentBase(object): """Base class for :class:`SeafFile` and :class:`SeafDir`. - It provides implementation of their common operations. + It provides implementation of their common oqperations. """ isdir = None - def __init__(self, repo, path, object_id, size=0): + def __init__(self, repo, name, type, id=None, parent_dir=None, size=None): """ - :param:`path` the full path of this entry within its repo, like - "/documents/example.md" - + :param:`repo` repository object + :param:'name' name of file or directory + :param:'type' dir or file + :param:'id' id of object + :param:'parent_dir' path of upstream directory. If there is no upstream dir, parent_dir should be '/' :param:`size` The size of a file. It should be zero for a dir. """ - self.client = repo.client self.repo = repo - self.path = path - self.id = object_id + self.path = '/'+ name + self.id = id if id is not None else ZERO_OBJ_ID + self.name=name + self.parent_dir = parent_dir if parent_dir is not None else '/' + self.type = type self.size = size + self.full_path = urljoin(self.parent_dir, self.path) - @property - def name(self): - return posixpath.basename(self.path) + def __str__(self): + return f"_SeafDirentBase[{self.type}: {self.name}, path: {self.full_path}]" + __repr__ = __str__ + + @classmethod + def from_json(cls, repo, dir_json): + dir_json = utf8lize(dir_json) + + repo=repo + name = dir_json['name'] + id = dir_json['id'] + parent_dir = dir_json.get('parent_dir', '/') + type = dir_json['type'] + size = dir_json.get('size', 0) + + return cls(repo, name, type, id, parent_dir, size) def list_revisions(self): pass @@ -36,23 +54,24 @@ def list_revisions(self): def delete(self): suffix = 'dir' if self.isdir else 'file' url = '/api2/repos/%s/%s/' % (self.repo.id, suffix) + querystr(p=self.path) - resp = self.client.delete(url) + resp = self.repo.client.delete(url) return resp def rename(self, newname): - """Change file/folder name to newname + """ + Change file/folder name to newname """ suffix = 'dir' if self.isdir else 'file' url = '/api2/repos/%s/%s/' % (self.repo.id, suffix) + querystr(p=self.path, reloaddir='true') postdata = {'operation': 'rename', 'newname': newname} - resp = self.client.post(url, data=postdata) + resp = self.repo.client.post(url, data=postdata) succeeded = resp.status_code == 200 if succeeded: if self.isdir: new_dirent = self.repo.get_dir(os.path.join(os.path.dirname(self.path), newname)) else: new_dirent = self.repo.get_file(os.path.join(os.path.dirname(self.path), newname)) - for key in list(self.__dict__.keys()): + for key in self.__dict__.keys(): self.__dict__[key] = new_dirent.__dict__[key] return succeeded @@ -69,7 +88,7 @@ def _copy_move_task(self, operation, dirent_type, dst_dir, dst_repo_id=None): 'src_dirent_name': src_dirent_name, 'dst_repo_id': dst_repo_id, 'dst_parent_dir': dst_parent_dir, 'operation': operation, 'dirent_type': dirent_type} - return self.client.post(url, data=postdata) + return self.repo.client.post(url, data=postdata) def copyTo(self, dst_dir, dst_repo_id=None): """Copy file/folder to other directory (also to a different repo) @@ -91,24 +110,54 @@ def moveTo(self, dst_dir, dst_repo_id=None): resp = self._copy_move_task('move', dirent_type, dst_dir, dst_repo_id) succeeded = resp.status_code == 200 if succeeded: - new_repo = self.client.repos.get_repo(dst_repo_id) + new_repo = self.repo.client.repos.get_repo(dst_repo_id) dst_path = os.path.join(dst_dir, os.path.basename(self.path)) if self.isdir: new_dirent = new_repo.get_dir(dst_path) else: new_dirent = new_repo.get_file(dst_path) - for key in list(self.__dict__.keys()): + for key in self.__dict__.keys(): self.__dict__[key] = new_dirent.__dict__[key] return succeeded - def get_share_link(self): - pass + def get_share_link(self, can_edit=False, can_download=True, password=None, expire_days=None, direct_link=True): + url = '/api/v2.1/share-links/' + post_data = { + "repo_id": self.repo.id, + "path": self.path, + "permissions": { + "can_edit": can_edit, + "can_download": can_download + } + } + if password: + post_data['password'] = password + if expire_days: + post_data['expire_days'] = expire_days + + resp = self.repo.client.post(url, data=post_data) + link = resp.json()['link'] + if direct_link: + link = link + '?dl=1' + + return link + + def _get_upload_link(self): + """ + get upload link of a file or directory. If object is a file, the file will be uploaded to parent dir. + If object is a directory, upload link will be created for itself + """ + url = '/api2/repos/%s/upload-link/' % self.repo.id + query = '?'+urlencode(dict(p=self.parent_dir if self.type=='file' else self.full_path)) + resp = self.repo.client.get(url+query) + return re.match(r'"(.*)"', resp.text).group(1) + class SeafDir(_SeafDirentBase): isdir = True def __init__(self, *args, **kwargs): - super(SeafDir, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.entries = None self.entries = kwargs.pop('entries', None) @@ -118,18 +167,23 @@ def ls(self, force_refresh=False): Return a list of objects of class :class:`SeafFile` or :class:`SeafDir`. """ if self.entries is None or force_refresh: - self.load_entries() + self.load_entries(type="d") return self.entries def share_to_user(self, email, permission): + """ + share dir to other user. + :param: email of other user + :param: permission should be 'r' for read and 'rw' for read and write + """ url = '/api2/repos/%s/dir/shared_items/' % self.repo.id + querystr(p=self.path) putdata = { 'share_type': 'user', 'username': email, 'permission': permission } - resp = self.client.put(url, data=putdata) + resp = self.repo.client.put(url, data=putdata) return resp.status_code == 200 def create_empty_file(self, name): @@ -140,7 +194,7 @@ def create_empty_file(self, name): path = posixpath.join(self.path, name) url = '/api2/repos/%s/file/' % self.repo.id + querystr(p=path, reloaddir='true') postdata = {'operation': 'create'} - resp = self.client.post(url, data=postdata) + resp = self.repo.client.post(url, data=postdata) self.id = resp.headers['oid'] self.load_entries(resp.json()) return SeafFile(self.repo, path, ZERO_OBJ_ID, 0) @@ -153,12 +207,12 @@ def mkdir(self, name): path = posixpath.join(self.path, name) url = '/api2/repos/%s/dir/' % self.repo.id + querystr(p=path, reloaddir='true') postdata = {'operation': 'mkdir'} - resp = self.client.post(url, data=postdata) + resp = self.repo.client.post(url, data=postdata) self.id = resp.headers['oid'] self.load_entries(resp.json()) return SeafDir(self.repo, path, ZERO_OBJ_ID) - def upload(self, fileobj, filename): + def upload(self, fileobj, filename, replace=False): """Upload a file to this folder. :param:fileobj :class:`File` like object @@ -172,11 +226,12 @@ def upload(self, fileobj, filename): files = { 'file': (filename, fileobj), 'parent_dir': self.path, + 'replace': 1 if replace else 0, } - self.client.post(upload_url, files=files) + self.repo.client.post(upload_url, files=files) return self.repo.get_file(posixpath.join(self.path, filename)) - def upload_local_file(self, filepath, name=None): + def upload_local_file(self, filepath, name=None, replace=False): """Upload a file to this folder. :param:filepath The path to the local file @@ -186,12 +241,7 @@ def upload_local_file(self, filepath, name=None): """ name = name or os.path.basename(filepath) with open(filepath, 'r') as fp: - return self.upload(fp, name) - - def _get_upload_link(self): - url = '/api2/repos/%s/upload-link/' % self.repo.id - resp = self.client.get(url) - return re.match(r'"(.*)"', resp.text).group(1) + return self.upload(fp, name, replace) def get_uploadable_sharelink(self): """Generate a uploadable shared link to this dir. @@ -200,19 +250,26 @@ def get_uploadable_sharelink(self): """ pass - def load_entries(self, dirents_json=None): + def load_entries(self, dirents_json=None, type=None): if dirents_json is None: - url = '/api2/repos/%s/dir/' % self.repo.id + querystr(p=self.path) - dirents_json = self.client.get(url).json() + url = '/api2/repos/%s/dir/' % self.repo.id + # oid: object id-id of upstream dir + # t: type - must be f for file or d for dir + if type is None: + query = '?' + urlencode(dict(oid=self.id, recursive=1)) + else: + query = '?' + urlencode(dict(oid=self.id, recursive=1, t=type)) + dirents_json = self.repo.client.get(url+query).json() self.entries = [self._load_dirent(entry_json) for entry_json in dirents_json] def _load_dirent(self, dirent_json): + dirent_json = utf8lize(dirent_json) path = posixpath.join(self.path, dirent_json['name']) if dirent_json['type'] == 'file': - return SeafFile(self.repo, path, dirent_json['id'], dirent_json['size']) + return SeafFile(self.repo, dirent_json['name'],dirent_json['type'], dirent_json['id'], dirent_json['parent_dir'], dirent_json.get('size', 0)) else: - return SeafDir(self.repo, path, dirent_json['id'], 0) + return SeafDir(self.repo, dirent_json['name'],dirent_json['type'], dirent_json['id'], dirent_json['parent_dir'], dirent_json.get('size', 0)) @property def num_entries(self): @@ -238,13 +295,13 @@ def __str__(self): (self.repo.id[:6], self.path, self.size) def _get_download_link(self): - url = '/api2/repos/%s/file/' % self.repo.id + querystr(p=self.path) - resp = self.client.get(url) + url = '/api2/repos/%s/file/' % self.repo.id + querystr(p=self.full_path,reuse=1) + resp = self.repo.client.get(url) return re.match(r'"(.*)"', resp.text).group(1) def get_content(self): """Get the content of the file""" url = self._get_download_link() - return self.client.get(url).content + return self.repo.client.get(url).content __repr__ = __str__ diff --git a/seafileapi/repo.py b/seafileapi/repo.py index 01811a2..f4f1c13 100644 --- a/seafileapi/repo.py +++ b/seafileapi/repo.py @@ -1,6 +1,7 @@ from urllib.parse import urlencode -from seafileapi.files import SeafDir, SeafFile -from seafileapi.utils import raise_does_not_exist +from .utils import utf8lize +from .files import SeafDir, SeafFile +from .utils import raise_does_not_exist class Repo(object): """ @@ -17,6 +18,7 @@ def __init__(self, client, repo_id, repo_name, @classmethod def from_json(cls, client, repo_json): + repo_json = utf8lize(repo_json) repo_id = repo_json['id'] repo_name = repo_json['name'] @@ -30,8 +32,9 @@ def is_readonly(self): return 'w' not in self.perm @raise_does_not_exist('The requested file does not exist') - def get_file(self, path): + def get_file(self, path, parent_dir=None): """Get the file object located in `path` in this repo. + param: parent_dir: path of parent directory. in case None, parent_dir is "/" Return a :class:`SeafFile` object """ @@ -40,7 +43,14 @@ def get_file(self, path): query = '?' + urlencode(dict(p=path)) file_json = self.client.get(url + query).json() - return SeafFile(self, path, file_json['id'], file_json['size']) + return SeafFile( + repo=self, + id=file_json['id'], + name=file_json['name'], + type='file', + parent_dir='/' if parent_dir is None else parent_dir, + size=file_json['size'] + ) @raise_does_not_exist('The requested dir does not exist') def get_dir(self, path): @@ -58,6 +68,39 @@ def get_dir(self, path): dir.load_entries(dir_json) return dir + # @raise_does_not_exist('The requested dir does not exist') + def get_items(self, type=None, recursive = False): + """Get the SeafDir and SeafFile objects located in this repo. + + Return a :class:`SeafDir` object + """ + # assert path.startswith('/') + url = '/api2/repos/%s/dir/' % self.id + query = '?' + urlencode(dict(t=type if type else '', recursive=1 if recursive else 0)) + resp = self.client.get(url + query).json() + data = [] + for j in resp: + if j['type'] == 'file': + data.append(SeafFile.from_json(repo=self, dir_json=j)) + else: + data.append(SeafDir.from_json(repo=self, dir_json=j)) + return data + + def get_files(self, oid=None, recursive = True): + """ + get all files in a repository. + input: oid - object id. The id of directory in this repo in case we want to find files in a specific dir + recursive - find file recursive + """ + url = '/api2/repos/%s/dir/' % self.id + query = '?' + urlencode(dict( + oid='' if oid is None else oid, + t='f', + recursive=1 if recursive == True else 0)) + resp = self.client.get(url + query).json() + + return [SeafFile.from_json(repo=self, dir_json=j) for j in resp] + def delete(self): """Remove this repo. Only the repo owner can do this""" self.client.delete('/api2/repos/' + self.id)