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 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">Filename</td>
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">Size</td>
31
- <td class="info-table-value">${formatSize(r.file_size)}</td>
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">Owner</td>
35
- <td class="info-table-value">${r.owner_id}</td>
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">Pathname</td>
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 = 0
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 = 0
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
- LARGE_FILE_BYTES = 8 * 1024 * 1024 # 8MB
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
@@ -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]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lfss
3
- Version: 0.7.7
3
+ Version: 0.7.8
4
4
  Summary: Lightweight file storage service
5
5
  Home-page: https://github.com/MenxLi/lfss
6
6
  Author: li, mengxun
@@ -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=N3fAniM0Zir_P9t-Mt9_zOWEeqDuhA2uDRfIZsOHn4w,5459
8
- frontend/popup.css,sha256=06Z3ut9-RviCUB2CjiONuEyJRpV3M7LAT6Qzl_nYf9s,1025
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=zp_ENEnWxiwZRM_gE7HP0j_MXEI8yActO6cQfiTkaD8,20171
11
- frontend/styles.css,sha256=l5_SECKR6vkEe4llydRIFxx0C2SOqcacmdMrtdLZRVM,4086
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=heOgwH6oNnfYsKJfA4VxWKdEXPstdVbbRXWxcDqLIS0,4176
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=-ePmx_jhqfPQDl_9i_C_St9ujlCyyWSqNJUvc26v4_4,3686
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=aTfjWORE9Mx7LSEjbfmHnULlrmIWEvEBSZ4fJKWZNjM,530
24
- lfss/src/connection_pool.py,sha256=teW_4DMiwlCN_bS7AhjkbY9cHZqUFlmHE_J2yPjHVsA,5125
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=LXsjNatuwg3iDCKTBgC-t7iEuM2X_mPsgNdDy92zhwo,3405
32
- lfss-0.7.7.dist-info/METADATA,sha256=j3VO-GRmoC-_fseS5NAUcUJaOMBUyIx0OS9Amiji9Hg,1967
33
- lfss-0.7.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
34
- lfss-0.7.7.dist-info/entry_points.txt,sha256=d_Ri3GXxUW-S0E6q953A8od0YMmUAnZGlJSKS46OiW8,172
35
- lfss-0.7.7.dist-info/RECORD,,
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