Skip to content

Commit 0d73cdb

Browse files
authored
New download_log method (#199)
Ref: https://docs.nextcloud.com/server/latest/developer_manual/_static/openapi.html#/operations/settings-log_settings-download Signed-off-by: Alexander Piskun <bigcat88@icloud.com>
1 parent b9d6050 commit 0d73cdb

File tree

5 files changed

+62
-31
lines changed

5 files changed

+62
-31
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ All notable changes to this project will be documented in this file.
66

77
### Added
88

9+
- `download_log` method to download `nextcloud.log`. #199
910
- NextcloudApp: API for registering Speech to Text providers(*avalaible from Nextcloud 29*). #196
10-
- NextcloudApp: API for registering Text Processing providers(*avalaible from Nextcloud 29*). #197
11+
- NextcloudApp: API for registering Text Processing providers(*avalaible from Nextcloud 29*). #198
1112

1213
### Fixed
1314

nc_py_api/_session.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Session represents one connection to Nextcloud. All related stuff for these live here."""
22

3+
import builtins
4+
import pathlib
35
import re
46
import typing
57
from abc import ABC, abstractmethod
@@ -250,6 +252,15 @@ def user(self) -> str:
250252
def set_user(self, user_id: str) -> None:
251253
self._user = user_id
252254

255+
def download2stream(self, url_path: str, fp, dav: bool = False, **kwargs):
256+
if isinstance(fp, str | pathlib.Path):
257+
with builtins.open(fp, "wb") as f:
258+
self._download2fp(url_path, f, dav, **kwargs)
259+
elif hasattr(fp, "write"):
260+
self._download2fp(url_path, fp, dav, **kwargs)
261+
else:
262+
raise TypeError("`fp` must be a path to file or an object with `write` method.")
263+
253264
def _get_adapter_kwargs(self, dav: bool) -> dict[str, typing.Any]:
254265
if dav:
255266
return {
@@ -276,6 +287,13 @@ def _response_event(self, response: Response) -> None:
276287
return
277288
self.response_headers = response.headers
278289

290+
def _download2fp(self, url_path: str, fp, dav: bool, **kwargs):
291+
adapter = self.adapter_dav if dav else self.adapter
292+
with adapter.stream("GET", url_path) as response:
293+
check_error(response)
294+
for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
295+
fp.write(data_chunk)
296+
279297

280298
class AsyncNcSessionBasic(NcSessionBase, ABC):
281299
adapter: AsyncClient
@@ -354,6 +372,15 @@ async def user(self) -> str:
354372
def set_user(self, user: str) -> None:
355373
self._user = user
356374

375+
async def download2stream(self, url_path: str, fp, dav: bool = False, **kwargs):
376+
if isinstance(fp, str | pathlib.Path):
377+
with builtins.open(fp, "wb") as f:
378+
await self._download2fp(url_path, f, dav, **kwargs)
379+
elif hasattr(fp, "write"):
380+
await self._download2fp(url_path, fp, dav, **kwargs)
381+
else:
382+
raise TypeError("`fp` must be a path to file or an object with `write` method.")
383+
357384
def _get_adapter_kwargs(self, dav: bool) -> dict[str, typing.Any]:
358385
if dav:
359386
return {
@@ -380,6 +407,13 @@ async def _response_event(self, response: Response) -> None:
380407
return
381408
self.response_headers = response.headers
382409

410+
async def _download2fp(self, url_path: str, fp, dav: bool, **kwargs):
411+
adapter = self.adapter_dav if dav else self.adapter
412+
async with adapter.stream("GET", url_path) as response:
413+
check_error(response)
414+
async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
415+
fp.write(data_chunk)
416+
383417

384418
class NcSession(NcSessionBasic):
385419
cfg: Config

nc_py_api/files/files.py

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,8 @@ def download2stream(self, path: str | FsNode, fp, **kwargs) -> None:
9898
The object must implement the ``file.write`` method and be able to write binary data.
9999
:param kwargs: **chunk_size** an int value specifying chunk size to write. Default = **5Mb**
100100
"""
101-
path = path.user_path if isinstance(path, FsNode) else path
102-
if isinstance(fp, str | Path):
103-
with builtins.open(fp, "wb") as f:
104-
self.__download2stream(path, f, **kwargs)
105-
elif hasattr(fp, "write"):
106-
self.__download2stream(path, fp, **kwargs)
107-
else:
108-
raise TypeError("`fp` must be a path to file or an object with `write` method.")
101+
path = quote(dav_get_obj_path(self._session.user, path.user_path if isinstance(path, FsNode) else path))
102+
self._session.download2stream(path, fp, dav=True, **kwargs)
109103

110104
def download_directory_as_zip(self, path: str | FsNode, local_path: str | Path | None = None, **kwargs) -> Path:
111105
"""Downloads a remote directory as zip archive.
@@ -439,12 +433,6 @@ def _listdir(
439433
self._session.cfg.dav_url_suffix, webdav_response, user, path, properties, exclude_self, prop_type
440434
)
441435

442-
def __download2stream(self, path: str, fp, **kwargs) -> None:
443-
with self._session.adapter_dav.stream("GET", quote(dav_get_obj_path(self._session.user, path))) as response:
444-
check_error(response, f"download_stream: user={self._session.user}, path={path}")
445-
for data_chunk in response.iter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
446-
fp.write(data_chunk)
447-
448436
def __upload_stream(self, path: str, fp, chunk_size: int) -> FsNode:
449437
_tmp_path = "nc-py-api-" + random_string(56)
450438
_dav_path = quote(dav_get_obj_path(self._session.user, _tmp_path, root_path="/uploads"))
@@ -561,14 +549,8 @@ async def download2stream(self, path: str | FsNode, fp, **kwargs) -> None:
561549
The object must implement the ``file.write`` method and be able to write binary data.
562550
:param kwargs: **chunk_size** an int value specifying chunk size to write. Default = **5Mb**
563551
"""
564-
path = path.user_path if isinstance(path, FsNode) else path
565-
if isinstance(fp, str | Path):
566-
with builtins.open(fp, "wb") as f:
567-
await self.__download2stream(path, f, **kwargs)
568-
elif hasattr(fp, "write"):
569-
await self.__download2stream(path, fp, **kwargs)
570-
else:
571-
raise TypeError("`fp` must be a path to file or an object with `write` method.")
552+
path = quote(dav_get_obj_path(await self._session.user, path.user_path if isinstance(path, FsNode) else path))
553+
await self._session.download2stream(path, fp, dav=True, **kwargs)
572554

573555
async def download_directory_as_zip(
574556
self, path: str | FsNode, local_path: str | Path | None = None, **kwargs
@@ -909,14 +891,6 @@ async def _listdir(
909891
self._session.cfg.dav_url_suffix, webdav_response, user, path, properties, exclude_self, prop_type
910892
)
911893

912-
async def __download2stream(self, path: str, fp, **kwargs) -> None:
913-
async with self._session.adapter_dav.stream(
914-
"GET", quote(dav_get_obj_path(await self._session.user, path))
915-
) as response:
916-
check_error(response, f"download_stream: user={await self._session.user}, path={path}")
917-
async for data_chunk in response.aiter_raw(chunk_size=kwargs.get("chunk_size", 5 * 1024 * 1024)):
918-
fp.write(data_chunk)
919-
920894
async def __upload_stream(self, path: str, fp, chunk_size: int) -> FsNode:
921895
_tmp_path = "nc-py-api-" + random_string(56)
922896
_dav_path = quote(dav_get_obj_path(await self._session.user, _tmp_path, root_path="/uploads"))

nc_py_api/nextcloud.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ def ocs(
135135
"""Performs OCS call and returns OCS response payload data."""
136136
return self._session.ocs(method, path, content=content, json=json, params=params, **kwargs)
137137

138+
def download_log(self, fp) -> None:
139+
"""Downloads Nextcloud log file. Requires Admin privileges."""
140+
self._session.download2stream("/index.php/settings/admin/log/download", fp)
141+
138142

139143
class _AsyncNextcloudBasic(ABC): # pylint: disable=too-many-instance-attributes
140144
apps: _AsyncAppsAPI
@@ -229,6 +233,10 @@ async def ocs(
229233
"""Performs OCS call and returns OCS response payload data."""
230234
return await self._session.ocs(method, path, content=content, json=json, params=params, **kwargs)
231235

236+
async def download_log(self, fp) -> None:
237+
"""Downloads Nextcloud log file. Requires Admin privileges."""
238+
await self._session.download2stream("/index.php/settings/admin/log/download", fp)
239+
232240

233241
class Nextcloud(_NextcloudBasic):
234242
"""Nextcloud client class.

tests/actual_tests/misc_test.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import datetime
2+
import io
23
import os
34

45
import pytest
@@ -206,3 +207,16 @@ async def test_perform_login_async(anc_any):
206207
assert not new_nc._session._capabilities
207208
await new_nc.perform_login()
208209
assert new_nc._session._capabilities
210+
211+
212+
def test_download_log(nc_any):
213+
buf = io.BytesIO()
214+
nc_any.download_log(buf)
215+
assert buf.tell() > 0
216+
217+
218+
@pytest.mark.asyncio(scope="session")
219+
async def test_download_log_async(anc_any):
220+
buf = io.BytesIO()
221+
await anc_any.download_log(buf)
222+
assert buf.tell() > 0

0 commit comments

Comments
 (0)