lfss 0.7.7__tar.gz → 0.7.8__tar.gz

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.
Files changed (33) hide show
  1. {lfss-0.7.7 → lfss-0.7.8}/PKG-INFO +1 -1
  2. {lfss-0.7.7 → lfss-0.7.8}/frontend/info.js +10 -10
  3. {lfss-0.7.7 → lfss-0.7.8}/frontend/popup.css +13 -0
  4. {lfss-0.7.7 → lfss-0.7.8}/frontend/scripts.js +2 -0
  5. {lfss-0.7.7 → lfss-0.7.8}/frontend/styles.css +10 -0
  6. {lfss-0.7.7 → lfss-0.7.8}/lfss/cli/balance.py +24 -6
  7. {lfss-0.7.7 → lfss-0.7.8}/lfss/cli/user.py +1 -11
  8. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/config.py +7 -2
  9. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/connection_pool.py +2 -1
  10. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/utils.py +12 -0
  11. {lfss-0.7.7 → lfss-0.7.8}/pyproject.toml +1 -1
  12. {lfss-0.7.7 → lfss-0.7.8}/Readme.md +0 -0
  13. {lfss-0.7.7 → lfss-0.7.8}/docs/Known_issues.md +0 -0
  14. {lfss-0.7.7 → lfss-0.7.8}/docs/Permission.md +0 -0
  15. {lfss-0.7.7 → lfss-0.7.8}/frontend/api.js +0 -0
  16. {lfss-0.7.7 → lfss-0.7.8}/frontend/index.html +0 -0
  17. {lfss-0.7.7 → lfss-0.7.8}/frontend/info.css +0 -0
  18. {lfss-0.7.7 → lfss-0.7.8}/frontend/popup.js +0 -0
  19. {lfss-0.7.7 → lfss-0.7.8}/frontend/utils.js +0 -0
  20. {lfss-0.7.7 → lfss-0.7.8}/lfss/cli/cli.py +0 -0
  21. {lfss-0.7.7 → lfss-0.7.8}/lfss/cli/panel.py +0 -0
  22. {lfss-0.7.7 → lfss-0.7.8}/lfss/cli/serve.py +0 -0
  23. {lfss-0.7.7 → lfss-0.7.8}/lfss/client/__init__.py +0 -0
  24. {lfss-0.7.7 → lfss-0.7.8}/lfss/client/api.py +0 -0
  25. {lfss-0.7.7 → lfss-0.7.8}/lfss/sql/init.sql +0 -0
  26. {lfss-0.7.7 → lfss-0.7.8}/lfss/sql/pragma.sql +0 -0
  27. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/__init__.py +0 -0
  28. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/database.py +0 -0
  29. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/datatype.py +0 -0
  30. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/error.py +0 -0
  31. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/log.py +0 -0
  32. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/server.py +0 -0
  33. {lfss-0.7.7 → lfss-0.7.8}/lfss/src/stat.py +0 -0
@@ -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
@@ -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>
@@ -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%;
@@ -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
  });
@@ -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
  }
@@ -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()
@@ -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()
@@ -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):
@@ -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
  [tool.poetry]
2
2
  name = "lfss"
3
- version = "0.7.7"
3
+ version = "0.7.8"
4
4
  description = "Lightweight file storage service"
5
5
  authors = ["li, mengxun <limengxun45@outlook.com>"]
6
6
  readme = "Readme.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes