def __upload(self, file, remote: str = None) -> StackFile: """ Core logic of the upload, previous method simply converts the name to a file IO pointer and passes it to this method :param file: File pointer :param path: Remote path :param name: Remote name :return: StackFile instance """ if not remote and not getattr(file, 'name', None): raise StackException( "Unable to determine remote file name, either set " "it via file.name or by passing the 'remote' parameter") name = remote or file.name path = join(self.__cwd, name) name = basename(name) try: resp = self.http.webdav('PUT', path, data=file, stream=True) assert resp.status_code == 201, 'Expected 201 (created) status code' return self.file(path) except (AssertionError, RequestException) as e: raise StackException(e)
def download_into(self, file: str, buffer: Union[BufferedIOBase, BytesIO, BinaryIO] = None, remote_path: str = None): """ Download a file from your STACK account :param file: File name to download :param remote_path: Path to find the file in :param buffer: Buffer to download into (BytesIO, StringIO, file pointer) :return: BytesIO buffer """ if not remote_path: remote_path = self.__cwd file = join(remote_path, file.lstrip("/")) if not buffer: buffer = BytesIO() if not hasattr(buffer, 'write'): raise StackException( "Download buffer must be a binary IO type, please " "use BytesIO or open your file in 'wb' mode.") try: resp = self.http.webdav('GET', file, stream=True) shutil.copyfileobj(resp.raw, buffer) buffer.seek(0) return buffer except RequestException as e: raise StackException(e)
def users(self) -> Iterable[StackUser]: """ Get an iterator of all users registered to this account :return: StackUser iterator """ limit = 50 offset = 0 amount = None while amount is None or (amount is not None and offset < int(amount)): params = { "public": False, "offset": offset, "limit": limit, "query": "" } resp = self.http.get("/api/users", params=params) if resp.status_code == 403: raise StackException( 'Unable to list users, access denied. ' 'Please log in with the administrator account.', resp=resp) users = resp.json() yield from (StackUser(stack=self, props=u) for u in users.get("users") or []) amount = int(users.get("amountUsers")) offset += limit
def ls(self, search: str = "", order: str = "asc", path: str = None) -> Iterable[Union[StackFile, StackDirectory]]: """ List the current (or other) directory :param path: Path to list, optional (default: CWD) :param search: Search files with a given name :param order: What way should the files be ordered? :return: StackNode iterator """ if order not in ("asc", "desc"): raise StackException("Invalid order parameter, got '{}', allowed " "values: 'asc', 'desc'".format(order)) if not path: path = self.__cwd offset = 0 amount = None while amount is None or (amount is not None and offset < int(amount)): files = self.__files(path, offset, query=search, order=order) offset += len(files.get("nodes") or []) amount = int(files.get("amount")) yield from self.__nodes_to_objects(files.get("nodes") or [])
def move(self, path: str): """ Move the file / directory to another location :param path: New path to send to, accepts absolute and relative paths :return: None """ if path.endswith("/"): path = join(path, self.name) if path.startswith("../"): path = realpath(join(self.directory, path)) elif not path.startswith("/") or path.startswith("./"): path = join(self.directory, path.lstrip('./')) try: dest = join(self._http.webdav_base_url, path.lstrip('/')) resp = self._http.webdav('MOVE', self.path, headers={'Destination': dest}) assert resp.status_code in ( 201, 403), 'Expected 201 (created) status code' self._props["path"] = path self.refresh() except (RequestException, AssertionError) as e: raise StackException(e)
def create_user(self, name: str, username: str, password: str, disk_quota: int = None) -> StackUser: """ Create a STACK user :param name: User to create :param username: Username :param password: Password :param disk_quota: Disk quota in bytes, if set to None user is allowed to use infinite space :return: The newly created stack user """ if self.enforce_password_policy: if len(password) < 8: raise StackException( "Password must be at least 8 characters long!") disk_quota = int(disk_quota) if disk_quota else -1 payload = { "action": "create", "user": { "username": username, "newUser": True, "isPublic": False, "password": password, "displayName": name, "quota": disk_quota } } resp = self.http.post("/api/users/update", json=[payload]) if resp.status_code == 409: raise StackException( "Unable to create user '{}', either you don't have permission " "to do so or the user already exists!".format(username), resp=resp) data = resp.json() if data.get("status") != "ok": raise StackException( "Expected 'ok' status, got response: {}".format(data), resp=resp) return self.user(username)
def __node(self, name: str): """ Get a node by name :param name: Node name to find :return: Node object """ if name.startswith("/"): path = name else: path = join(self.__cwd, name) resp = self.http.get("/api/pathinfo", params={"path": path}) if resp.status_code == 404: raise StackException('No such file or directory.', resp=resp) if resp.status_code != 200: raise StackException(resp.text.strip(), resp=resp) return self.__node_to_object(resp.json())
def directory(self, name: str) -> StackDirectory: """ Get a directory by name :param name: Name to find :return: Directory object """ node = self.__node(name) if isinstance(node, StackFile): raise StackException("Directory '{}' is a file!".format(name)) return node
def file(self, name: str) -> StackFile: """ Get a file by name :param name: Name to find :return: File object """ node = self.__node(name) if isinstance(node, StackDirectory): raise StackException("File '{}' is a directory!".format(name)) return node
def user(self, name: str) -> StackUser: """ Get a user by name :param name: Name to find :return: Stack user """ params = {"public": False, "offset": 0, "limit": 1, "query": name} users = self.http.get("/api/users", params=params) if users.status_code == 403: raise StackException( 'Unable to list users, access denied. ' 'Please log in with the administrator account.', resp=users) data = users.json() if not any(data.get("users") or []): raise StackException("Unable to find user '{}'".format(name), resp=users) return StackUser(stack=self, props=data.get("users", [])[0])
def delete(self): """ Delete the current user :return: None """ data = {"action": "delete", "user": self._props} resp = self._http.post("/api/users/update", json=[data], csrf=True) resp_data = resp.json() if not resp_data.get("status") == "ok": raise StackException( "Unable to delete user '{}', expected status 'ok' " "and got response: {}".format(self.username, resp_data), resp=resp)
def mkdir(self, name: str, path: str = None) -> StackDirectory: """ Make a new directory :param name: Directory name to create :param path: Directory to create it in :return: StackDirectory """ if not path: path = self.__cwd path = join(path, name) try: self.http.webdav('MKCOL', path) return self.directory(path) except RequestException as e: raise StackException(e)
def save(self): """ Save the current user state :return: None """ resp = self._http.post("/api/users/update", json=[{ "action": "update", "user": self._props }], csrf=True) resp_data = resp.json() if resp_data.get("status") != "ok": raise StackException( "Unable to set properties. Expected status 'ok' " "and got response: {}".format(resp_data), resp=resp)
def login(self) -> None: """ Log into STACK :return: None :raises: StackException """ data = {"username": self.__username, "password": self.__password} resp = self.http.post("/login", data=data, allow_redirects=False, csrf=False) self.__logged_in = resp.status_code in (301, 302) if self.__logged_in: logging.debug("Logged in successfully") else: logging.debug( "No redirect detected in login request, invalid password.") raise StackException("Invalid username or password.", resp=resp)
def upload(self, file, *, remote: str = None) -> StackFile: """ Upload a file to Stack :param file: IO pointer or string containing a path to a file :param remote: Custom name which will be used on the remote server, defaults to file.name in the current working directory :return: Instance of a stack file """ if hasattr(file, 'read'): return self.__upload(file=file, remote=remote) if isinstance(file, str): with open(file, "rb") as fd: return self.__upload(file=fd, remote=remote) raise StackException("File should either be a path to a file on " "disk or an IO type, got: {}".format(type(file)))
def walk(self, path: str = None, order: str = "asc") -> Iterable[StackFile]: """ Recursively walk through the entire directory tree, yielding each file in all directories :param path: Starting path to walk :param order: Walk order (asc, desc) :return: Generator of StackFile's """ if order not in ('asc', 'desc'): raise StackException( 'Invalid order: {!r}, accepted values: asc, desc'.format( order)) if not path: path = self.__cwd for node in self.ls(path=path, order=order): if isinstance(node, StackDirectory): yield from self.walk(node.path, order=order) else: yield node