lfss 0.6.0__py3-none-any.whl → 0.7.1__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.
- frontend/scripts.js +1 -1
- lfss/cli/balance.py +14 -11
- lfss/cli/cli.py +1 -1
- lfss/cli/user.py +34 -32
- lfss/client/api.py +22 -2
- lfss/sql/init.sql +5 -5
- lfss/sql/pragma.sql +7 -4
- lfss/src/config.py +1 -0
- lfss/src/connection_pool.py +151 -0
- lfss/src/database.py +253 -327
- lfss/src/datatype.py +1 -1
- lfss/src/server.py +69 -75
- {lfss-0.6.0.dist-info → lfss-0.7.1.dist-info}/METADATA +1 -1
- {lfss-0.6.0.dist-info → lfss-0.7.1.dist-info}/RECORD +16 -15
- {lfss-0.6.0.dist-info → lfss-0.7.1.dist-info}/WHEEL +0 -0
- {lfss-0.6.0.dist-info → lfss-0.7.1.dist-info}/entry_points.txt +0 -0
lfss/src/datatype.py
CHANGED
lfss/src/server.py
CHANGED
@@ -17,20 +17,24 @@ from .log import get_logger
|
|
17
17
|
from .stat import RequestDB
|
18
18
|
from .config import MAX_BUNDLE_BYTES, MAX_FILE_BYTES, LARGE_FILE_BYTES
|
19
19
|
from .utils import ensure_uri_compnents, format_last_modified, now_stamp
|
20
|
-
from .
|
20
|
+
from .connection_pool import global_connection_init, global_connection_close, unique_cursor
|
21
|
+
from .database import Database, UserRecord, DECOY_USER, FileRecord, check_user_permission, FileReadPermission, UserConn, FileConn
|
21
22
|
|
22
23
|
logger = get_logger("server", term_level="DEBUG")
|
23
24
|
logger_failed_request = get_logger("failed_requests", term_level="INFO")
|
24
|
-
|
25
|
+
db = Database()
|
25
26
|
req_conn = RequestDB()
|
26
27
|
|
27
28
|
@asynccontextmanager
|
28
29
|
async def lifespan(app: FastAPI):
|
29
|
-
global
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
global db
|
31
|
+
try:
|
32
|
+
await global_connection_init(n_read = 2)
|
33
|
+
await asyncio.gather(db.init(), req_conn.init())
|
34
|
+
yield
|
35
|
+
await req_conn.commit()
|
36
|
+
finally:
|
37
|
+
await asyncio.gather(req_conn.close(), global_connection_close())
|
34
38
|
|
35
39
|
def handle_exception(fn):
|
36
40
|
@wraps(fn)
|
@@ -58,13 +62,15 @@ async def get_current_user(
|
|
58
62
|
First try to get the user from the bearer token,
|
59
63
|
if not found, try to get the user from the query parameter
|
60
64
|
"""
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
return DECOY_USER
|
65
|
+
async with unique_cursor() as conn:
|
66
|
+
uconn = UserConn(conn)
|
67
|
+
if token:
|
68
|
+
user = await uconn.get_user_by_credential(token.credentials)
|
66
69
|
else:
|
67
|
-
|
70
|
+
if not q_token:
|
71
|
+
return DECOY_USER
|
72
|
+
else:
|
73
|
+
user = await uconn.get_user_by_credential(q_token)
|
68
74
|
|
69
75
|
if not user:
|
70
76
|
raise HTTPException(status_code=401, detail="Invalid token")
|
@@ -116,25 +122,31 @@ async def get_file(path: str, download = False, user: UserRecord = Depends(get_c
|
|
116
122
|
if path == "": path = "/"
|
117
123
|
if path.endswith("/"):
|
118
124
|
# return file under the path as json
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
125
|
+
async with unique_cursor() as conn:
|
126
|
+
fconn = FileConn(conn)
|
127
|
+
if user.id == 0:
|
128
|
+
raise HTTPException(status_code=403, detail="Permission denied, credential required")
|
129
|
+
if path == "/":
|
130
|
+
return {
|
131
|
+
"dirs": await fconn.list_root(user.username) \
|
132
|
+
if not user.is_admin else await fconn.list_root(),
|
133
|
+
"files": []
|
134
|
+
}
|
135
|
+
|
136
|
+
if not path.startswith(f"{user.username}/") and not user.is_admin:
|
137
|
+
raise HTTPException(status_code=403, detail="Permission denied, path must start with username")
|
138
|
+
|
139
|
+
return await fconn.list_path(path, flat = False)
|
132
140
|
|
133
|
-
|
134
|
-
|
135
|
-
|
141
|
+
async with unique_cursor() as conn:
|
142
|
+
fconn = FileConn(conn)
|
143
|
+
file_record = await fconn.get_file_record(path)
|
144
|
+
if not file_record:
|
145
|
+
raise HTTPException(status_code=404, detail="File not found")
|
146
|
+
|
147
|
+
uconn = UserConn(conn)
|
148
|
+
owner = await uconn.get_user_by_id(file_record.owner_id)
|
136
149
|
|
137
|
-
owner = await conn.user.get_user_by_id(file_record.owner_id)
|
138
150
|
assert owner is not None, "Owner not found"
|
139
151
|
allow_access, reason = check_user_permission(user, owner, file_record)
|
140
152
|
if not allow_access:
|
@@ -145,7 +157,7 @@ async def get_file(path: str, download = False, user: UserRecord = Depends(get_c
|
|
145
157
|
if media_type is None:
|
146
158
|
media_type = file_record.mime_type
|
147
159
|
if not file_record.external:
|
148
|
-
fblob = await
|
160
|
+
fblob = await db.read_file(path)
|
149
161
|
return Response(
|
150
162
|
content=fblob, media_type=media_type, headers={
|
151
163
|
"Content-Disposition": f"{disposition}; filename={fname}",
|
@@ -155,7 +167,7 @@ async def get_file(path: str, download = False, user: UserRecord = Depends(get_c
|
|
155
167
|
)
|
156
168
|
else:
|
157
169
|
return StreamingResponse(
|
158
|
-
await
|
170
|
+
await db.read_file_stream(path), media_type=media_type, headers={
|
159
171
|
"Content-Disposition": f"{disposition}; filename={fname}",
|
160
172
|
"Content-Length": str(file_record.file_size),
|
161
173
|
"Last-Modified": format_last_modified(file_record.create_time)
|
@@ -192,7 +204,10 @@ async def put_file(
|
|
192
204
|
|
193
205
|
logger.info(f"PUT {path}, user: {user.username}")
|
194
206
|
exists_flag = False
|
195
|
-
|
207
|
+
async with unique_cursor() as conn:
|
208
|
+
fconn = FileConn(conn)
|
209
|
+
file_record = await fconn.get_file_record(path)
|
210
|
+
|
196
211
|
if file_record:
|
197
212
|
if conflict == "abort":
|
198
213
|
raise HTTPException(status_code=409, detail="File exists")
|
@@ -202,7 +217,7 @@ async def put_file(
|
|
202
217
|
}, content=json.dumps({"url": path}))
|
203
218
|
# remove the old file
|
204
219
|
exists_flag = True
|
205
|
-
await
|
220
|
+
await db.delete_file(path)
|
206
221
|
|
207
222
|
# check content-type
|
208
223
|
content_type = request.headers.get("Content-Type")
|
@@ -236,9 +251,9 @@ async def put_file(
|
|
236
251
|
chunk_size = 16 * 1024 * 1024 # 16MB
|
237
252
|
for b in range(0, len(blobs), chunk_size):
|
238
253
|
yield blobs[b:b+chunk_size]
|
239
|
-
await
|
254
|
+
await db.save_file(user.id, path, blob_reader(), permission = FileReadPermission(permission), mime_type = mime_t)
|
240
255
|
else:
|
241
|
-
await
|
256
|
+
await db.save_file(user.id, path, blobs, permission = FileReadPermission(permission), mime_type=mime_t)
|
242
257
|
|
243
258
|
# https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/PUT
|
244
259
|
if exists_flag:
|
@@ -262,11 +277,11 @@ async def delete_file(path: str, user: UserRecord = Depends(get_current_user)):
|
|
262
277
|
logger.info(f"DELETE {path}, user: {user.username}")
|
263
278
|
|
264
279
|
if path.endswith("/"):
|
265
|
-
res = await
|
280
|
+
res = await db.delete_path(path)
|
266
281
|
else:
|
267
|
-
res = await
|
282
|
+
res = await db.delete_file(path)
|
268
283
|
|
269
|
-
await
|
284
|
+
await db.record_user_activity(user.username)
|
270
285
|
if res:
|
271
286
|
return Response(status_code=200, content="Deleted")
|
272
287
|
else:
|
@@ -291,14 +306,14 @@ async def bundle_files(path: str, user: UserRecord = Depends(get_current_user)):
|
|
291
306
|
owner_id = file_record.owner_id
|
292
307
|
owner = owner_records_cache.get(owner_id, None)
|
293
308
|
if owner is None:
|
294
|
-
owner = await conn.user.get_user_by_id(owner_id)
|
295
|
-
assert owner is not None, f"File owner not found: id={owner_id}"
|
296
309
|
owner_records_cache[owner_id] = owner
|
297
310
|
|
298
311
|
allow_access, _ = check_user_permission(user, owner, file_record)
|
299
312
|
return allow_access
|
300
313
|
|
301
|
-
|
314
|
+
async with unique_cursor() as conn:
|
315
|
+
fconn = FileConn(conn)
|
316
|
+
files = await fconn.list_path(path, flat = True)
|
302
317
|
files = [f for f in files if await is_access_granted(f)]
|
303
318
|
if len(files) == 0:
|
304
319
|
raise HTTPException(status_code=404, detail="No files found")
|
@@ -309,7 +324,7 @@ async def bundle_files(path: str, user: UserRecord = Depends(get_current_user)):
|
|
309
324
|
raise HTTPException(status_code=400, detail="Too large to zip")
|
310
325
|
|
311
326
|
file_paths = [f.url for f in files]
|
312
|
-
zip_buffer = await
|
327
|
+
zip_buffer = await db.zip_path(path, file_paths)
|
313
328
|
return Response(
|
314
329
|
content=zip_buffer.getvalue(), media_type="application/zip", headers={
|
315
330
|
"Content-Disposition": f"attachment; filename=bundle.zip",
|
@@ -322,8 +337,11 @@ async def bundle_files(path: str, user: UserRecord = Depends(get_current_user)):
|
|
322
337
|
async def get_file_meta(path: str, user: UserRecord = Depends(get_current_user)):
|
323
338
|
logger.info(f"GET meta({path}), user: {user.username}")
|
324
339
|
path = ensure_uri_compnents(path)
|
325
|
-
|
326
|
-
|
340
|
+
async with unique_cursor() as conn:
|
341
|
+
fconn = FileConn(conn)
|
342
|
+
get_fn = fconn.get_file_record if not path.endswith("/") else fconn.get_path_record
|
343
|
+
record = await get_fn(path)
|
344
|
+
|
327
345
|
if not record:
|
328
346
|
raise HTTPException(status_code=404, detail="Path not found")
|
329
347
|
return record
|
@@ -341,30 +359,22 @@ async def update_file_meta(
|
|
341
359
|
path = ensure_uri_compnents(path)
|
342
360
|
if path.startswith("/"):
|
343
361
|
path = path[1:]
|
344
|
-
await
|
362
|
+
await db.record_user_activity(user.username)
|
345
363
|
|
346
364
|
# file
|
347
365
|
if not path.endswith("/"):
|
348
|
-
file_record = await conn.file.get_file_record(path)
|
349
|
-
if not file_record:
|
350
|
-
logger.debug(f"Reject update meta request from {user.username} to {path}")
|
351
|
-
raise HTTPException(status_code=404, detail="File not found")
|
352
|
-
|
353
|
-
if not (user.is_admin or user.id == file_record.owner_id):
|
354
|
-
logger.debug(f"Reject update meta request from {user.username} to {path}")
|
355
|
-
raise HTTPException(status_code=403, detail="Permission denied")
|
356
|
-
|
357
366
|
if perm is not None:
|
358
367
|
logger.info(f"Update permission of {path} to {perm}")
|
359
|
-
await
|
360
|
-
|
368
|
+
await db.update_file_record(
|
369
|
+
user = user,
|
370
|
+
url = path,
|
361
371
|
permission = FileReadPermission(perm)
|
362
372
|
)
|
363
373
|
|
364
374
|
if new_path is not None:
|
365
375
|
new_path = ensure_uri_compnents(new_path)
|
366
376
|
logger.info(f"Update path of {path} to {new_path}")
|
367
|
-
await
|
377
|
+
await db.move_file(path, new_path)
|
368
378
|
|
369
379
|
# directory
|
370
380
|
else:
|
@@ -372,24 +382,8 @@ async def update_file_meta(
|
|
372
382
|
if new_path is not None:
|
373
383
|
new_path = ensure_uri_compnents(new_path)
|
374
384
|
logger.info(f"Update path of {path} to {new_path}")
|
375
|
-
assert new_path.endswith("/"), "New path must end with /"
|
376
|
-
if new_path.startswith("/"):
|
377
|
-
new_path = new_path[1:]
|
378
|
-
|
379
|
-
# check if new path is under the user's directory
|
380
|
-
first_component = new_path.split("/")[0]
|
381
|
-
if not (first_component == user.username or user.is_admin):
|
382
|
-
raise HTTPException(status_code=403, detail="Permission denied, path must start with username")
|
383
|
-
elif user.is_admin:
|
384
|
-
_is_user = await conn.user.get_user(first_component)
|
385
|
-
if not _is_user:
|
386
|
-
raise HTTPException(status_code=404, detail="User not found, path must start with username")
|
387
|
-
|
388
|
-
# check if old path is under the user's directory (non-admin)
|
389
|
-
if not path.startswith(f"{user.username}/") and not user.is_admin:
|
390
|
-
raise HTTPException(status_code=403, detail="Permission denied, path must start with username")
|
391
385
|
# currently only move own file, with overwrite
|
392
|
-
await
|
386
|
+
await db.move_path(user, path, new_path)
|
393
387
|
|
394
388
|
return Response(status_code=200, content="OK")
|
395
389
|
|
@@ -5,28 +5,29 @@ frontend/api.js,sha256=-ouhsmucEunAK3m1H__MqffQkXAjoeVEfM15BvqfIZs,7677
|
|
5
5
|
frontend/index.html,sha256=VPJDs2LG8ep9kjlsKzjWzpN9vc1VGgdvOUlNTZWyQoQ,2088
|
6
6
|
frontend/popup.css,sha256=VzkjG1ZTLxhHMtTyobnlvqYmVsTmdbJJed2Pu1cc06c,1007
|
7
7
|
frontend/popup.js,sha256=3PgaGZmxSdV1E-D_MWgcR7aHWkcsHA1BNKSOkmP66tA,5191
|
8
|
-
frontend/scripts.js,sha256=
|
8
|
+
frontend/scripts.js,sha256=hQ8m3L7P-LplLqrPUWD6pBo4C_tCUl2XZKRNtkWBy8I,21155
|
9
9
|
frontend/styles.css,sha256=wly8O-zF4EUgV12Tv1bATSfmJsLITv2u3_SiyXVaxv4,4096
|
10
10
|
frontend/utils.js,sha256=Ts4nlef8pkrEgpwX-uQwAhWvwxlIzex8ijDLNCa22ps,2372
|
11
|
-
lfss/cli/balance.py,sha256=
|
12
|
-
lfss/cli/cli.py,sha256
|
11
|
+
lfss/cli/balance.py,sha256=vhTlZoBF97dHBf5IittHpybKswaA-xXOfiPAdvIHD5Q,4794
|
12
|
+
lfss/cli/cli.py,sha256=bJOeEyri_XVWUvnjohsw_oPYKp-bELxLrg5sVWOpKQA,2259
|
13
13
|
lfss/cli/panel.py,sha256=iGdVmdWYjA_7a78ZzWEB_3ggIOBeUKTzg6F5zLaB25c,1401
|
14
14
|
lfss/cli/serve.py,sha256=bO3GT0kuylMGN-7bZWP4e71MlugGZ_lEMkYaYld_Ntg,985
|
15
|
-
lfss/cli/user.py,sha256=
|
15
|
+
lfss/cli/user.py,sha256=Bu2IOchLdClBqjBqVeDck3kE0UaKWpL6mG5SPihBorc,3498
|
16
16
|
lfss/client/__init__.py,sha256=YttaGTBup7mxnW0CtxdZPF0HWga2cGM4twz9MXJIrts,1827
|
17
|
-
lfss/client/api.py,sha256=
|
18
|
-
lfss/sql/init.sql,sha256=
|
19
|
-
lfss/sql/pragma.sql,sha256=
|
17
|
+
lfss/client/api.py,sha256=ICqpcyvSf-9QmYNv9EQ5fA_MViSuLxSNn-CIBNqWkW8,5414
|
18
|
+
lfss/sql/init.sql,sha256=C-JtQAlaOjESI8uoF1Y_9dKukEVSw5Ll-7yA3gG-XHU,1210
|
19
|
+
lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
|
20
20
|
lfss/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
lfss/src/config.py,sha256=
|
22
|
-
lfss/src/
|
23
|
-
lfss/src/
|
21
|
+
lfss/src/config.py,sha256=0TjCCrqkoiLw45vOT0PbDciWbzpD7ztWtubwIwXCsPk,527
|
22
|
+
lfss/src/connection_pool.py,sha256=Y47BJ1gsyusmmVA1PHoFBhytup8s3HbU9D_Aw0y4-TM,4848
|
23
|
+
lfss/src/database.py,sha256=l0Z-ixfwAkYHbSgk9cVceOJ2vzkYUJeVw25Ny7HQxAA,31104
|
24
|
+
lfss/src/datatype.py,sha256=BLS7vuuKnFZQg0nrKeP9SymqUhcN6HwPgejU0yBd_Ak,1622
|
24
25
|
lfss/src/error.py,sha256=imbhwnbhnI3HLhkbfICROe3F0gleKrOk4XnqHJDOtuI,285
|
25
26
|
lfss/src/log.py,sha256=qNE04sHoZ2rMbQ5dR2zT7Xaz1KVAfYp5hKpWJX42S9g,5244
|
26
|
-
lfss/src/server.py,sha256=
|
27
|
+
lfss/src/server.py,sha256=9mnBlcHJruwOV-aoHa1P146F4khpVDmif7rq58yZX3U,15306
|
27
28
|
lfss/src/stat.py,sha256=hTMtQyM_Ukmhc33Bb9FGCfBMIX02KrGHQg8nL7sC8sU,2082
|
28
29
|
lfss/src/utils.py,sha256=8VkrtpSmurbMiX7GyK-n7Grvzy3uwSJXHdONEsuLCDI,2272
|
29
|
-
lfss-0.
|
30
|
-
lfss-0.
|
31
|
-
lfss-0.
|
32
|
-
lfss-0.
|
30
|
+
lfss-0.7.1.dist-info/METADATA,sha256=nNjDG5BqDBRpeYP3NNPEdM02wWeIIJNO1VDdKNrgqQ0,1820
|
31
|
+
lfss-0.7.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
32
|
+
lfss-0.7.1.dist-info/entry_points.txt,sha256=d_Ri3GXxUW-S0E6q953A8od0YMmUAnZGlJSKS46OiW8,172
|
33
|
+
lfss-0.7.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|