From bea1d57a17b9cbbe4ab1182dab5b28c154b35b25 Mon Sep 17 00:00:00 2001 From: nguacon01 Date: Mon, 16 Nov 2020 12:16:37 +0100 Subject: [PATCH 1/3] add more attributes for Base object --- seafileapi/files.py | 144 ++++++++++++++++++++++++++++++++------------ 1 file changed, 105 insertions(+), 39 deletions(-) diff --git a/seafileapi/files.py b/seafileapi/files.py index ed01e64..6798c35 100644 --- a/seafileapi/files.py +++ b/seafileapi/files.py @@ -1,34 +1,60 @@ import io import os +from os import path import posixpath import re -from seafileapi.utils import querystr +from .utils import querystr, utf8lize, urlencode, urljoin +import urllib.parse 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, id, name, type, parent_dir=None, size=None): """ :param:`path` the full path of this entry within its repo, like "/documents/example.md" :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 = '/'+ urllib.parse.quote(name) + self.id = id + self.name=name + self.parent_dir = parent_dir + 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): + """ + load object data from json + + """ + dir_json = utf8lize(dir_json) + + repo=repo + id = dir_json['id'] + name = dir_json['name'] + parent_dir = dir_json.get('parent_dir', '/') + type = dir_json['type'] + size = dir_json.get('size', 0) + + return cls(repo, id, name, type, parent_dir, size) + + # @property + # def name(self): + # return posixpath.basename(self.path) def list_revisions(self): pass @@ -36,7 +62,7 @@ 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): @@ -45,14 +71,14 @@ def rename(self, 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 +95,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 +117,55 @@ 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 file or folder upload link + """ + url = '/api2/repos/%s/upload-link/' % self.repo.id + # param "p" is the path of folder in which we want to upload to + # in case we want to get upload link from a file in this folder, we can take its parent_dir path as param "p" + 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,7 +175,7 @@ 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 @@ -129,7 +186,7 @@ def share_to_user(self, email, permission): '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 +197,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 +210,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 +229,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 +244,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 +253,32 @@ def get_uploadable_sharelink(self): """ pass - def load_entries(self, dirents_json=None): + def load_entries(self, dirents_json=None, type=None): + """ + Load entries into a folder + param: dirents_json: json data of the folder. Json data contains sub folders and files + param: type: should be "file" or "dir" in case we want to load data in a folder preciselly + """ 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']) + # return _SeafDirentBase(self.repo, dirent_json['id'], dirent_json['name'],dirent_json['type'], dirent_json['parent_dir'], dirent_json.get('size', 0)) if dirent_json['type'] == 'file': - return SeafFile(self.repo, path, dirent_json['id'], dirent_json['size']) + return SeafFile(self.repo, dirent_json['id'], dirent_json['name'], dirent_json['type'], 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['id'], dirent_json['name'], dirent_json['type'], dirent_json['parent_dir'], dirent_json.get('size', 0)) @property def num_entries(self): @@ -238,13 +304,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) + 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__ From 34abe3de1ceb3e73cc4426d05088b59b31c429a9 Mon Sep 17 00:00:00 2001 From: nguacon01 Date: Mon, 16 Nov 2020 14:13:00 +0100 Subject: [PATCH 2/3] add function get_files and get_items in repo object --- seafileapi/repo.py | 51 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) 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) From 943592b89b78f79540771be37db639bd1879995e Mon Sep 17 00:00:00 2001 From: nguacon01 Date: Thu, 26 Nov 2020 17:16:23 +0100 Subject: [PATCH 3/3] modify seafdirentbase object --- seafileapi/files.py | 57 +++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/seafileapi/files.py b/seafileapi/files.py index 6798c35..88bd996 100644 --- a/seafileapi/files.py +++ b/seafileapi/files.py @@ -1,10 +1,8 @@ import io import os -from os import path import posixpath import re from .utils import querystr, utf8lize, urlencode, urljoin -import urllib.parse ZERO_OBJ_ID = '0000000000000000000000000000000000000000' @@ -15,19 +13,21 @@ class _SeafDirentBase(object): """ isdir = None - def __init__(self, repo, id, name, type, parent_dir=None, size=None): + 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.repo = repo - self.path = '/'+ urllib.parse.quote(name) - self.id = id + self.path = '/'+ name + self.id = id if id is not None else ZERO_OBJ_ID self.name=name - self.parent_dir = parent_dir - self.type=type + 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) @@ -37,24 +37,16 @@ def __str__(self): @classmethod def from_json(cls, repo, dir_json): - """ - load object data from json - - """ dir_json = utf8lize(dir_json) repo=repo - id = dir_json['id'] 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, id, name, type, parent_dir, size) - - # @property - # def name(self): - # return posixpath.basename(self.path) + return cls(repo, name, type, id, parent_dir, size) def list_revisions(self): pass @@ -66,7 +58,8 @@ def delete(self): 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') @@ -151,11 +144,10 @@ def get_share_link(self, can_edit=False, can_download=True, password=None, expir def _get_upload_link(self): """ - Get file or folder upload link + 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 - # param "p" is the path of folder in which we want to upload to - # in case we want to get upload link from a file in this folder, we can take its parent_dir path as param "p" 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) @@ -180,6 +172,11 @@ def ls(self, force_refresh=False): 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', @@ -254,11 +251,6 @@ def get_uploadable_sharelink(self): pass def load_entries(self, dirents_json=None, type=None): - """ - Load entries into a folder - param: dirents_json: json data of the folder. Json data contains sub folders and files - param: type: should be "file" or "dir" in case we want to load data in a folder preciselly - """ if dirents_json is None: url = '/api2/repos/%s/dir/' % self.repo.id # oid: object id-id of upstream dir @@ -274,11 +266,10 @@ def load_entries(self, dirents_json=None, type=None): def _load_dirent(self, dirent_json): dirent_json = utf8lize(dirent_json) path = posixpath.join(self.path, dirent_json['name']) - # return _SeafDirentBase(self.repo, dirent_json['id'], dirent_json['name'],dirent_json['type'], dirent_json['parent_dir'], dirent_json.get('size', 0)) if dirent_json['type'] == 'file': - return SeafFile(self.repo, dirent_json['id'], dirent_json['name'], dirent_json['type'], dirent_json['parent_dir'], dirent_json.get('size', 0)) + 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, dirent_json['id'], dirent_json['name'], dirent_json['type'], dirent_json['parent_dir'], dirent_json.get('size', 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): @@ -304,7 +295,7 @@ 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.full_path) + 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)