-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* list folders and files, uoload file * increase timeout to max 12h * no need for special post_upload function * params has always keys in _execute_request() * allow to use raw response content (StreamReader) * fix * add download_file, improve upload_file * add delete_file * small things * add tests * add docs * extend docs * add create_parents to upload
- Loading branch information
Showing
11 changed files
with
771 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
"""Synology FileStation API wrapper.""" | ||
|
||
from __future__ import annotations | ||
|
||
from collections.abc import AsyncIterator | ||
from io import BufferedReader | ||
|
||
from aiohttp import StreamReader | ||
|
||
from synology_dsm.api import SynoBaseApi | ||
|
||
from .models import ( | ||
SynoFileAdditionalOwner, | ||
SynoFileFile, | ||
SynoFileFileAdditional, | ||
SynoFileFileAdditionalPermission, | ||
SynoFileFileAdditionalTime, | ||
SynoFileSharedFolder, | ||
SynoFileSharedFolderAdditional, | ||
SynoFileSharedFolderAdditionalPermission, | ||
SynoFileSharedFolderAdditionalVolumeStatus, | ||
) | ||
|
||
|
||
class SynoFileStation(SynoBaseApi): | ||
"""An implementation of a Synology FileStation.""" | ||
|
||
API_KEY = "SYNO.FileStation.*" | ||
LIST_API_KEY = "SYNO.FileStation.List" | ||
DOWNLOAD_API_KEY = "SYNO.FileStation.Download" | ||
UPLOAD_API_KEY = "SYNO.FileStation.Upload" | ||
DELETE_API_KEY = "SYNO.FileStation.Delete" | ||
|
||
async def get_shared_folders( | ||
self, offset: int = 0, limit: int = 100, only_writable: bool = False | ||
) -> list[SynoFileSharedFolder] | None: | ||
"""Get a list of all shared folders.""" | ||
raw_data = await self._dsm.get( | ||
self.LIST_API_KEY, | ||
"list_share", | ||
{ | ||
"offset": offset, | ||
"limit": limit, | ||
"onlywritable": only_writable, | ||
"additional": ( | ||
'["real_path","owner","time","perm",' | ||
'"mount_point_type","sync_share","volume_status"]' | ||
), | ||
}, | ||
) | ||
if not isinstance(raw_data, dict) or (data := raw_data.get("data")) is None: | ||
return None | ||
|
||
shared_folders: list[SynoFileSharedFolder] = [] | ||
for folder in data["shares"]: | ||
additional = folder["additional"] | ||
shared_folders.append( | ||
SynoFileSharedFolder( | ||
SynoFileSharedFolderAdditional( | ||
additional["mount_point_type"], | ||
SynoFileAdditionalOwner(**additional["owner"]), | ||
SynoFileSharedFolderAdditionalPermission(**additional["perm"]), | ||
SynoFileSharedFolderAdditionalVolumeStatus( | ||
**additional["volume_status"], | ||
), | ||
), | ||
folder["isdir"], | ||
folder["name"], | ||
folder["path"], | ||
) | ||
) | ||
|
||
return shared_folders | ||
|
||
async def get_files( | ||
self, path: str, offset: int = 0, limit: int = 100 | ||
) -> list[SynoFileFile] | None: | ||
"""Get a list of all files in a folder.""" | ||
raw_data = await self._dsm.get( | ||
self.LIST_API_KEY, | ||
"list", | ||
{ | ||
"offset": offset, | ||
"limit": limit, | ||
"folder_path": path, | ||
"additional": ( | ||
'["real_path","owner","time","perm",' | ||
'"mount_point_type","type","size"]' | ||
), | ||
}, | ||
) | ||
if not isinstance(raw_data, dict) or (data := raw_data.get("data")) is None: | ||
return None | ||
|
||
files: list[SynoFileFile] = [] | ||
for file in data["files"]: | ||
additional = file["additional"] | ||
files.append( | ||
SynoFileFile( | ||
SynoFileFileAdditional( | ||
additional["mount_point_type"], | ||
SynoFileAdditionalOwner(**additional["owner"]), | ||
SynoFileFileAdditionalPermission(**additional["perm"]), | ||
additional["real_path"], | ||
additional["size"], | ||
SynoFileFileAdditionalTime(**additional["time"]), | ||
additional["type"], | ||
), | ||
file["isdir"], | ||
file["name"], | ||
file["path"], | ||
) | ||
) | ||
|
||
return files | ||
|
||
async def upload_file( | ||
self, | ||
path: str, | ||
filename: str, | ||
source: bytes | BufferedReader | AsyncIterator[bytes] | str, | ||
create_parents: bool = False, | ||
) -> bool | None: | ||
"""Upload a file to a folder from eather a local source_file or content.""" | ||
if isinstance(source, str): | ||
source = open(source, "rb") | ||
|
||
raw_data = await self._dsm.post( | ||
self.UPLOAD_API_KEY, | ||
"upload", | ||
path=path, | ||
filename=filename, | ||
content=source, | ||
create_parents=create_parents, | ||
) | ||
if not isinstance(raw_data, dict): | ||
return None | ||
return raw_data.get("success") | ||
|
||
async def download_file( | ||
self, path: str, filename: str, target_file: str | None = None | ||
) -> StreamReader | bool | None: | ||
"""Download a file to local target_file or returns an async StreamReader.""" | ||
response_content = await self._dsm.get( | ||
self.DOWNLOAD_API_KEY, | ||
"download", | ||
{"path": f"{path}/{filename}", "mode": "download"}, | ||
raw_response_content=True, | ||
) | ||
if not isinstance(response_content, StreamReader): | ||
return None | ||
|
||
if target_file: | ||
with open(target_file, "wb") as fh: | ||
async for data in response_content.iter_chunked(8192): | ||
fh.write(data) | ||
return True | ||
|
||
return response_content | ||
|
||
async def delete_file(self, path: str, filename: str) -> bool | None: | ||
"""Delete a file.""" | ||
raw_data = await self._dsm.get( | ||
self.DELETE_API_KEY, | ||
"delete", | ||
{"path": f"{path}/{filename}", "recursive": False}, | ||
) | ||
if not isinstance(raw_data, dict): | ||
return None | ||
return raw_data.get("success") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
"""Data models for Synology FileStation Module.""" | ||
|
||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
|
||
# ------------------------------------- | ||
# generic additional data | ||
# ------------------------------------- | ||
|
||
|
||
@dataclass | ||
class SynoFileAdditionalOwner: | ||
"""Representation of an Synology FileStation additionl owner data.""" | ||
|
||
gid: int | ||
group: str | ||
uid: int | ||
user: str | ||
|
||
|
||
# ------------------------------------- | ||
# shared folder | ||
# ------------------------------------- | ||
|
||
|
||
@dataclass | ||
class SynoFileSharedFolderAdditionalPermission: | ||
"""Representation of an Synology FileStation additionl permission data.""" | ||
|
||
acl: dict | ||
acl_enable: bool | ||
adv_right: dict | ||
is_acl_mode: bool | ||
is_share_readonly: bool | ||
posix: int | ||
share_right: str | ||
|
||
|
||
@dataclass | ||
class SynoFileSharedFolderAdditionalVolumeStatus: | ||
"""Representation of an Synology FileStation additionl permission data.""" | ||
|
||
freespace: int | ||
totalspace: int | ||
readonly: bool | ||
|
||
|
||
@dataclass | ||
class SynoFileSharedFolderAdditional: | ||
"""Representation of an Synology FileStation Shared Folder additionl data.""" | ||
|
||
mount_point_type: str | ||
owner: SynoFileAdditionalOwner | ||
perm: SynoFileSharedFolderAdditionalPermission | ||
volume_status: SynoFileSharedFolderAdditionalVolumeStatus | ||
|
||
|
||
@dataclass | ||
class SynoFileSharedFolder: | ||
"""Representation of an Synology FileStation Shared Folder.""" | ||
|
||
additional: SynoFileSharedFolderAdditional | ||
is_dir: bool | ||
name: str | ||
path: str | ||
|
||
|
||
# ------------------------------------- | ||
# file | ||
# ------------------------------------- | ||
|
||
|
||
@dataclass | ||
class SynoFileFileAdditionalPermission: | ||
"""Representation of an Synology FileStation additionl permission data.""" | ||
|
||
acl: dict | ||
is_acl_mode: bool | ||
posix: int | ||
|
||
|
||
@dataclass | ||
class SynoFileFileAdditionalTime: | ||
"""Representation of an Synology FileStation additionl permission data.""" | ||
|
||
atime: int | ||
ctime: int | ||
crtime: int | ||
mtime: int | ||
|
||
|
||
@dataclass | ||
class SynoFileFileAdditional: | ||
"""Representation of an Synology FileStation File additionl data.""" | ||
|
||
mount_point_type: str | ||
owner: SynoFileAdditionalOwner | ||
perm: SynoFileFileAdditionalPermission | ||
real_path: str | ||
size: int | ||
time: SynoFileFileAdditionalTime | ||
type: str | ||
|
||
|
||
@dataclass | ||
class SynoFileFile: | ||
"""Representation of an Synology FileStation File.""" | ||
|
||
additional: SynoFileFileAdditional | ||
is_dir: bool | ||
name: str | ||
path: str |
Oops, something went wrong.