lfss 0.7.15__py3-none-any.whl → 0.8.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.
- Readme.md +2 -2
- docs/Permission.md +4 -2
- frontend/api.js +214 -8
- frontend/index.html +40 -28
- frontend/login.css +21 -0
- frontend/login.js +83 -0
- frontend/scripts.js +73 -84
- frontend/state.js +19 -4
- frontend/styles.css +26 -8
- frontend/thumb.css +6 -0
- frontend/thumb.js +6 -2
- lfss/{client → api}/__init__.py +52 -35
- lfss/{client/api.py → api/connector.py} +89 -8
- lfss/cli/cli.py +1 -1
- lfss/cli/user.py +1 -1
- lfss/src/connection_pool.py +3 -2
- lfss/src/database.py +158 -72
- lfss/src/datatype.py +8 -3
- lfss/src/error.py +3 -1
- lfss/src/server.py +67 -9
- lfss/src/stat.py +1 -1
- lfss/src/utils.py +47 -13
- {lfss-0.7.15.dist-info → lfss-0.8.0.dist-info}/METADATA +4 -3
- lfss-0.8.0.dist-info/RECORD +43 -0
- lfss-0.7.15.dist-info/RECORD +0 -41
- {lfss-0.7.15.dist-info → lfss-0.8.0.dist-info}/WHEEL +0 -0
- {lfss-0.7.15.dist-info → lfss-0.8.0.dist-info}/entry_points.txt +0 -0
lfss/src/server.py
CHANGED
@@ -16,9 +16,13 @@ from .error import *
|
|
16
16
|
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, CHUNK_SIZE
|
19
|
-
from .utils import ensure_uri_compnents, format_last_modified, now_stamp
|
19
|
+
from .utils import ensure_uri_compnents, format_last_modified, now_stamp, wait_for_debounce_tasks
|
20
20
|
from .connection_pool import global_connection_init, global_connection_close, unique_cursor
|
21
|
-
from .database import Database,
|
21
|
+
from .database import Database, DECOY_USER, check_user_permission, UserConn, FileConn, delayed_log_activity
|
22
|
+
from .datatype import (
|
23
|
+
FileReadPermission, FileRecord, UserRecord, PathContents,
|
24
|
+
FileSortKey, DirSortKey
|
25
|
+
)
|
22
26
|
from .thumb import get_thumb
|
23
27
|
|
24
28
|
logger = get_logger("server", term_level="DEBUG")
|
@@ -35,6 +39,7 @@ async def lifespan(app: FastAPI):
|
|
35
39
|
yield
|
36
40
|
await req_conn.commit()
|
37
41
|
finally:
|
42
|
+
await wait_for_debounce_tasks()
|
38
43
|
await asyncio.gather(req_conn.close(), global_connection_close())
|
39
44
|
|
40
45
|
def handle_exception(fn):
|
@@ -49,6 +54,7 @@ def handle_exception(fn):
|
|
49
54
|
if isinstance(e, InvalidPathError): raise HTTPException(status_code=400, detail=str(e))
|
50
55
|
if isinstance(e, FileNotFoundError): raise HTTPException(status_code=404, detail=str(e))
|
51
56
|
if isinstance(e, FileExistsError): raise HTTPException(status_code=409, detail=str(e))
|
57
|
+
if isinstance(e, TooManyItemsError): raise HTTPException(status_code=400, detail=str(e))
|
52
58
|
logger.error(f"Uncaptured error in {fn.__name__}: {e}")
|
53
59
|
raise
|
54
60
|
return wrapper
|
@@ -182,7 +188,7 @@ async def emit_file(
|
|
182
188
|
@handle_exception
|
183
189
|
async def get_file(
|
184
190
|
path: str,
|
185
|
-
download: bool = False,
|
191
|
+
download: bool = False, thumb: bool = False,
|
186
192
|
user: UserRecord = Depends(get_current_user)
|
187
193
|
):
|
188
194
|
path = ensure_uri_compnents(path)
|
@@ -199,8 +205,6 @@ async def get_file(
|
|
199
205
|
return await emit_thumbnail(path, download, create_time=None)
|
200
206
|
|
201
207
|
if path == "/":
|
202
|
-
if flat:
|
203
|
-
raise HTTPException(status_code=400, detail="Flat query not supported for root path")
|
204
208
|
return PathContents(
|
205
209
|
dirs = await fconn.list_root_dirs(user.username, skim=True) \
|
206
210
|
if not user.is_admin else await fconn.list_root_dirs(skim=True),
|
@@ -210,7 +214,7 @@ async def get_file(
|
|
210
214
|
if not path.startswith(f"{user.username}/") and not user.is_admin:
|
211
215
|
raise HTTPException(status_code=403, detail="Permission denied, path must start with username")
|
212
216
|
|
213
|
-
return await fconn.list_path(path
|
217
|
+
return await fconn.list_path(path)
|
214
218
|
|
215
219
|
# handle file query
|
216
220
|
async with unique_cursor() as conn:
|
@@ -332,7 +336,7 @@ async def delete_file(path: str, user: UserRecord = Depends(registered_user)):
|
|
332
336
|
else:
|
333
337
|
res = await db.delete_file(path, user if not user.is_admin else None)
|
334
338
|
|
335
|
-
await
|
339
|
+
await delayed_log_activity(user.username)
|
336
340
|
if res:
|
337
341
|
return Response(status_code=200, content="Deleted")
|
338
342
|
else:
|
@@ -366,7 +370,10 @@ async def bundle_files(path: str, user: UserRecord = Depends(registered_user)):
|
|
366
370
|
|
367
371
|
async with unique_cursor() as conn:
|
368
372
|
fconn = FileConn(conn)
|
369
|
-
files =
|
373
|
+
files = await fconn.list_path_files(
|
374
|
+
url = path, flat = True,
|
375
|
+
limit=(await fconn.count_path_files(url = path, flat = True))
|
376
|
+
)
|
370
377
|
files = [f for f in files if await is_access_granted(f)]
|
371
378
|
if len(files) == 0:
|
372
379
|
raise HTTPException(status_code=404, detail="No files found")
|
@@ -421,7 +428,7 @@ async def update_file_meta(
|
|
421
428
|
path = ensure_uri_compnents(path)
|
422
429
|
if path.startswith("/"):
|
423
430
|
path = path[1:]
|
424
|
-
await
|
431
|
+
await delayed_log_activity(user.username)
|
425
432
|
|
426
433
|
# file
|
427
434
|
if not path.endswith("/"):
|
@@ -448,6 +455,57 @@ async def update_file_meta(
|
|
448
455
|
await db.move_path(user, path, new_path)
|
449
456
|
|
450
457
|
return Response(status_code=200, content="OK")
|
458
|
+
|
459
|
+
async def validate_path_permission(path: str, user: UserRecord):
|
460
|
+
if not path.endswith("/"):
|
461
|
+
raise HTTPException(status_code=400, detail="Path must end with /")
|
462
|
+
if not path.startswith(f"{user.username}/") and not user.is_admin:
|
463
|
+
raise HTTPException(status_code=403, detail="Permission denied")
|
464
|
+
|
465
|
+
@router_api.get("/count-files")
|
466
|
+
async def count_files(path: str, flat: bool = False, user: UserRecord = Depends(registered_user)):
|
467
|
+
await validate_path_permission(path, user)
|
468
|
+
path = ensure_uri_compnents(path)
|
469
|
+
async with unique_cursor() as conn:
|
470
|
+
fconn = FileConn(conn)
|
471
|
+
return { "count": await fconn.count_path_files(url = path, flat = flat) }
|
472
|
+
@router_api.get("/list-files")
|
473
|
+
async def list_files(
|
474
|
+
path: str, offset: int = 0, limit: int = 1000,
|
475
|
+
order_by: FileSortKey = "", order_desc: bool = False,
|
476
|
+
flat: bool = False, user: UserRecord = Depends(registered_user)
|
477
|
+
):
|
478
|
+
await validate_path_permission(path, user)
|
479
|
+
path = ensure_uri_compnents(path)
|
480
|
+
async with unique_cursor() as conn:
|
481
|
+
fconn = FileConn(conn)
|
482
|
+
return await fconn.list_path_files(
|
483
|
+
url = path, offset = offset, limit = limit,
|
484
|
+
order_by=order_by, order_desc=order_desc,
|
485
|
+
flat=flat
|
486
|
+
)
|
487
|
+
|
488
|
+
@router_api.get("/count-dirs")
|
489
|
+
async def count_dirs(path: str, user: UserRecord = Depends(registered_user)):
|
490
|
+
await validate_path_permission(path, user)
|
491
|
+
path = ensure_uri_compnents(path)
|
492
|
+
async with unique_cursor() as conn:
|
493
|
+
fconn = FileConn(conn)
|
494
|
+
return { "count": await fconn.count_path_dirs(url = path) }
|
495
|
+
@router_api.get("/list-dirs")
|
496
|
+
async def list_dirs(
|
497
|
+
path: str, offset: int = 0, limit: int = 1000,
|
498
|
+
order_by: DirSortKey = "", order_desc: bool = False,
|
499
|
+
skim: bool = True, user: UserRecord = Depends(registered_user)
|
500
|
+
):
|
501
|
+
await validate_path_permission(path, user)
|
502
|
+
path = ensure_uri_compnents(path)
|
503
|
+
async with unique_cursor() as conn:
|
504
|
+
fconn = FileConn(conn)
|
505
|
+
return await fconn.list_path_dirs(
|
506
|
+
url = path, offset = offset, limit = limit,
|
507
|
+
order_by=order_by, order_desc=order_desc, skim=skim
|
508
|
+
)
|
451
509
|
|
452
510
|
@router_api.get("/whoami")
|
453
511
|
@handle_exception
|
lfss/src/stat.py
CHANGED
lfss/src/utils.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
-
import datetime
|
1
|
+
import datetime, time
|
2
2
|
import urllib.parse
|
3
3
|
import asyncio
|
4
4
|
import functools
|
5
5
|
import hashlib
|
6
|
+
from asyncio import Lock
|
7
|
+
from collections import OrderedDict
|
6
8
|
from concurrent.futures import ThreadPoolExecutor
|
7
9
|
from typing import TypeVar, Callable, Awaitable
|
8
10
|
from functools import wraps, partial
|
11
|
+
from uuid import uuid4
|
9
12
|
import os
|
10
13
|
|
11
14
|
def hash_credential(username: str, password: str):
|
@@ -25,25 +28,56 @@ def ensure_uri_compnents(path: str):
|
|
25
28
|
""" Ensure the path components are safe to use """
|
26
29
|
return encode_uri_compnents(decode_uri_compnents(path))
|
27
30
|
|
28
|
-
|
29
|
-
|
31
|
+
g_debounce_tasks: OrderedDict[str, asyncio.Task] = OrderedDict()
|
32
|
+
lock_debounce_task_queue = Lock()
|
33
|
+
async def wait_for_debounce_tasks():
|
34
|
+
async def stop_task(task: asyncio.Task):
|
35
|
+
task.cancel()
|
36
|
+
try:
|
37
|
+
await task
|
38
|
+
except asyncio.CancelledError:
|
39
|
+
pass
|
40
|
+
await asyncio.gather(*map(stop_task, g_debounce_tasks.values()))
|
41
|
+
g_debounce_tasks.clear()
|
42
|
+
|
43
|
+
def debounce_async(delay: float = 0.1, max_wait: float = 1.):
|
44
|
+
"""
|
45
|
+
Debounce the async procedure,
|
46
|
+
ensuring execution at least once every `max_wait` seconds.
|
47
|
+
"""
|
30
48
|
def debounce_wrap(func):
|
31
|
-
|
49
|
+
task_record: tuple[str, asyncio.Task] | None = None
|
50
|
+
last_execution_time = 0
|
51
|
+
|
32
52
|
async def delayed_func(*args, **kwargs):
|
53
|
+
nonlocal last_execution_time
|
33
54
|
await asyncio.sleep(delay)
|
34
55
|
await func(*args, **kwargs)
|
56
|
+
last_execution_time = time.monotonic()
|
35
57
|
|
36
|
-
task_record: asyncio.Task | None = None
|
37
58
|
@functools.wraps(func)
|
38
59
|
async def wrapper(*args, **kwargs):
|
39
|
-
nonlocal task_record
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
60
|
+
nonlocal task_record, last_execution_time
|
61
|
+
|
62
|
+
async with lock_debounce_task_queue:
|
63
|
+
if task_record is not None:
|
64
|
+
task_record[1].cancel()
|
65
|
+
g_debounce_tasks.pop(task_record[0], None)
|
66
|
+
|
67
|
+
if time.monotonic() - last_execution_time > max_wait:
|
68
|
+
await func(*args, **kwargs)
|
69
|
+
last_execution_time = time.monotonic()
|
70
|
+
return
|
71
|
+
|
72
|
+
task = asyncio.create_task(delayed_func(*args, **kwargs))
|
73
|
+
task_uid = uuid4().hex
|
74
|
+
task_record = (task_uid, task)
|
75
|
+
async with lock_debounce_task_queue:
|
76
|
+
g_debounce_tasks[task_uid] = task
|
77
|
+
if len(g_debounce_tasks) > 2048:
|
78
|
+
# finished tasks are not removed from the dict
|
79
|
+
# so we need to clear it periodically
|
80
|
+
await wait_for_debounce_tasks()
|
47
81
|
return wrapper
|
48
82
|
return debounce_wrap
|
49
83
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: lfss
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.8.0
|
4
4
|
Summary: Lightweight file storage service
|
5
5
|
Home-page: https://github.com/MenxLi/lfss
|
6
6
|
Author: li, mengxun
|
@@ -16,6 +16,7 @@ Requires-Dist: aiosqlite (==0.*)
|
|
16
16
|
Requires-Dist: fastapi (==0.*)
|
17
17
|
Requires-Dist: mimesniff (==1.*)
|
18
18
|
Requires-Dist: pillow
|
19
|
+
Requires-Dist: requests (==2.*)
|
19
20
|
Requires-Dist: uvicorn (==0.*)
|
20
21
|
Project-URL: Repository, https://github.com/MenxLi/lfss
|
21
22
|
Description-Content-Type: text/markdown
|
@@ -23,7 +24,7 @@ Description-Content-Type: text/markdown
|
|
23
24
|
# Lightweight File Storage Service (LFSS)
|
24
25
|
[](https://pypi.org/project/lfss/)
|
25
26
|
|
26
|
-
My experiment on a lightweight file/object storage service.
|
27
|
+
My experiment on a lightweight and high-performance file/object storage service.
|
27
28
|
It stores small files and metadata in sqlite, large files in the filesystem.
|
28
29
|
Tested on 2 million files, and it works fine...
|
29
30
|
|
@@ -45,7 +46,7 @@ Or, you can start a web server at `/frontend` and open `index.html` in your brow
|
|
45
46
|
|
46
47
|
The API usage is simple, just `GET`, `PUT`, `DELETE` to the `/<username>/file/url` path.
|
47
48
|
Authentication is done via `Authorization` header with the value `Bearer <token>`, or through the `token` query parameter.
|
48
|
-
You can refer to `frontend` as an application example, and `frontend/api.js` or `lfss/
|
49
|
+
You can refer to `frontend` as an application example, and `frontend/api.js` or `lfss/api/connector.py` for the API usage.
|
49
50
|
|
50
51
|
By default, the service exposes all files to the public for `GET` requests,
|
51
52
|
but file-listing is restricted to the user's own files.
|
@@ -0,0 +1,43 @@
|
|
1
|
+
Readme.md,sha256=LpbTvUWjCOv4keMNDrZvEnNAmCQnvaxvlq2srWixXn0,1299
|
2
|
+
docs/Known_issues.md,sha256=rfdG3j1OJF-59S9E06VPyn0nZKbW-ybPxkoZ7MEZWp8,81
|
3
|
+
docs/Permission.md,sha256=9r9nEmhqfz18RTS8FI0fZ9F0a31r86OoAyx3EQxxpk0,2317
|
4
|
+
frontend/api.js,sha256=9cR8ddaoF-ulauQ1tcISV2nmfakkplC7uS8-lWFqU58,15820
|
5
|
+
frontend/index.html,sha256=-k0bJ5FRqdl_H-O441D_H9E-iejgRCaL_z5UeYaS2qc,3384
|
6
|
+
frontend/info.css,sha256=Ny0N3GywQ3a9q1_Qph_QFEKB4fEnTe_2DJ1Y5OsLLmQ,595
|
7
|
+
frontend/info.js,sha256=WhOGaeqMoezEAfg4nIpK26hvejC7AZ-ZDLiJmRj0kDk,5758
|
8
|
+
frontend/login.css,sha256=VMM0QfbDFYerxKWKSGhMI1yg5IRBXg0TTdLJEEhQZNk,355
|
9
|
+
frontend/login.js,sha256=QoO8yKmBHDVP-ZomCMOaV7xVUVIhpl7esJrb6T5aHQE,2466
|
10
|
+
frontend/popup.css,sha256=TJZYFW1ZcdD1IVTlNPYNtMWKPbN6XDbQ4hKBOFK8uLg,1284
|
11
|
+
frontend/popup.js,sha256=3PgaGZmxSdV1E-D_MWgcR7aHWkcsHA1BNKSOkmP66tA,5191
|
12
|
+
frontend/scripts.js,sha256=PFgAZC-frV-qs6sYCb1ZzH_CAa7in17qbfqYiaIxNmA,21697
|
13
|
+
frontend/state.js,sha256=vbNL5DProRKmSEY7xu9mZH6IY0PBenF8WGxPtGgDnLI,1680
|
14
|
+
frontend/styles.css,sha256=xcNLqI3KBsY5TLnku8UIP0Jfr7QLajr1_KNlZj9eheM,4935
|
15
|
+
frontend/thumb.css,sha256=rNsx766amYS2DajSQNabhpQ92gdTpNoQKmV69OKvtpI,295
|
16
|
+
frontend/thumb.js,sha256=RQ_whXNwmkdG4SEbNQGeh488YYzqwoNYDc210hPeuhQ,5703
|
17
|
+
frontend/utils.js,sha256=IYUZl77ugiXKcLxSNOWC4NSS0CdD5yRgUsDb665j0xM,2556
|
18
|
+
lfss/api/__init__.py,sha256=YI1_9nyW0E5lyXn_PmmIzIff1ccBC-KCA0twpsKDRIY,5453
|
19
|
+
lfss/api/connector.py,sha256=q9CJBOmN83tfpwI1IclSzq_lzI4Kq1SOKC3S5H-vuWo,9301
|
20
|
+
lfss/cli/balance.py,sha256=R2rbO2tg9TVnnQIVeU0GJVeMS-5LDhEdk4mbOE9qGq0,4121
|
21
|
+
lfss/cli/cli.py,sha256=8VKe41m_LhVSFxGlvgBxdz55sjscLNbbkNX1fOnmES4,4618
|
22
|
+
lfss/cli/panel.py,sha256=iGdVmdWYjA_7a78ZzWEB_3ggIOBeUKTzg6F5zLaB25c,1401
|
23
|
+
lfss/cli/serve.py,sha256=T-jz_PJcaY9FJRRfyWV9MbjDI73YdOOCn4Nr2PO-s0c,993
|
24
|
+
lfss/cli/user.py,sha256=wlR-xcJKCtr_y5QgYO9GM0JyDCKooIRlsAxw2eilPfs,3418
|
25
|
+
lfss/cli/vacuum.py,sha256=4TMMYC_5yEt7jeaFTVC3iX0X6aUzYXBiCsrcIYgw_uA,3695
|
26
|
+
lfss/sql/init.sql,sha256=C-JtQAlaOjESI8uoF1Y_9dKukEVSw5Ll-7yA3gG-XHU,1210
|
27
|
+
lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
|
28
|
+
lfss/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
29
|
+
lfss/src/bounded_pool.py,sha256=BI1dU-MBf82TMwJBYbjhEty7w1jIUKc5Bn9SnZ_-hoY,1288
|
30
|
+
lfss/src/config.py,sha256=K1b5clNRO4EzxDG-p6U5aRLDaq-Up0tDHfn_D79D0ns,857
|
31
|
+
lfss/src/connection_pool.py,sha256=4YULPcmndy33FyBSo9w1fZjAlBX2q-xPP27xJOTA06I,5228
|
32
|
+
lfss/src/database.py,sha256=yFMoxHJ-ZRwi9qvSVdIl9m_gpycmL2eencdv7P0vzwQ,35678
|
33
|
+
lfss/src/datatype.py,sha256=q2lc8BJaB2sS7gtqPMqxM625mckgI2SmvuqbadiObLY,2158
|
34
|
+
lfss/src/error.py,sha256=Bh_GUtuNsMSMIKbFreU4CfZzL96TZdNAIcgmDxvGbQ0,333
|
35
|
+
lfss/src/log.py,sha256=u6WRZZsE7iOx6_CV2NHh1ugea26p408FI4WstZh896A,5139
|
36
|
+
lfss/src/server.py,sha256=OcyPuVyvBCxpgF8ESxzDYNZnUJIbkOGDv-eghUSVM9E,20063
|
37
|
+
lfss/src/stat.py,sha256=WlRiiAl0rHX9oPi3thi6K4GKn70WHpClSDCt7iZUow4,3197
|
38
|
+
lfss/src/thumb.py,sha256=qjCNMpnCozMuzkhm-2uAYy1eAuYTeWG6xqs-13HX-7k,3266
|
39
|
+
lfss/src/utils.py,sha256=nal2rpr00jq1PeFhGQXkvU0FIbtRhXTj8VmbeIyRyLI,5184
|
40
|
+
lfss-0.8.0.dist-info/METADATA,sha256=uNZzNHCActK1ok2aPmZZqMXE3JxyWHStAVAH5mzpvkc,2076
|
41
|
+
lfss-0.8.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
42
|
+
lfss-0.8.0.dist-info/entry_points.txt,sha256=VJ8svMz7RLtMCgNk99CElx7zo7M-N-z7BWDVw2HA92E,205
|
43
|
+
lfss-0.8.0.dist-info/RECORD,,
|
lfss-0.7.15.dist-info/RECORD
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
Readme.md,sha256=vsPotlwPAaHI5plh4aaszpi3rr7ZGDn7-wLdEYTWQ0k,1275
|
2
|
-
docs/Known_issues.md,sha256=rfdG3j1OJF-59S9E06VPyn0nZKbW-ybPxkoZ7MEZWp8,81
|
3
|
-
docs/Permission.md,sha256=X0VNfBKU52f93QYqcVyiBFJ3yURiSkhIo9S_5fdSgzM,2265
|
4
|
-
frontend/api.js,sha256=o1sP4rKxxnM-rebxnlMlPkhPHzKaVW4kZC7B4ufbOK4,8026
|
5
|
-
frontend/index.html,sha256=i45ilkRCorqXP0bTfMiwT3QmmEF23V34lZJC1nODHLo,2862
|
6
|
-
frontend/info.css,sha256=Ny0N3GywQ3a9q1_Qph_QFEKB4fEnTe_2DJ1Y5OsLLmQ,595
|
7
|
-
frontend/info.js,sha256=WhOGaeqMoezEAfg4nIpK26hvejC7AZ-ZDLiJmRj0kDk,5758
|
8
|
-
frontend/popup.css,sha256=TJZYFW1ZcdD1IVTlNPYNtMWKPbN6XDbQ4hKBOFK8uLg,1284
|
9
|
-
frontend/popup.js,sha256=3PgaGZmxSdV1E-D_MWgcR7aHWkcsHA1BNKSOkmP66tA,5191
|
10
|
-
frontend/scripts.js,sha256=tmA_tG3bLEEqn0jTgZ-DkcEl6egxdrFYJmstb9eZMr0,21862
|
11
|
-
frontend/state.js,sha256=Dda-2G4QzyqdxffjJa3Lb7rgJOrg2LvJ3TCRcB8YCrU,1327
|
12
|
-
frontend/styles.css,sha256=krMo6Ulroi8pqEq1exQsFEU-FJqT9GzI8vyARiNF11k,4484
|
13
|
-
frontend/thumb.css,sha256=1i8wudiMWGwdrnTqs6yrS8YaiPeHPR-A2YqUNJN20Ok,165
|
14
|
-
frontend/thumb.js,sha256=6m8bscQpi2sYaLRir2ZeY1H_1ZRKFVss5P28AnLvIVQ,5486
|
15
|
-
frontend/utils.js,sha256=IYUZl77ugiXKcLxSNOWC4NSS0CdD5yRgUsDb665j0xM,2556
|
16
|
-
lfss/cli/balance.py,sha256=R2rbO2tg9TVnnQIVeU0GJVeMS-5LDhEdk4mbOE9qGq0,4121
|
17
|
-
lfss/cli/cli.py,sha256=LH1nx5wI1K2DZ3hvHz7oq5HcXVDoW2V6sr7q9gJ8gqo,4621
|
18
|
-
lfss/cli/panel.py,sha256=iGdVmdWYjA_7a78ZzWEB_3ggIOBeUKTzg6F5zLaB25c,1401
|
19
|
-
lfss/cli/serve.py,sha256=T-jz_PJcaY9FJRRfyWV9MbjDI73YdOOCn4Nr2PO-s0c,993
|
20
|
-
lfss/cli/user.py,sha256=ETLtj0N-kmxv0mhmeAsO6cY7kPq7nOOP4DetxIRoQpQ,3405
|
21
|
-
lfss/cli/vacuum.py,sha256=4TMMYC_5yEt7jeaFTVC3iX0X6aUzYXBiCsrcIYgw_uA,3695
|
22
|
-
lfss/client/__init__.py,sha256=kcClDdHaQowAhHFYdrFyFqHIOe5MEjfENYq1icuj3Mg,4577
|
23
|
-
lfss/client/api.py,sha256=aHorSuxl79xvRRfwuitXANYoTogWKVO11XX7mdrhHTE,5807
|
24
|
-
lfss/sql/init.sql,sha256=C-JtQAlaOjESI8uoF1Y_9dKukEVSw5Ll-7yA3gG-XHU,1210
|
25
|
-
lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
|
26
|
-
lfss/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
27
|
-
lfss/src/bounded_pool.py,sha256=BI1dU-MBf82TMwJBYbjhEty7w1jIUKc5Bn9SnZ_-hoY,1288
|
28
|
-
lfss/src/config.py,sha256=K1b5clNRO4EzxDG-p6U5aRLDaq-Up0tDHfn_D79D0ns,857
|
29
|
-
lfss/src/connection_pool.py,sha256=r4Ho5d_Gd4S_KbT7515UJoiyfIgS6xyttqMsKqOfaIg,5190
|
30
|
-
lfss/src/database.py,sha256=w2QPE3h1Lx0D0fUmdtu9s1XHpNp9p27zqm8AVeP2UVg,32476
|
31
|
-
lfss/src/datatype.py,sha256=WfrLALU_7wei5-i_b0TxY8xWI5mwxLUHFepHSps49zA,1767
|
32
|
-
lfss/src/error.py,sha256=imbhwnbhnI3HLhkbfICROe3F0gleKrOk4XnqHJDOtuI,285
|
33
|
-
lfss/src/log.py,sha256=u6WRZZsE7iOx6_CV2NHh1ugea26p408FI4WstZh896A,5139
|
34
|
-
lfss/src/server.py,sha256=igkPC3gdJoIqcVTKBAKkVPRrclXR2ZNBdRIAEci4xMo,17717
|
35
|
-
lfss/src/stat.py,sha256=Wr-ug_JqtbSIf3XwQnv1xheGhsDTEOlLWuYoKO_26Jo,3201
|
36
|
-
lfss/src/thumb.py,sha256=qjCNMpnCozMuzkhm-2uAYy1eAuYTeWG6xqs-13HX-7k,3266
|
37
|
-
lfss/src/utils.py,sha256=TBGYvgt6xMP8UC5wTGHAr9fmdhu0_gjOtxcSeyvGyVM,3918
|
38
|
-
lfss-0.7.15.dist-info/METADATA,sha256=s6jCttzD9bRauwKiKGNgrBouK5UDNEUMTgDHZBY_VfY,2021
|
39
|
-
lfss-0.7.15.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
40
|
-
lfss-0.7.15.dist-info/entry_points.txt,sha256=VJ8svMz7RLtMCgNk99CElx7zo7M-N-z7BWDVw2HA92E,205
|
41
|
-
lfss-0.7.15.dist-info/RECORD,,
|
File without changes
|
File without changes
|