sync: auto-sync from HOWARD-HOME at 2026-07-05 19:59:52
Author: Howard Enos Machine: HOWARD-HOME Timestamp: 2026-07-05 19:59:52
This commit is contained in:
@@ -174,6 +174,111 @@ class SeafileClient:
|
||||
devices = [d for d in devices if d.get("user") == user_email]
|
||||
return devices
|
||||
|
||||
# -- write plumbing --------------------------------------------------------
|
||||
def _write(self, method: str, path: str, form: Optional[dict] = None,
|
||||
retry_auth: bool = True) -> Any:
|
||||
"""POST/PUT/DELETE against the admin API using form-encoding.
|
||||
|
||||
The Seafile admin endpoints read from request.data; the share endpoints
|
||||
use QueryDict.getlist('share_to'), which only works for form-encoded
|
||||
bodies (not JSON) -- so all writes go out as
|
||||
application/x-www-form-urlencoded. `form` values that are lists are
|
||||
expanded (doseq) into repeated fields.
|
||||
"""
|
||||
url = f"{self.base}{path}"
|
||||
data = urllib.parse.urlencode(form or {}, doseq=True).encode()
|
||||
req = bc.urllib.request.Request(
|
||||
url, data=data, method=method,
|
||||
headers={"Authorization": f"Token {self._tok()}",
|
||||
"Content-Type": "application/x-www-form-urlencoded"})
|
||||
try:
|
||||
with bc.urllib.request.urlopen(req, timeout=bc.HTTP_TIMEOUT) as resp:
|
||||
status = resp.getcode()
|
||||
raw = resp.read().decode("utf-8", errors="replace")
|
||||
except bc.urllib.error.HTTPError as exc:
|
||||
status = exc.code
|
||||
raw = exc.read().decode("utf-8", errors="replace")
|
||||
except bc.urllib.error.URLError as exc:
|
||||
raise bc.BackupError(f"Seafile {method} {path} failed: {exc}")
|
||||
if status in (401, 403) and retry_auth:
|
||||
self._token = self._authorize()
|
||||
return self._write(method, path, form, retry_auth=False)
|
||||
try:
|
||||
parsed = json.loads(raw) if raw.strip() else {}
|
||||
except json.JSONDecodeError:
|
||||
parsed = {"_raw": raw[:600]}
|
||||
if status not in (200, 201):
|
||||
msg = parsed.get("error_msg") or parsed.get("detail") or raw[:400]
|
||||
raise bc.BackupError(f"Seafile {method} {path} failed (HTTP {status}): {msg}",
|
||||
status=status)
|
||||
return parsed
|
||||
|
||||
@staticmethod
|
||||
def _uenc(email: str) -> str:
|
||||
return urllib.parse.quote(email, safe="")
|
||||
|
||||
# -- user writes -----------------------------------------------------------
|
||||
def create_user(self, email: str, password: str, name: Optional[str] = None,
|
||||
quota_mb: Optional[int] = None, role: Optional[str] = None,
|
||||
is_active: bool = True) -> dict:
|
||||
form: dict = {"email": email, "password": password,
|
||||
"is_active": "true" if is_active else "false"}
|
||||
if name:
|
||||
form["name"] = name
|
||||
if quota_mb is not None:
|
||||
form["quota_total"] = str(int(quota_mb))
|
||||
if role:
|
||||
form["role"] = role
|
||||
return self._write("POST", "/api/v2.1/admin/users/", form)
|
||||
|
||||
def update_user(self, email: str, is_active: Optional[bool] = None,
|
||||
password: Optional[str] = None, name: Optional[str] = None,
|
||||
quota_mb: Optional[int] = None, role: Optional[str] = None) -> dict:
|
||||
form: dict = {}
|
||||
if is_active is not None:
|
||||
form["is_active"] = "true" if is_active else "false"
|
||||
if password is not None:
|
||||
form["password"] = password
|
||||
if name is not None:
|
||||
form["name"] = name
|
||||
if quota_mb is not None:
|
||||
form["quota_total"] = str(int(quota_mb))
|
||||
if role is not None:
|
||||
form["role"] = role
|
||||
if not form:
|
||||
raise bc.BackupError("update_user: nothing to change")
|
||||
return self._write("PUT", f"/api/v2.1/admin/users/{self._uenc(email)}/", form)
|
||||
|
||||
def delete_user(self, email: str) -> dict:
|
||||
return self._write("DELETE", f"/api/v2.1/admin/users/{self._uenc(email)}/")
|
||||
|
||||
# -- library writes --------------------------------------------------------
|
||||
def create_library(self, name: str, owner: Optional[str] = None) -> dict:
|
||||
form: dict = {"name": name}
|
||||
if owner:
|
||||
form["owner"] = owner
|
||||
return self._write("POST", "/api/v2.1/admin/libraries/", form)
|
||||
|
||||
def transfer_library(self, repo_id: str, new_owner: str) -> dict:
|
||||
return self._write("PUT", f"/api/v2.1/admin/libraries/{repo_id}/",
|
||||
{"owner": new_owner})
|
||||
|
||||
def delete_library(self, repo_id: str) -> dict:
|
||||
return self._write("DELETE", f"/api/v2.1/admin/libraries/{repo_id}/")
|
||||
|
||||
# -- sharing writes --------------------------------------------------------
|
||||
def share_library(self, repo_id: str, share_to, permission: str = "rw",
|
||||
share_type: str = "user") -> dict:
|
||||
recipients = share_to if isinstance(share_to, list) else [share_to]
|
||||
form = {"share_type": share_type, "permission": permission,
|
||||
"share_to": recipients}
|
||||
return self._write("POST", f"/api/v2.1/admin/libraries/{repo_id}/shares/", form)
|
||||
|
||||
def unshare_library(self, repo_id: str, share_to: str,
|
||||
share_type: str = "user") -> dict:
|
||||
return self._write("DELETE", f"/api/v2.1/admin/libraries/{repo_id}/shares/",
|
||||
{"share_type": share_type, "share_to": share_to})
|
||||
|
||||
|
||||
def main() -> int:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user