lfss 0.7.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.
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 .database import Database, UserRecord, DECOY_USER, FileRecord, check_user_permission, FileReadPermission
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
- conn = Database()
25
+ db = Database()
25
26
  req_conn = RequestDB()
26
27
 
27
28
  @asynccontextmanager
28
29
  async def lifespan(app: FastAPI):
29
- global conn
30
- await asyncio.gather(conn.init(), req_conn.init())
31
- yield
32
- await asyncio.gather(conn.commit(), req_conn.commit())
33
- await asyncio.gather(conn.close(), req_conn.close())
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
- if token:
62
- user = await conn.user.get_user_by_credential(token.credentials)
63
- else:
64
- if not q_token:
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
- user = await conn.user.get_user_by_credential(q_token)
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
- if user.id == 0:
120
- raise HTTPException(status_code=403, detail="Permission denied, credential required")
121
- if path == "/":
122
- return {
123
- "dirs": await conn.file.list_root(user.username) \
124
- if not user.is_admin else await conn.file.list_root(),
125
- "files": []
126
- }
127
-
128
- if not path.startswith(f"{user.username}/") and not user.is_admin:
129
- raise HTTPException(status_code=403, detail="Permission denied, path must start with username")
130
-
131
- return await conn.file.list_path(path, flat = False)
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
- file_record = await conn.file.get_file_record(path)
134
- if not file_record:
135
- raise HTTPException(status_code=404, detail="File not found")
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 conn.read_file(path)
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 conn.read_file_stream(path), media_type=media_type, headers={
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
- file_record = await conn.file.get_file_record(path)
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 conn.delete_file(path)
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 conn.save_file(user.id, path, blob_reader(), permission = FileReadPermission(permission), mime_type = mime_t)
254
+ await db.save_file(user.id, path, blob_reader(), permission = FileReadPermission(permission), mime_type = mime_t)
240
255
  else:
241
- await conn.save_file(user.id, path, blobs, permission = FileReadPermission(permission), mime_type=mime_t)
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 conn.delete_path(path)
280
+ res = await db.delete_path(path)
266
281
  else:
267
- res = await conn.delete_file(path)
282
+ res = await db.delete_file(path)
268
283
 
269
- await conn.user.set_active(user.username)
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
- files = await conn.file.list_path(path, flat = True)
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 conn.zip_path(path, file_paths)
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
- get_fn = conn.file.get_file_record if not path.endswith("/") else conn.file.get_path_record
326
- record = await get_fn(path)
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 conn.user.set_active(user.username)
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 conn.file.update_file_record(
360
- url = file_record.url,
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 conn.move_file(path, new_path)
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 conn.move_path(path, new_path, user_id = user.id)
386
+ await db.move_path(user, path, new_path)
393
387
 
394
388
  return Response(status_code=200, content="OK")
395
389
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lfss
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Lightweight file storage service
5
5
  Home-page: https://github.com/MenxLi/lfss
6
6
  Author: li, mengxun
@@ -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=JkjcyT-IpzSypwI4oWwgY9UDdKkR1ZSYdSc4c6MlukE,21128
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=P9YidInC8QH-jwvMyL9C37SnCLOI9806628zxJW9ChY,4731
11
+ lfss/cli/balance.py,sha256=vhTlZoBF97dHBf5IittHpybKswaA-xXOfiPAdvIHD5Q,4794
12
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=uSNgF8wGYpdOowN8Mah3V_ii6NlxRMNecrrVj3HaemU,3328
15
+ lfss/cli/user.py,sha256=Bu2IOchLdClBqjBqVeDck3kE0UaKWpL6mG5SPihBorc,3498
16
16
  lfss/client/__init__.py,sha256=YttaGTBup7mxnW0CtxdZPF0HWga2cGM4twz9MXJIrts,1827
17
17
  lfss/client/api.py,sha256=ICqpcyvSf-9QmYNv9EQ5fA_MViSuLxSNn-CIBNqWkW8,5414
18
- lfss/sql/init.sql,sha256=uHgcV03KehKOxhXWI7LfnxJL4PQCVmbwn3KHaCurs80,1346
18
+ lfss/sql/init.sql,sha256=C-JtQAlaOjESI8uoF1Y_9dKukEVSw5Ll-7yA3gG-XHU,1210
19
19
  lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
20
20
  lfss/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  lfss/src/config.py,sha256=0TjCCrqkoiLw45vOT0PbDciWbzpD7ztWtubwIwXCsPk,527
22
- lfss/src/database.py,sha256=BJJcORkIzSccdT7yNegAozhpUlq1em7BQ3p8wG4h0fI,31071
22
+ lfss/src/connection_pool.py,sha256=Y47BJ1gsyusmmVA1PHoFBhytup8s3HbU9D_Aw0y4-TM,4848
23
+ lfss/src/database.py,sha256=l0Z-ixfwAkYHbSgk9cVceOJ2vzkYUJeVw25Ny7HQxAA,31104
23
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=J8A9hUnqf-AzCF-Q2Ve9iMPkJYKZ81OP26dFLRm89BM,16219
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.7.0.dist-info/METADATA,sha256=sv5tlm_odl1rk_a9sG_vz2eztFh2Vx5g-NUvtQK1XGY,1820
30
- lfss-0.7.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
31
- lfss-0.7.0.dist-info/entry_points.txt,sha256=d_Ri3GXxUW-S0E6q953A8od0YMmUAnZGlJSKS46OiW8,172
32
- lfss-0.7.0.dist-info/RECORD,,
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