Skip to content

Commit 8928aa4

Browse files
authored
Add ability to list root catalogue for fs_list_directory (#6)
1 parent adee742 commit 8928aa4

File tree

9 files changed

+126
-23
lines changed

9 files changed

+126
-23
lines changed

.gitattributes

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,18 @@
55
js/* binary
66
screenshots/* binary
77
lib/TProto/* binary
8-
img/* binary
8+
*.bin binary
9+
*.heif binary
10+
*.heic binary
11+
*.hif binary
12+
*.avif binary
13+
*.png binary
14+
*.gif binary
15+
*.webp binary
16+
*.tiff binary
17+
*.jpeg binary
18+
*.jpg binary
19+
*.svg binary
920

1021
# Files to exclude from GitHub Languages statistics
1122
*.Dockerfile linguist-vendored=true

.github/workflows/py_analysis-coverage.yml

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,11 @@ jobs:
113113
with:
114114
path: apps/${{ env.APP_NAME }}
115115

116-
- name: Enable App
117-
run: php occ app:enable ${{ env.APP_NAME }}
116+
- name: Enable App & Test Data
117+
run: |
118+
php occ app:enable ${{ env.APP_NAME }}
119+
cp -R apps/${{ env.APP_NAME }}/tests/nc_py_api/test_dir ./data/admin/files/
120+
php occ files:scan admin
118121
119122
- name: Generate coverage report
120123
working-directory: apps/${{ env.APP_NAME }}
@@ -202,8 +205,11 @@ jobs:
202205
with:
203206
path: apps/${{ env.APP_NAME }}
204207

205-
- name: Enable App
206-
run: php occ app:enable ${{ env.APP_NAME }}
208+
- name: Enable App & Test Data
209+
run: |
210+
php occ app:enable ${{ env.APP_NAME }}
211+
cp -R apps/${{ env.APP_NAME }}/tests/nc_py_api/test_dir ./data/admin/files/
212+
php occ files:scan admin
207213
208214
- name: Generate coverage report
209215
working-directory: apps/${{ env.APP_NAME }}
@@ -291,8 +297,11 @@ jobs:
291297
with:
292298
path: apps/${{ env.APP_NAME }}
293299

294-
- name: Enable App
295-
run: php occ app:enable ${{ env.APP_NAME }}
300+
- name: Enable App & Test Data
301+
run: |
302+
php occ app:enable ${{ env.APP_NAME }}
303+
cp -R apps/${{ env.APP_NAME }}/tests/nc_py_api/test_dir ./data/admin/files/
304+
php occ files:scan admin
296305
297306
- name: Generate coverage report
298307
working-directory: apps/${{ env.APP_NAME }}

.pre-commit-config.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,20 @@ repos:
1717
rev: 5.10.1
1818
hooks:
1919
- id: isort
20-
exclude: (^3rdparty)
20+
files: nc_py_api/
2121

2222
- repo: https://github.com/psf/black
2323
rev: 22.10.0
2424
hooks:
2525
- id: black
26-
exclude: (^3rdparty)
26+
files: nc_py_api/
2727

2828
- repo: https://github.com/PyCQA/flake8
2929
rev: 6.0.0
3030
hooks:
3131
- id: flake8
3232
types: [file, python]
33-
exclude: (^3rdparty)
33+
files: nc_py_api/
3434

3535
- repo: https://github.com/pre-commit/mirrors-mypy
3636
rev: v0.991

nc_py_api/db_requests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
)
1212

1313

14-
def get_paths_by_ids(file_ids: list) -> list:
14+
def get_paths_by_ids(file_ids: list[int]) -> list:
1515
"""For each element of list in file_ids return [path, fileid, storage]. Order of file_ids is not preserved."""
1616

1717
query = (

nc_py_api/files.py

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fnmatch import fnmatch
55
from os import environ, path
66
from pathlib import Path
7-
from typing import Literal, Optional, TypedDict
7+
from typing import Literal, Optional, TypedDict, Union
88

99
from . import mimetype
1010
from .config import CONFIG
@@ -62,12 +62,31 @@ def fs_get_objs_info(file_ids: list[int]) -> list[FsNodeInfo]:
6262
return [db_record_to_fs_node(i) for i in raw_result]
6363

6464

65-
def fs_list_directory(file_id: int, user_id=USER_ID) -> list[FsNodeInfo]:
66-
_ = user_id # noqa # will be used in 0.4.0 version
67-
dir_info = get_paths_by_ids([file_id])
65+
def fs_list_directory(file_id: Optional[Union[int, FsNodeInfo]] = None, user_id=USER_ID) -> list[FsNodeInfo]:
66+
"""Get listing of the directory.
67+
68+
:param file_id: `fileid` or :py:data:`FsNodeInfo` of the directory. Can be `None` to list `root` directory.
69+
:param user_id: `uid` of user. Optional, in most cases you should not specify it.
70+
71+
:returns: list of :py:data:`FsNodeInfo` dictionaries."""
72+
73+
storage_id = internal_path = None
74+
if file_id is None: # get user root `files` folder
75+
file_id = get_files_root_node(user_id)
76+
if file_id is None:
77+
return []
78+
if not isinstance(file_id, int): # FsNodeInfo
79+
storage_id = file_id["storageId"]
80+
internal_path = file_id["internal_path"]
81+
file_id = file_id["id"]
82+
else:
83+
dir_info = get_paths_by_ids([file_id])
84+
if dir_info:
85+
storage_id = dir_info[0]["storage"]
86+
internal_path = dir_info[0]["path"]
6887
file_mounts = []
69-
if dir_info:
70-
file_mounts = get_mounts_to(dir_info[0]["storage"], dir_info[0]["path"])
88+
if storage_id and internal_path:
89+
file_mounts = get_mounts_to(storage_id, internal_path)
7190
raw_result = get_directory_list(file_id, file_mounts)
7291
return [db_record_to_fs_node(i) for i in raw_result]
7392

@@ -132,29 +151,36 @@ def fs_get_file_data(file_info: FsNodeInfo) -> bytes:
132151
return request_file_from_php(file_info)
133152

134153

135-
def get_storage_info(storage_id: int) -> dict:
154+
def get_storage_by_id(storage_id: int) -> dict:
136155
for storage_info in STORAGES_INFO:
137156
if storage_info["numeric_id"] == storage_id:
138157
return storage_info
139158
return {}
140159

141160

161+
def get_storage_by_user_id(user_id: str) -> dict:
162+
for storage_info in STORAGES_INFO:
163+
if storage_info["user_id"] == user_id:
164+
return storage_info
165+
return {}
166+
167+
142168
def get_storage_mount_point(storage_id: int) -> str:
143-
storage_info = get_storage_info(storage_id)
169+
storage_info = get_storage_by_id(storage_id)
144170
if storage_info:
145171
return storage_info["mount_point"]
146172
return ""
147173

148174

149175
def get_storage_user_id(storage_id: int) -> str:
150-
storage_info = get_storage_info(storage_id)
176+
storage_info = get_storage_by_id(storage_id)
151177
if storage_info:
152178
return storage_info["user_id"]
153179
return ""
154180

155181

156182
def get_storage_root_id(storage_id: int) -> int:
157-
storage_info = get_storage_info(storage_id)
183+
storage_info = get_storage_by_id(storage_id)
158184
if storage_info:
159185
return storage_info["root_id"]
160186
return 0
@@ -171,7 +197,7 @@ def request_file_from_php(file_info: FsNodeInfo) -> bytes:
171197

172198

173199
def get_file_full_path(storage_id: int, relative_path: str) -> str:
174-
storage_info = get_storage_info(storage_id)
200+
storage_info = get_storage_by_id(storage_id)
175201
if not storage_info:
176202
return ""
177203
path_data = storage_info["id"].split(sep="::", maxsplit=1)
@@ -186,7 +212,7 @@ def get_file_full_path(storage_id: int, relative_path: str) -> str:
186212

187213

188214
def is_local_storage(storage_id: int) -> bool:
189-
storage_info = get_storage_info(storage_id)
215+
storage_info = get_storage_by_id(storage_id)
190216
if not storage_info:
191217
return False
192218
if storage_info["available"] == 0:
@@ -249,3 +275,15 @@ def is_path_in_exclude(fs_path: str, exclude_patterns: list[str]) -> bool:
249275
if fnmatch(name, pattern):
250276
return True
251277
return False
278+
279+
280+
def get_files_root_node(user_id: str) -> Union[FsNodeInfo, None]:
281+
root_id = get_storage_by_user_id(user_id).get("root_id", 0)
282+
if not root_id:
283+
log.debug("can not find storage for specified user: %s", user_id)
284+
return None
285+
for i in get_directory_list(root_id, []):
286+
if i["name"] == "files" and i["mimetype"] == mimetype.DIR:
287+
return db_record_to_fs_node(i)
288+
log.debug("can not find `files` directory inside root_id dir")
289+
return None

tests/nc_py_api/files_test.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import pytest
2+
3+
import nc_py_api
4+
5+
6+
@pytest.mark.parametrize("user_id", ["admin"])
7+
def test_fs_list_directory(user_id):
8+
root_dir_listing = nc_py_api.fs_list_directory(user_id=user_id)
9+
# `Documents`, `Photos`, `Templates`, `test_files` folders
10+
assert len(root_dir_listing) >= 4
11+
assert any(fs_obj["name"] == "Documents" for fs_obj in root_dir_listing)
12+
assert any(fs_obj["name"] == "Photos" for fs_obj in root_dir_listing)
13+
assert any(fs_obj["name"] == "Templates" for fs_obj in root_dir_listing)
14+
assert any(fs_obj["name"] == "test_dir" for fs_obj in root_dir_listing)
15+
test_dir = [fs_obj for fs_obj in root_dir_listing if fs_obj["name"] == "test_dir"][0]
16+
assert test_dir["is_dir"]
17+
assert test_dir["is_local"]
18+
assert test_dir["mimetype"] == nc_py_api.mimetype.DIR
19+
assert test_dir["mimepart"] == nc_py_api.get_mimetype_id("httpd")
20+
assert test_dir["internal_path"] == "files/test_dir"
21+
assert test_dir["permissions"] == 31
22+
assert test_dir["ownerName"] == user_id
23+
test_dir_listing = nc_py_api.fs_list_directory(test_dir["id"])
24+
assert test_dir_listing == nc_py_api.fs_list_directory(test_dir) # results should be the same
25+
empty_dir = [fs_obj for fs_obj in test_dir_listing if fs_obj["name"] == "empty_dir"][0]
26+
assert empty_dir["size"] == 0
27+
# directory should be with one empty file
28+
assert len(nc_py_api.fs_list_directory(empty_dir)) == 1 # pass FsNodeInfo as fileid
29+
assert len(nc_py_api.fs_list_directory(empty_dir["id"])) == 1 # pass fileid as fileid
30+
hopper_img = [fs_obj for fs_obj in test_dir_listing if fs_obj["name"] == "hopper.png"][0]
31+
assert not hopper_img["is_dir"]
32+
assert hopper_img["is_local"]
33+
assert hopper_img["mimetype"] == nc_py_api.get_mimetype_id("image/png")
34+
assert hopper_img["mimepart"] == nc_py_api.mimetype.IMAGE
35+
assert hopper_img["internal_path"] == "files/test_dir/hopper.png"
36+
assert hopper_img["permissions"] == 27
37+
assert hopper_img["ownerName"] == user_id
38+
# probably tests should be divided into smaller parts, need api for getting FsNode by internal_path + user_id...

tests/nc_py_api/occ_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from unittest import mock
23

34
import nc_py_api
@@ -8,7 +9,9 @@ def test_occ_call():
89

910

1011
def test_occ_call_invalid_command():
12+
logging.disable(logging.CRITICAL)
1113
assert nc_py_api.occ_call("invalid command") is None
14+
logging.disable(logging.NOTSET)
1215

1316

1417
def test_occ_call_with_param():
@@ -20,7 +23,9 @@ def test_occ_call_decode():
2023

2124

2225
def test_occ_call_decode_invalid_command():
26+
logging.disable(logging.CRITICAL)
2327
assert nc_py_api.occ_call_decode("invalid command") is None
28+
logging.disable(logging.NOTSET)
2429

2530

2631
def test_occ_call_decode_with_param():
@@ -32,7 +37,9 @@ def test_get_cloud_app_config_value():
3237

3338

3439
def test_get_cloud_app_config_invalid_name():
40+
logging.disable(logging.CRITICAL)
3541
assert nc_py_api.get_cloud_app_config_value("core", "invalid_name") is None
42+
logging.disable(logging.NOTSET)
3643

3744

3845
def test_get_cloud_app_config_default_value():

tests/nc_py_api/test_dir/empty_dir/empty_file.bin

Whitespace-only changes.
29.9 KB
Loading

0 commit comments

Comments
 (0)