lfss 0.12.3__py3-none-any.whl → 0.13.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lfss/svc/app_native.py CHANGED
@@ -2,7 +2,7 @@ from typing import Optional, Literal, Annotated
2
2
  from collections import OrderedDict
3
3
 
4
4
  from fastapi import Depends, Request, Response, UploadFile, Query
5
- from fastapi.responses import StreamingResponse, JSONResponse
5
+ from fastapi.responses import StreamingResponse, JSONResponse, RedirectResponse
6
6
  from fastapi.exceptions import HTTPException
7
7
 
8
8
  from ..eng.utils import ensure_uri_components
@@ -16,7 +16,7 @@ from ..eng.datatype import (
16
16
  from ..eng.error import InvalidPathError
17
17
 
18
18
  from .app_base import *
19
- from .common_impl import get_impl, put_file_impl, post_file_impl, delete_impl, copy_impl
19
+ from .common_impl import get_impl, put_file_impl, post_file_impl, delete_impl, copy_impl, move_impl
20
20
 
21
21
  @router_fs.get("/{path:path}")
22
22
  @handle_exception
@@ -139,6 +139,7 @@ async def get_file_meta(path: str, user: UserRecord = Depends(registered_user)):
139
139
  record = await fconn.get_dir_record(path)
140
140
  return record
141
141
 
142
+ # TODO: will remove in next major version, use /move and /set-perm instead
142
143
  @router_api.post("/meta")
143
144
  @handle_exception
144
145
  async def update_file_meta(
@@ -147,6 +148,7 @@ async def update_file_meta(
147
148
  new_path: Optional[str] = None,
148
149
  user: UserRecord = Depends(registered_user)
149
150
  ):
151
+ logger.warning("POST /meta is deprecated and will be removed in next major version")
150
152
  path = ensure_uri_components(path)
151
153
  if path.startswith("/"):
152
154
  path = path[1:]
@@ -162,18 +164,13 @@ async def update_file_meta(
162
164
  )
163
165
 
164
166
  if new_path is not None:
165
- new_path = ensure_uri_components(new_path)
166
- logger.info(f"Update path of {path} to {new_path}")
167
- await db.move_file(path, new_path, user)
167
+ return await move_impl(user, path, new_path)
168
168
 
169
169
  # directory
170
170
  else:
171
171
  assert perm is None, "Permission is not supported for directory"
172
172
  if new_path is not None:
173
- new_path = ensure_uri_components(new_path)
174
- logger.info(f"Update path of {path} to {new_path}")
175
- # will raise duplicate path error if same name path exists in the new path
176
- await db.move_dir(path, new_path, user)
173
+ return await move_impl(user, path, new_path)
177
174
 
178
175
  return Response(status_code=200, content="OK")
179
176
 
@@ -185,15 +182,27 @@ async def copy_file(
185
182
  ):
186
183
  return await copy_impl(src_path = src, dst_path = dst, op_user = user)
187
184
 
188
- @router_api.get("/list-peers")
185
+ @router_api.post("/move")
189
186
  @handle_exception
190
- async def list_peers(user: UserRecord = Depends(registered_user), level: AccessLevel = AccessLevel.READ, incoming: bool = False):
191
- async with unique_cursor() as conn:
192
- uconn = UserConn(conn)
193
- peer_users = await uconn.list_peer_users(user.id, level, incoming=incoming)
194
- for u in peer_users:
195
- u.credential = "__HIDDEN__"
196
- return peer_users
187
+ async def move_file(
188
+ src: str, dst: str,
189
+ user: UserRecord = Depends(registered_user)
190
+ ):
191
+ return await move_impl(src_path = src, dst_path = dst, op_user = user)
192
+
193
+ @router_api.post("/set-perm")
194
+ @handle_exception
195
+ async def set_permission(
196
+ path: str, perm: int, user: UserRecord = Depends(registered_user)
197
+ ):
198
+ if path.endswith("/"):
199
+ raise HTTPException(status_code=400, detail="Path must not end with /")
200
+ await db.update_file_record(
201
+ url = ensure_uri_components(path),
202
+ permission = FileReadPermission(perm),
203
+ op_user = user,
204
+ )
205
+ return Response(status_code=200, content="OK")
197
206
 
198
207
  async def validate_path_read_permission(path: str, user: UserRecord):
199
208
  if not path.endswith("/"):
@@ -289,12 +298,17 @@ async def get_multiple_files(
289
298
  status_code = 206 if partial_content else 200
290
299
  )
291
300
 
292
-
301
+
302
+ # --------- Backward compatibility API ---------
293
303
  @router_api.get("/whoami")
294
304
  @handle_exception
295
- async def whoami(user: UserRecord = Depends(registered_user)):
296
- user.credential = "__HIDDEN__"
297
- return user
305
+ async def whoami(r: Request):
306
+ return RedirectResponse(url=f"/_api/user/whoami?{r.url.query}")
307
+
308
+ @router_api.get("/list-peers")
309
+ @handle_exception
310
+ async def list_peers(r: Request):
311
+ return RedirectResponse(url=f"/_api/user/list-peers?{r.url.query}")
298
312
 
299
313
  __all__ = [
300
314
  "app", "router_api", "router_fs"
@@ -0,0 +1,28 @@
1
+ from fastapi import Depends
2
+
3
+ from .app_base import *
4
+ from ..eng.datatype import UserRecord, AccessLevel
5
+ from ..eng.database import unique_cursor, UserConn, FileConn
6
+
7
+ @router_user.get("/whoami")
8
+ @handle_exception
9
+ async def whoami(user: UserRecord = Depends(registered_user)):
10
+ return user.desensitize()
11
+
12
+ @router_user.get("/storage")
13
+ @handle_exception
14
+ async def user_storage(user: UserRecord = Depends(registered_user)):
15
+ async with unique_cursor() as conn:
16
+ fconn = FileConn(conn)
17
+ return {
18
+ "quota": user.max_storage,
19
+ "used": await fconn.user_size(user.id)
20
+ }
21
+
22
+ @router_user.get("/list-peers")
23
+ @handle_exception
24
+ async def list_peers(user: UserRecord = Depends(registered_user), level: AccessLevel = AccessLevel.READ, incoming: bool = False):
25
+ async with unique_cursor() as conn:
26
+ uconn = UserConn(conn)
27
+ peer_users = await uconn.list_peer_users(user.id, level, incoming=incoming)
28
+ return [u.desensitize() for u in peer_users]
lfss/svc/common_impl.py CHANGED
@@ -150,7 +150,10 @@ async def _get_dir_impl(
150
150
  thumb: bool = False,
151
151
  is_head = False,
152
152
  ):
153
- """ handle directory query, return file under the path as json """
153
+ """
154
+ handle directory query, return file under the path as json
155
+ TODO: will change the behavior at next (major) version update
156
+ """
154
157
  assert path.endswith("/")
155
158
  async with unique_cursor() as cur:
156
159
  fconn = FileConn(cur)
@@ -180,7 +183,7 @@ async def _get_dir_impl(
180
183
  else:
181
184
  raise HTTPException(status_code=404, detail="User not found")
182
185
  else:
183
- if await FileConn(cur).count_dir_files(path, flat=True) > 0:
186
+ if await fconn.is_dir_exist(path):
184
187
  return Response(status_code=200)
185
188
  else:
186
189
  raise HTTPException(status_code=404, detail="Path not found")
@@ -320,15 +323,33 @@ async def copy_impl(
320
323
  if copy_type == "file":
321
324
  async with unique_cursor() as cur:
322
325
  fconn = FileConn(cur)
323
- dst_record = await fconn.get_file_record(dst_path)
324
- if dst_record:
325
- raise HTTPException(status_code=409, detail="Destination exists")
326
+ if await fconn.get_file_record(dst_path, throw=False) is not None:
327
+ raise HTTPException(status_code=409, detail="Destination exists")
326
328
  await db.copy_file(src_path, dst_path, op_user)
327
329
  else:
328
330
  async with unique_cursor() as cur:
329
331
  fconn = FileConn(cur)
330
- dst_fcount = await fconn.count_dir_files(dst_path, flat=True)
331
- if dst_fcount > 0:
332
- raise HTTPException(status_code=409, detail="Destination exists")
332
+ if await fconn.is_dir_exist(dst_path):
333
+ raise HTTPException(status_code=409, detail="Destination exists")
333
334
  await db.copy_dir(src_path, dst_path, op_user)
334
- return Response(status_code=201, content="OK")
335
+ return Response(status_code=201, content="OK")
336
+
337
+ async def move_impl(
338
+ op_user: UserRecord, src_path: str, dst_path: str,
339
+ ):
340
+ src_path = ensure_uri_components(src_path)
341
+ dst_path = ensure_uri_components(dst_path)
342
+
343
+ is_file = not src_path.endswith("/")
344
+ if (src_path[-1] == "/") != (dst_path[-1] == "/"):
345
+ raise HTTPException(status_code=400, detail="Source and destination must be same type")
346
+
347
+ if src_path == dst_path:
348
+ raise HTTPException(status_code=400, detail="Source and destination are the same")
349
+
350
+ logger.info(f"Move {src_path} to {dst_path}, user: {op_user.username}")
351
+ if is_file:
352
+ await db.move_file(src_path, dst_path, op_user)
353
+ else:
354
+ await db.move_dir(src_path, dst_path, op_user)
355
+ return Response(status_code=200, content="OK")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lfss
3
- Version: 0.12.3
3
+ Version: 0.13.0
4
4
  Summary: Lite file storage service
5
5
  Home-page: https://github.com/MenxLi/lfss
6
6
  Author: Li, Mengxun
@@ -4,27 +4,32 @@ docs/Enviroment_variables.md,sha256=CZ5DrrXSLU5RLBEVQ-gLMaOIuFthd7dEiTzO7ODrPRQ,
4
4
  docs/Known_issues.md,sha256=ZqETcWP8lzTOel9b2mxEgCnADFF8IxOrEtiVO1NoMAk,251
5
5
  docs/Permission.md,sha256=thUJx7YRoU63Pb-eqo5l5450DrZN3QYZ36GCn8r66no,3152
6
6
  docs/Webdav.md,sha256=-Ja-BTWSY1BEMAyZycvEMNnkNTPZ49gSPzmf3Lbib70,1547
7
- docs/changelog.md,sha256=WbbVBKfhGz1ehu7vVwGTVyF4g4Ko5glLXPr4bQwp-n8,2748
8
- frontend/api.js,sha256=RqvwRWhYZx7_cDlyYgWzgJAr83RxH47WzY-5KL6aRX0,22670
7
+ docs/changelog.md,sha256=7D1nsEF4taIsXvPJ5i4uHmp2QXB7w3OCKCfcnuzpo4E,3394
8
+ frontend/api.js,sha256=tcNneh2wswtA7xM4GrrD4dwtU5VKQ-86qwR5dZlHasw,25749
9
+ frontend/base.css,sha256=iZwhILCy8suGM6Z5e4b4SzBK_WTItclmgwXJxC41TyY,564
10
+ frontend/edit.css,sha256=QIIEezsLd3gMjA9q25kg2WQFsY51Zkqs72K-8cdPvUc,1932
11
+ frontend/edit.html,sha256=ISOYQCHLojEQGTpZ2HIf3i6HNUFah59K3m5z9O1wqPs,810
12
+ frontend/edit.js,sha256=KBS29KjoC0KDwwdkNr7rlfGXjAjkh35xMcweufZemu8,3777
9
13
  frontend/index.html,sha256=-k0bJ5FRqdl_H-O441D_H9E-iejgRCaL_z5UeYaS2qc,3384
10
14
  frontend/info.css,sha256=Ny0N3GywQ3a9q1_Qph_QFEKB4fEnTe_2DJ1Y5OsLLmQ,595
11
15
  frontend/info.js,sha256=xGUJPCSrtDhuSu0ELLQZ77PmVWldg-prU1mwQGbdEoA,5797
12
- frontend/login.css,sha256=VMM0QfbDFYerxKWKSGhMI1yg5IRBXg0TTdLJEEhQZNk,355
16
+ frontend/login.css,sha256=XWVEH-J1CUDO7zdr9tm_47iNZBis9G9x7cFO92gJ7FE,383
13
17
  frontend/login.js,sha256=xJkulk8dlvV4BhevADLeUrnZwShiFTWv3Wg2iJFUZlY,2423
14
18
  frontend/popup.css,sha256=TJZYFW1ZcdD1IVTlNPYNtMWKPbN6XDbQ4hKBOFK8uLg,1284
15
19
  frontend/popup.js,sha256=cyUjtO0wbtqbEodHfwyUsak9iWbcDXeWMGDhpCPbcoE,5453
16
- frontend/scripts.js,sha256=awnhw4CHxE4cPvg3g9pramtyxMEo7i_ygGSCer1BsFI,24633
20
+ frontend/scripts.js,sha256=tyBagLk3R3GJJSg56wNWAKgYdCh4hVhEbL3lxVSvQ3s,26493
17
21
  frontend/state.js,sha256=vbNL5DProRKmSEY7xu9mZH6IY0PBenF8WGxPtGgDnLI,1680
18
- frontend/styles.css,sha256=xcNLqI3KBsY5TLnku8UIP0Jfr7QLajr1_KNlZj9eheM,4935
22
+ frontend/styles.css,sha256=vE_iwAzU7tiJHUeMY2pcq3BcdATDncCvF0pgBnKHGrA,4565
19
23
  frontend/thumb.css,sha256=rNsx766amYS2DajSQNabhpQ92gdTpNoQKmV69OKvtpI,295
20
24
  frontend/thumb.js,sha256=46ViD2TlTTWy0fx6wjoAs_5CQ4ajYB90vVzM7UO2IHw,6182
21
25
  frontend/utils.js,sha256=XP5hM_mROYaxK5dqn9qZVwv7GdQuiDzByilFskbrnxA,6068
22
- lfss/api/__init__.py,sha256=qHlQAnvw2y0FNZKhes6ikzItEEQvyJWFhVMs1GAqZiM,6822
23
- lfss/api/connector.py,sha256=jMgYhSYQN0yDD9B1e35F7x6ZFswR3zBKQ19TkdP2KK0,17018
24
- lfss/cli/__init__.py,sha256=OPJLYHvqsyNUoPRzW4ITKQ3hEuotx7u-OsN4Uoz1XvA,1132
26
+ lfss/api/__init__.py,sha256=SoNB7vjs2I5SXrYRbfcUZgIoZUhzDsEIDSZhPMf7LiE,232
27
+ lfss/api/bundle.py,sha256=fN0fPTURO8srRhwrZYhRUlZ9rStrevGitDz-WDbyUCk,6758
28
+ lfss/api/connector.py,sha256=p6_kmGzGYn0Jpk2K8aHzYTcz7P25AJkuJHkJM0uGAzM,16985
29
+ lfss/cli/__init__.py,sha256=44qzN7yOoEQO53sPPEHlGZZgT3HXnt1LbTLFS9F0DOg,1332
25
30
  lfss/cli/balance.py,sha256=fUbKKAUyaDn74f7mmxMfBL4Q4voyBLHu6Lg_g8GfMOQ,4121
26
- lfss/cli/cli.py,sha256=31s0sUmCdqYF9pbYsADb6TaThOs5qOga8P5Q1iUlA5I,15612
27
- lfss/cli/cli_lib.py,sha256=QtXB8WsThz4R5n8ZpxKF_20L1BPBpvbC1QJBeuq0-NA,2866
31
+ lfss/cli/cli.py,sha256=m3h3fPUlJGjDtok8xyHID5CWZ2sYbwGmDpnICyrFSB4,18327
32
+ lfss/cli/cli_lib.py,sha256=B3NuICVnTxpZ8lHc0x1KEekw9ygGNwd00U3zx3uAbBM,2783
28
33
  lfss/cli/log.py,sha256=TBlt8mhHMouv8ZBUMHYfGZiV6-0yPdajJQ5mkGHEojI,3016
29
34
  lfss/cli/panel.py,sha256=Xq3I_n-ctveym-Gh9LaUpzHiLlvt3a_nuDiwUS-MGrg,1597
30
35
  lfss/cli/serve.py,sha256=vTo6_BiD7Dn3VLvHsC5RKRBC3lMu45JVr_0SqpgHdj0,1086
@@ -34,21 +39,22 @@ lfss/eng/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
39
  lfss/eng/bounded_pool.py,sha256=BI1dU-MBf82TMwJBYbjhEty7w1jIUKc5Bn9SnZ_-hoY,1288
35
40
  lfss/eng/config.py,sha256=0dncHYn3tYw4pKBwXuP_huz0u7ud23fJ6SUmUPfLmeM,967
36
41
  lfss/eng/connection_pool.py,sha256=1aq7nSgd7hB9YNV4PjD1RDRyl_moDw3ubBtSLyfgGBs,6320
37
- lfss/eng/database.py,sha256=9ezwrZEPC-Jnh5dxEKahb9AsFWbSQlNcYVZqVbZrYwI,57502
38
- lfss/eng/datatype.py,sha256=27UB7-l9SICy5lAvKjdzpTL_GohZjzstQcr9PtAq7nM,2709
39
- lfss/eng/error.py,sha256=JGf5NV-f4rL6tNIDSAx5-l9MG8dEj7F2w_MuOjj1d1o,732
42
+ lfss/eng/database.py,sha256=4qbPn-_R_-KqpGF7UrEiXj0nk_PutJu2jwv-8Wvgvs8,61220
43
+ lfss/eng/datatype.py,sha256=hU4p5lsfhE1mPF0oHtAq1sz0rPoVOYiSt_QAoUYmGpU,3368
44
+ lfss/eng/error.py,sha256=NpGP_9C7EWATARDb7nCoznOznIHvvRbWbvHdG6ExoMI,1174
40
45
  lfss/eng/log.py,sha256=yciFQ7Utz1AItNekS4YtdP6bM7i1krA6qSAU2wVQv24,7698
41
46
  lfss/eng/thumb.py,sha256=AFyWEkkpuCKGWOB9bLlaDwPKzQ9JtCSSmHMhX2Gu3CI,3096
42
47
  lfss/eng/utils.py,sha256=LB84nX-fEgMlWMPo5ByU48U7RCy8EcxqIHAMWP8d-8w,6773
43
48
  lfss/sql/init.sql,sha256=FBmVzkNjYUnWjEELRFzf7xb50GngmzmeDVffT1Uk8u8,1625
44
49
  lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
45
- lfss/svc/app.py,sha256=r1KUO3sPaaJWbkJF0bcVTD7arPKLs2jFlq52Ixicomo,220
46
- lfss/svc/app_base.py,sha256=s5ieQVI4BT0CBYavRx0dyBqwts7PYnjyCovHNYPHul8,7014
50
+ lfss/svc/app.py,sha256=GBsqrQOkl37HGb2ZLGE_fTEuyFZwcc95RVr2DP39gsI,283
51
+ lfss/svc/app_base.py,sha256=DiAJDpDGq2Z7UwjD0irBVZBk6TxZCpWsXBGpbRCZ3gY,7091
47
52
  lfss/svc/app_dav.py,sha256=DRMgByUAQ3gD6wL9xmikV5kvVmATN7QkxGSttFTYxFU,18245
48
- lfss/svc/app_native.py,sha256=imqnuAoseTS2CmztUI0yQ0Jjq_jqbjxYG-_FFnYp6u0,11040
49
- lfss/svc/common_impl.py,sha256=wlTQm8zEGAfyw9FJvK9zqgLQw47MzNq6IT3OgwdUaCw,13736
53
+ lfss/svc/app_native.py,sha256=QSjDv2CBGKL4NneiXkXBGH8B3_MQqkGZEe3g0-ntvAo,11366
54
+ lfss/svc/app_native_user.py,sha256=iI70MXzh5_DJ_ab64vrJpexHcB0zwuDFOuRa3KozVMo,995
55
+ lfss/svc/common_impl.py,sha256=h1lmjFf4JUZMRwPhwfLCbsuplvAsZz59sUaYjE_QZBQ,14492
50
56
  lfss/svc/request_log.py,sha256=v8yXEIzPjaksu76Oh5vgdbUEUrw8Kt4etLAXBWSGie8,3207
51
- lfss-0.12.3.dist-info/METADATA,sha256=ExulCVxgaGu45rI_ksXQhAtuvVnXfjbLnWCTTL9gLuM,2818
52
- lfss-0.12.3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
53
- lfss-0.12.3.dist-info/entry_points.txt,sha256=M4ubn9oLYcTc9wxlLKWwljnluStPWpCDlCGuTVU8twg,255
54
- lfss-0.12.3.dist-info/RECORD,,
57
+ lfss-0.13.0.dist-info/METADATA,sha256=a41tjDaHn9pEcnW-2x_YMrNbP9cLI7jv8MuQtZkJx7U,2818
58
+ lfss-0.13.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
59
+ lfss-0.13.0.dist-info/entry_points.txt,sha256=M4ubn9oLYcTc9wxlLKWwljnluStPWpCDlCGuTVU8twg,255
60
+ lfss-0.13.0.dist-info/RECORD,,
File without changes