lfss 0.7.7__py3-none-any.whl → 0.7.8__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/info.js +10 -10
- frontend/popup.css +13 -0
- frontend/scripts.js +2 -0
- frontend/styles.css +10 -0
- lfss/cli/balance.py +24 -6
- lfss/cli/user.py +1 -11
- lfss/src/config.py +7 -2
- lfss/src/connection_pool.py +2 -1
- lfss/src/utils.py +12 -0
- {lfss-0.7.7.dist-info → lfss-0.7.8.dist-info}/METADATA +1 -1
- {lfss-0.7.7.dist-info → lfss-0.7.8.dist-info}/RECORD +13 -13
- {lfss-0.7.7.dist-info → lfss-0.7.8.dist-info}/WHEEL +0 -0
- {lfss-0.7.7.dist-info → lfss-0.7.8.dist-info}/entry_points.txt +0 -0
frontend/info.js
CHANGED
@@ -19,28 +19,28 @@ export function showInfoPanel(r, u){
|
|
19
19
|
<div class="info-container-left">
|
20
20
|
<table class="info-table">
|
21
21
|
<tr>
|
22
|
-
<td class="info-table-key">
|
22
|
+
<td class="info-table-key">Name</td>
|
23
23
|
<td class="info-table-value">${decodePathURI(r.url).split('/').pop()}</td>
|
24
24
|
</tr>
|
25
|
+
<tr>
|
26
|
+
<td class="info-table-key">Size</td>
|
27
|
+
<td class="info-table-value">${formatSize(r.file_size)}</td>
|
28
|
+
</tr>
|
25
29
|
<tr>
|
26
30
|
<td class="info-table-key">File-Type</td>
|
27
31
|
<td class="info-table-value">${r.mime_type}</td>
|
28
32
|
</tr>
|
29
33
|
<tr>
|
30
|
-
<td class="info-table-key">
|
31
|
-
<td class="info-table-value">${
|
34
|
+
<td class="info-table-key">Owner-ID</td>
|
35
|
+
<td class="info-table-value">${r.owner_id}</td>
|
32
36
|
</tr>
|
33
37
|
<tr>
|
34
|
-
<td class="info-table-key">
|
35
|
-
<td class="info-table-value">${r.
|
38
|
+
<td class="info-table-key">Access-Time</td>
|
39
|
+
<td class="info-table-value">${cvtGMT2Local(r.access_time)}</td>
|
36
40
|
</tr>
|
37
41
|
<tr>
|
38
42
|
<td class="info-table-key">Create-Time</td>
|
39
43
|
<td class="info-table-value">${cvtGMT2Local(r.create_time)}</td>
|
40
|
-
</td>
|
41
|
-
<tr>
|
42
|
-
<td class="info-table-key">Access-Time</td>
|
43
|
-
<td class="info-table-value">${cvtGMT2Local(r.access_time)}</td>
|
44
44
|
</tr>
|
45
45
|
</table>
|
46
46
|
</div>
|
@@ -82,7 +82,7 @@ export function showDirInfoPanel(r, u, c){
|
|
82
82
|
<div class="info-container-left">
|
83
83
|
<table class="info-table">
|
84
84
|
<tr>
|
85
|
-
<td class="info-table-key">
|
85
|
+
<td class="info-table-key">Name</td>
|
86
86
|
<td class="info-table-value" id="info-table-pathname">${fmtPath.split('/').pop()}</td>
|
87
87
|
</tr>
|
88
88
|
<tr>
|
frontend/popup.css
CHANGED
@@ -9,6 +9,19 @@ div.floating-window.blocker{
|
|
9
9
|
z-index: 100;
|
10
10
|
}
|
11
11
|
|
12
|
+
@keyframes fade-in{
|
13
|
+
from{
|
14
|
+
opacity: 0;
|
15
|
+
transform: translate(-50%, calc(-50% + 0.25rem));
|
16
|
+
}
|
17
|
+
to{
|
18
|
+
opacity: 1;
|
19
|
+
transform: translate(-50%, -50%);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
div.floating-window.window{
|
23
|
+
animation: fade-in 0.1s ease-in;
|
24
|
+
}
|
12
25
|
div.floating-window.window{
|
13
26
|
position: fixed;
|
14
27
|
top: 50%;
|
frontend/scripts.js
CHANGED
@@ -277,6 +277,8 @@ function refreshFileList(){
|
|
277
277
|
infoButton.style.cursor = 'pointer';
|
278
278
|
infoButton.textContent = 'Details';
|
279
279
|
infoButton.style.width = '100%';
|
280
|
+
infoButton.style.display = 'block';
|
281
|
+
infoButton.style.textAlign = 'center';
|
280
282
|
infoButton.addEventListener('click', () => {
|
281
283
|
showDirInfoPanel(dir, userRecord, conn);
|
282
284
|
});
|
frontend/styles.css
CHANGED
@@ -219,4 +219,14 @@ a{
|
|
219
219
|
.delete-btn:hover{
|
220
220
|
color: white !important;
|
221
221
|
background-color: #990511c7 !important;
|
222
|
+
}
|
223
|
+
|
224
|
+
button{
|
225
|
+
transition: all 0.2s;
|
226
|
+
}
|
227
|
+
button:hover {
|
228
|
+
transform: scale(1.05);
|
229
|
+
}
|
230
|
+
button:active {
|
231
|
+
transform: scale(0.95);
|
222
232
|
}
|
lfss/cli/balance.py
CHANGED
@@ -3,10 +3,11 @@ Balance the storage by ensuring that large file thresholds are met.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
from lfss.src.config import LARGE_BLOB_DIR, LARGE_FILE_BYTES
|
6
|
-
import argparse, time
|
6
|
+
import argparse, time, itertools
|
7
7
|
from functools import wraps
|
8
8
|
from asyncio import Semaphore
|
9
9
|
import aiofiles, asyncio
|
10
|
+
from contextlib import contextmanager
|
10
11
|
from lfss.src.database import transaction, unique_cursor
|
11
12
|
from lfss.src.connection_pool import global_entrance
|
12
13
|
|
@@ -30,7 +31,6 @@ async def move_to_external(f_id: str, flag: str = ''):
|
|
30
31
|
if blob_row is None:
|
31
32
|
print(f"{flag}File {f_id} not found in blobs.fdata")
|
32
33
|
return
|
33
|
-
await c.execute("BEGIN")
|
34
34
|
blob: bytes = blob_row[0]
|
35
35
|
async with aiofiles.open(LARGE_BLOB_DIR / f_id, 'wb') as f:
|
36
36
|
await f.write(blob)
|
@@ -59,8 +59,7 @@ async def _main(batch_size: int = 10000):
|
|
59
59
|
start_time = time.time()
|
60
60
|
|
61
61
|
e_cout = 0
|
62
|
-
batch_count =
|
63
|
-
while True:
|
62
|
+
for batch_count in itertools.count(start=0):
|
64
63
|
async with unique_cursor() as conn:
|
65
64
|
exceeded_rows = list(await (await conn.execute(
|
66
65
|
"SELECT file_id FROM fmeta WHERE file_size > ? AND external = 0 LIMIT ? OFFSET ?",
|
@@ -76,8 +75,7 @@ async def _main(batch_size: int = 10000):
|
|
76
75
|
await asyncio.gather(*tasks)
|
77
76
|
|
78
77
|
i_count = 0
|
79
|
-
batch_count =
|
80
|
-
while True:
|
78
|
+
for batch_count in itertools.count(start=0):
|
81
79
|
async with unique_cursor() as conn:
|
82
80
|
under_rows = list(await (await conn.execute(
|
83
81
|
"SELECT file_id, file_size, external FROM fmeta WHERE file_size <= ? AND external = 1 LIMIT ? OFFSET ?",
|
@@ -95,15 +93,35 @@ async def _main(batch_size: int = 10000):
|
|
95
93
|
end_time = time.time()
|
96
94
|
print(f"Balancing complete, took {end_time - start_time:.2f} seconds. "
|
97
95
|
f"{e_cout} files moved to external storage, {i_count} files moved to internal storage.")
|
96
|
+
|
97
|
+
@global_entrance()
|
98
|
+
async def vacuum(index: bool = False, blobs: bool = False):
|
99
|
+
@contextmanager
|
100
|
+
def indicator(name: str):
|
101
|
+
print(f"\033[1;33mRunning {name}... \033[0m")
|
102
|
+
s = time.time()
|
103
|
+
yield
|
104
|
+
print(f"{name} took {time.time() - s:.2f} seconds")
|
105
|
+
|
106
|
+
async with unique_cursor(is_write=True) as c:
|
107
|
+
if index:
|
108
|
+
with indicator("VACUUM-index"):
|
109
|
+
await c.execute("VACUUM main")
|
110
|
+
if blobs:
|
111
|
+
with indicator("VACUUM-blobs"):
|
112
|
+
await c.execute("VACUUM blobs")
|
98
113
|
|
99
114
|
def main():
|
100
115
|
global sem
|
101
116
|
parser = argparse.ArgumentParser(description="Balance the storage by ensuring that large file thresholds are met.")
|
102
117
|
parser.add_argument("-j", "--jobs", type=int, default=2, help="Number of concurrent jobs")
|
103
118
|
parser.add_argument("-b", "--batch-size", type=int, default=10000, help="Batch size for processing files")
|
119
|
+
parser.add_argument("--vacuum", action="store_true", help="Run VACUUM only on index.db after balancing")
|
120
|
+
parser.add_argument("--vacuum-all", action="store_true", help="Run VACUUM on both index.db and blobs.db after balancing")
|
104
121
|
args = parser.parse_args()
|
105
122
|
sem = Semaphore(args.jobs)
|
106
123
|
asyncio.run(_main(args.batch_size))
|
124
|
+
asyncio.run(vacuum(index=args.vacuum or args.vacuum_all, blobs=args.vacuum_all))
|
107
125
|
|
108
126
|
if __name__ == '__main__':
|
109
127
|
main()
|
lfss/cli/user.py
CHANGED
@@ -1,20 +1,10 @@
|
|
1
1
|
import argparse, asyncio
|
2
2
|
from contextlib import asynccontextmanager
|
3
3
|
from .cli import parse_permission, FileReadPermission
|
4
|
+
from ..src.utils import parse_storage_size
|
4
5
|
from ..src.database import Database, FileReadPermission, transaction, UserConn
|
5
6
|
from ..src.connection_pool import global_entrance
|
6
7
|
|
7
|
-
def parse_storage_size(s: str) -> int:
|
8
|
-
if s[-1] in 'Kk':
|
9
|
-
return int(s[:-1]) * 1024
|
10
|
-
if s[-1] in 'Mm':
|
11
|
-
return int(s[:-1]) * 1024 * 1024
|
12
|
-
if s[-1] in 'Gg':
|
13
|
-
return int(s[:-1]) * 1024 * 1024 * 1024
|
14
|
-
if s[-1] in 'Tt':
|
15
|
-
return int(s[:-1]) * 1024 * 1024 * 1024 * 1024
|
16
|
-
return int(s)
|
17
|
-
|
18
8
|
@global_entrance(1)
|
19
9
|
async def _main():
|
20
10
|
parser = argparse.ArgumentParser()
|
lfss/src/config.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
from pathlib import Path
|
2
1
|
import os
|
2
|
+
from pathlib import Path
|
3
|
+
from .utils import parse_storage_size
|
3
4
|
|
4
5
|
__default_dir = '.storage_data'
|
5
6
|
|
@@ -12,6 +13,10 @@ LARGE_BLOB_DIR = DATA_HOME / 'large_blobs'
|
|
12
13
|
LARGE_BLOB_DIR.mkdir(exist_ok=True)
|
13
14
|
|
14
15
|
# https://sqlite.org/fasterthanfs.html
|
15
|
-
|
16
|
+
__env_large_file = os.environ.get('LFSS_LARGE_FILE', None)
|
17
|
+
if __env_large_file is not None:
|
18
|
+
LARGE_FILE_BYTES = parse_storage_size(__env_large_file)
|
19
|
+
else:
|
20
|
+
LARGE_FILE_BYTES = 8 * 1024 * 1024 # 8MB
|
16
21
|
MAX_FILE_BYTES = 512 * 1024 * 1024 # 512MB
|
17
22
|
MAX_BUNDLE_BYTES = 512 * 1024 * 1024 # 512MB
|
lfss/src/connection_pool.py
CHANGED
@@ -5,6 +5,7 @@ from contextlib import asynccontextmanager
|
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from asyncio import Semaphore, Lock
|
7
7
|
from functools import wraps
|
8
|
+
from typing import Callable, Awaitable
|
8
9
|
|
9
10
|
from .log import get_logger
|
10
11
|
from .config import DATA_HOME
|
@@ -125,7 +126,7 @@ async def global_connection(n_read: int = 1):
|
|
125
126
|
await global_connection_close()
|
126
127
|
|
127
128
|
def global_entrance(n_read: int = 1):
|
128
|
-
def decorator(func):
|
129
|
+
def decorator(func: Callable[..., Awaitable]):
|
129
130
|
@wraps(func)
|
130
131
|
async def wrapper(*args, **kwargs):
|
131
132
|
async with global_connection(n_read):
|
lfss/src/utils.py
CHANGED
@@ -62,6 +62,18 @@ def now_stamp() -> float:
|
|
62
62
|
def stamp_to_str(stamp: float) -> str:
|
63
63
|
return datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d %H:%M:%S')
|
64
64
|
|
65
|
+
def parse_storage_size(s: str) -> int:
|
66
|
+
""" Parse the file size string to bytes """
|
67
|
+
if s[-1].isdigit():
|
68
|
+
return int(s)
|
69
|
+
unit = s[-1].lower()
|
70
|
+
match unit:
|
71
|
+
case 'b': return int(s[:-1])
|
72
|
+
case 'k': return int(s[:-1]) * 1024
|
73
|
+
case 'm': return int(s[:-1]) * 1024**2
|
74
|
+
case 'g': return int(s[:-1]) * 1024**3
|
75
|
+
case 't': return int(s[:-1]) * 1024**4
|
76
|
+
case _: raise ValueError(f"Invalid file size string: {s}")
|
65
77
|
|
66
78
|
_FnReturnT = TypeVar('_FnReturnT')
|
67
79
|
_AsyncReturnT = Awaitable[_FnReturnT]
|
@@ -4,32 +4,32 @@ docs/Permission.md,sha256=X0VNfBKU52f93QYqcVyiBFJ3yURiSkhIo9S_5fdSgzM,2265
|
|
4
4
|
frontend/api.js,sha256=DxWmqO0AAOsWLXYtbgAzEnSmVyEJyzcxXSCH7H3STUk,7925
|
5
5
|
frontend/index.html,sha256=Mem8de9vwmZoe4x1DKqpu_aFgIBURqT3mIGdeOOTbIs,2051
|
6
6
|
frontend/info.css,sha256=Ny0N3GywQ3a9q1_Qph_QFEKB4fEnTe_2DJ1Y5OsLLmQ,595
|
7
|
-
frontend/info.js,sha256=
|
8
|
-
frontend/popup.css,sha256=
|
7
|
+
frontend/info.js,sha256=mXCQnRESSx7iiR1FW63sAXHnm0NZ3REeaRmnjZNeQbU,5454
|
8
|
+
frontend/popup.css,sha256=TJZYFW1ZcdD1IVTlNPYNtMWKPbN6XDbQ4hKBOFK8uLg,1284
|
9
9
|
frontend/popup.js,sha256=3PgaGZmxSdV1E-D_MWgcR7aHWkcsHA1BNKSOkmP66tA,5191
|
10
|
-
frontend/scripts.js,sha256=
|
11
|
-
frontend/styles.css,sha256=
|
10
|
+
frontend/scripts.js,sha256=OP99BSbnyTE1LJebGVUvV3WUnDBiZdqaC3a9SE1FF6U,20286
|
11
|
+
frontend/styles.css,sha256=37aU9Iep_hTz3LnAAAcEhC_I7AC0A4lX6apnMuGPTlA,4214
|
12
12
|
frontend/utils.js,sha256=Ts4nlef8pkrEgpwX-uQwAhWvwxlIzex8ijDLNCa22ps,2372
|
13
|
-
lfss/cli/balance.py,sha256=
|
13
|
+
lfss/cli/balance.py,sha256=X6Q6e7sb6LMlCXG4qSD2MUMlixV2Kc9EXPluIe2S5DA,5090
|
14
14
|
lfss/cli/cli.py,sha256=LH1nx5wI1K2DZ3hvHz7oq5HcXVDoW2V6sr7q9gJ8gqo,4621
|
15
15
|
lfss/cli/panel.py,sha256=iGdVmdWYjA_7a78ZzWEB_3ggIOBeUKTzg6F5zLaB25c,1401
|
16
16
|
lfss/cli/serve.py,sha256=bO3GT0kuylMGN-7bZWP4e71MlugGZ_lEMkYaYld_Ntg,985
|
17
|
-
lfss/cli/user.py,sha256
|
17
|
+
lfss/cli/user.py,sha256=ETLtj0N-kmxv0mhmeAsO6cY7kPq7nOOP4DetxIRoQpQ,3405
|
18
18
|
lfss/client/__init__.py,sha256=8uvcKs3PYQamDd_cjfN-fX9QUohEzJqeJlOYkBlzC3M,4556
|
19
19
|
lfss/client/api.py,sha256=kSkB4wADTu012-1wl6v90OiZrw6aTQ42GU4jtV4KO0k,5764
|
20
20
|
lfss/sql/init.sql,sha256=C-JtQAlaOjESI8uoF1Y_9dKukEVSw5Ll-7yA3gG-XHU,1210
|
21
21
|
lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
|
22
22
|
lfss/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
-
lfss/src/config.py,sha256=
|
24
|
-
lfss/src/connection_pool.py,sha256=
|
23
|
+
lfss/src/config.py,sha256=CIbVFWRu86dl2GVlXlCDv93W8PLwT89NtznU6TCKvtk,729
|
24
|
+
lfss/src/connection_pool.py,sha256=r4Ho5d_Gd4S_KbT7515UJoiyfIgS6xyttqMsKqOfaIg,5190
|
25
25
|
lfss/src/database.py,sha256=-itbpGb7cQrywZLFk4aNcuy38Krsyemtyiz8GIt4i7M,31944
|
26
26
|
lfss/src/datatype.py,sha256=BLS7vuuKnFZQg0nrKeP9SymqUhcN6HwPgejU0yBd_Ak,1622
|
27
27
|
lfss/src/error.py,sha256=imbhwnbhnI3HLhkbfICROe3F0gleKrOk4XnqHJDOtuI,285
|
28
28
|
lfss/src/log.py,sha256=xOnkuH-gB_jSVGqNnDVEW05iki6SCJ2xdEhjz5eEsMo,5136
|
29
29
|
lfss/src/server.py,sha256=EA5fK4qc98tF8qoS9F6VaxIE65D5X8Ztkjqy8EUYIv8,16276
|
30
30
|
lfss/src/stat.py,sha256=hTMtQyM_Ukmhc33Bb9FGCfBMIX02KrGHQg8nL7sC8sU,2082
|
31
|
-
lfss/src/utils.py,sha256=
|
32
|
-
lfss-0.7.
|
33
|
-
lfss-0.7.
|
34
|
-
lfss-0.7.
|
35
|
-
lfss-0.7.
|
31
|
+
lfss/src/utils.py,sha256=S9LCJ5OkNk_zM4rZnrHg1UDjnNkDVO_ejmfsBeNJs4s,3868
|
32
|
+
lfss-0.7.8.dist-info/METADATA,sha256=JwNtzTpXrk_e4ydOV286AkNBUANiIMlDxPiptRAoHH4,1967
|
33
|
+
lfss-0.7.8.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
34
|
+
lfss-0.7.8.dist-info/entry_points.txt,sha256=d_Ri3GXxUW-S0E6q953A8od0YMmUAnZGlJSKS46OiW8,172
|
35
|
+
lfss-0.7.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|