lfss 0.7.1__py3-none-any.whl → 0.7.2__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 +4 -2
- lfss/cli/balance.py +14 -32
- lfss/src/config.py +2 -2
- lfss/src/connection_pool.py +16 -15
- lfss/src/database.py +7 -1
- {lfss-0.7.1.dist-info → lfss-0.7.2.dist-info}/METADATA +5 -3
- {lfss-0.7.1.dist-info → lfss-0.7.2.dist-info}/RECORD +9 -9
- {lfss-0.7.1.dist-info → lfss-0.7.2.dist-info}/WHEEL +0 -0
- {lfss-0.7.1.dist-info → lfss-0.7.2.dist-info}/entry_points.txt +0 -0
Readme.md
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
# Lightweight File Storage Service (LFSS)
|
2
2
|
[](https://pypi.org/project/lfss/)
|
3
3
|
|
4
|
-
|
4
|
+
My experiment on a lightweight file/object storage service.
|
5
|
+
It stores small files and metadata in sqlite, large files in the filesystem, and serves them through a simple REST API.
|
6
|
+
Tested on 2 million files, and it works fine... thanks to the sqlite database!
|
5
7
|
|
6
8
|
Usage:
|
7
9
|
```sh
|
@@ -21,7 +23,7 @@ Or, you can start a web server at `/frontend` and open `index.html` in your brow
|
|
21
23
|
|
22
24
|
The API usage is simple, just `GET`, `PUT`, `DELETE` to the `/<username>/file/url` path.
|
23
25
|
Authentication is done via `Authorization` header with the value `Bearer <token>`, or through the `token` query parameter.
|
24
|
-
You can refer to `frontend` as an application example, and `frontend/api.js` or `lfss
|
26
|
+
You can refer to `frontend` as an application example, and `frontend/api.js` or `lfss/client/api.py` for the API usage.
|
25
27
|
|
26
28
|
By default, the service exposes all files to the public for `GET` requests,
|
27
29
|
but file-listing is restricted to the user's own files.
|
lfss/cli/balance.py
CHANGED
@@ -24,28 +24,19 @@ def barriered(func):
|
|
24
24
|
|
25
25
|
@barriered
|
26
26
|
async def move_to_external(f_id: str, flag: str = ''):
|
27
|
-
# async with aiosqlite.connect(db_file, timeout = 60) as c:
|
28
27
|
async with transaction() as c:
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
cursor = await c.execute( "SELECT data FROM blobs.fdata WHERE file_id = ?", (f_id,))
|
29
|
+
blob_row = await cursor.fetchone()
|
30
|
+
if blob_row is None:
|
31
|
+
print(f"{flag}File {f_id} not found in blobs.fdata")
|
32
|
+
return
|
34
33
|
await c.execute("BEGIN")
|
35
34
|
blob: bytes = blob_row[0]
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
await c.commit()
|
42
|
-
print(f"{flag}Moved {f_id} to external storage")
|
43
|
-
except Exception as e:
|
44
|
-
await c.rollback()
|
45
|
-
print(f"{flag}Error moving {f_id}: {e}")
|
46
|
-
|
47
|
-
if isinstance(e, KeyboardInterrupt):
|
48
|
-
raise e
|
35
|
+
async with aiofiles.open(LARGE_BLOB_DIR / f_id, 'wb') as f:
|
36
|
+
await f.write(blob)
|
37
|
+
await c.execute( "UPDATE fmeta SET external = 1 WHERE file_id = ?", (f_id,))
|
38
|
+
await c.execute( "DELETE FROM blobs.fdata WHERE file_id = ?", (f_id,))
|
39
|
+
print(f"{flag}Moved {f_id} to external storage")
|
49
40
|
|
50
41
|
@barriered
|
51
42
|
async def move_to_internal(f_id: str, flag: str = ''):
|
@@ -56,19 +47,10 @@ async def move_to_internal(f_id: str, flag: str = ''):
|
|
56
47
|
async with aiofiles.open(LARGE_BLOB_DIR / f_id, 'rb') as f:
|
57
48
|
blob = await f.read()
|
58
49
|
|
59
|
-
await c.execute("
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
await c.commit()
|
64
|
-
(LARGE_BLOB_DIR / f_id).unlink(missing_ok=True)
|
65
|
-
print(f"{flag}Moved {f_id} to internal storage")
|
66
|
-
except Exception as e:
|
67
|
-
await c.rollback()
|
68
|
-
print(f"{flag}Error moving {f_id}: {e}")
|
69
|
-
if isinstance(e, KeyboardInterrupt):
|
70
|
-
raise e
|
71
|
-
|
50
|
+
await c.execute("INSERT INTO blobs.fdata (file_id, data) VALUES (?, ?)", (f_id, blob))
|
51
|
+
await c.execute("UPDATE fmeta SET external = 0 WHERE file_id = ?", (f_id,))
|
52
|
+
(LARGE_BLOB_DIR / f_id).unlink(missing_ok=True)
|
53
|
+
print(f"{flag}Moved {f_id} to internal storage")
|
72
54
|
|
73
55
|
@global_entrance()
|
74
56
|
async def _main(batch_size: int = 10000):
|
lfss/src/config.py
CHANGED
@@ -13,5 +13,5 @@ LARGE_BLOB_DIR.mkdir(exist_ok=True)
|
|
13
13
|
|
14
14
|
# https://sqlite.org/fasterthanfs.html
|
15
15
|
LARGE_FILE_BYTES = 8 * 1024 * 1024 # 8MB
|
16
|
-
MAX_FILE_BYTES =
|
17
|
-
MAX_BUNDLE_BYTES =
|
16
|
+
MAX_FILE_BYTES = 512 * 1024 * 1024 # 512MB
|
17
|
+
MAX_BUNDLE_BYTES = 512 * 1024 * 1024 # 512MB
|
lfss/src/connection_pool.py
CHANGED
@@ -66,26 +66,27 @@ class SqlConnectionPool:
|
|
66
66
|
if len(self._connections) == 0:
|
67
67
|
raise Exception("No available connections, please init the pool first")
|
68
68
|
|
69
|
-
if w:
|
70
|
-
assert self._w_connection
|
71
|
-
if self._w_connection.is_available:
|
72
|
-
self._w_connection.is_available = False
|
73
|
-
return self._w_connection
|
74
|
-
raise Exception("Write connection is not available")
|
75
|
-
|
76
69
|
async with self._lock:
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
70
|
+
if w:
|
71
|
+
assert self._w_connection
|
72
|
+
if self._w_connection.is_available:
|
73
|
+
self._w_connection.is_available = False
|
74
|
+
return self._w_connection
|
75
|
+
raise Exception("Write connection is not available")
|
76
|
+
|
77
|
+
else:
|
78
|
+
for c in self._connections:
|
79
|
+
if c.is_available:
|
80
|
+
c.is_available = False
|
81
|
+
return c
|
81
82
|
raise Exception("No available connections, impossible?")
|
82
83
|
|
83
84
|
async def release(self, conn: SqlConnection):
|
84
|
-
if conn == self._w_connection:
|
85
|
-
conn.is_available = True
|
86
|
-
return
|
87
|
-
|
88
85
|
async with self._lock:
|
86
|
+
if conn == self._w_connection:
|
87
|
+
conn.is_available = True
|
88
|
+
return
|
89
|
+
|
89
90
|
if not conn in self._connections:
|
90
91
|
raise Exception("Connection not in pool")
|
91
92
|
conn.is_available = True
|
lfss/src/database.py
CHANGED
@@ -519,7 +519,13 @@ class Database:
|
|
519
519
|
raise FileNotFoundError(f"File {url} not found")
|
520
520
|
if not r.external:
|
521
521
|
raise ValueError(f"File {url} is not stored externally, should use read_file instead")
|
522
|
-
|
522
|
+
ret = fconn.get_file_blob_external(r.file_id)
|
523
|
+
|
524
|
+
async with transaction() as w_cur:
|
525
|
+
await FileConn(w_cur).log_access(url)
|
526
|
+
|
527
|
+
return ret
|
528
|
+
|
523
529
|
|
524
530
|
async def read_file(self, url: str) -> bytes:
|
525
531
|
validate_url(url)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: lfss
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.2
|
4
4
|
Summary: Lightweight file storage service
|
5
5
|
Home-page: https://github.com/MenxLi/lfss
|
6
6
|
Author: li, mengxun
|
@@ -21,7 +21,9 @@ Description-Content-Type: text/markdown
|
|
21
21
|
# Lightweight File Storage Service (LFSS)
|
22
22
|
[](https://pypi.org/project/lfss/)
|
23
23
|
|
24
|
-
|
24
|
+
My experiment on a lightweight file/object storage service.
|
25
|
+
It stores small files and metadata in sqlite, large files in the filesystem, and serves them through a simple REST API.
|
26
|
+
Tested on 2 million files, and it works fine... thanks to the sqlite database!
|
25
27
|
|
26
28
|
Usage:
|
27
29
|
```sh
|
@@ -41,7 +43,7 @@ Or, you can start a web server at `/frontend` and open `index.html` in your brow
|
|
41
43
|
|
42
44
|
The API usage is simple, just `GET`, `PUT`, `DELETE` to the `/<username>/file/url` path.
|
43
45
|
Authentication is done via `Authorization` header with the value `Bearer <token>`, or through the `token` query parameter.
|
44
|
-
You can refer to `frontend` as an application example, and `frontend/api.js` or `lfss
|
46
|
+
You can refer to `frontend` as an application example, and `frontend/api.js` or `lfss/client/api.py` for the API usage.
|
45
47
|
|
46
48
|
By default, the service exposes all files to the public for `GET` requests,
|
47
49
|
but file-listing is restricted to the user's own files.
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Readme.md,sha256=
|
1
|
+
Readme.md,sha256=t5dninN6gFzve_NySxqGrEkmAYhF1h1RhaMdCOeCILA,1348
|
2
2
|
docs/Known_issues.md,sha256=rfdG3j1OJF-59S9E06VPyn0nZKbW-ybPxkoZ7MEZWp8,81
|
3
3
|
docs/Permission.md,sha256=EY1Y4tT5PDdHDb2pXsVgMAJXxkUihTZfPT0_z7Q53FA,1485
|
4
4
|
frontend/api.js,sha256=-ouhsmucEunAK3m1H__MqffQkXAjoeVEfM15BvqfIZs,7677
|
@@ -8,7 +8,7 @@ frontend/popup.js,sha256=3PgaGZmxSdV1E-D_MWgcR7aHWkcsHA1BNKSOkmP66tA,5191
|
|
8
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=
|
11
|
+
lfss/cli/balance.py,sha256=heOgwH6oNnfYsKJfA4VxWKdEXPstdVbbRXWxcDqLIS0,4176
|
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
|
@@ -18,16 +18,16 @@ lfss/client/api.py,sha256=ICqpcyvSf-9QmYNv9EQ5fA_MViSuLxSNn-CIBNqWkW8,5414
|
|
18
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
|
-
lfss/src/config.py,sha256=
|
22
|
-
lfss/src/connection_pool.py,sha256=
|
23
|
-
lfss/src/database.py,sha256=
|
21
|
+
lfss/src/config.py,sha256=G-tOdk6RrE62_d-QoD6WXD4l70YriBazUFdumTz3P20,529
|
22
|
+
lfss/src/connection_pool.py,sha256=qBtXr2j_O7vGI2Mv0HVsza35T8nljIx85jLlyEv1SgE,4918
|
23
|
+
lfss/src/database.py,sha256=tGcHfxThmRTmtLIlEWdJtAe1tzP64wHSC1JcbQdRU-8,31226
|
24
24
|
lfss/src/datatype.py,sha256=BLS7vuuKnFZQg0nrKeP9SymqUhcN6HwPgejU0yBd_Ak,1622
|
25
25
|
lfss/src/error.py,sha256=imbhwnbhnI3HLhkbfICROe3F0gleKrOk4XnqHJDOtuI,285
|
26
26
|
lfss/src/log.py,sha256=qNE04sHoZ2rMbQ5dR2zT7Xaz1KVAfYp5hKpWJX42S9g,5244
|
27
27
|
lfss/src/server.py,sha256=9mnBlcHJruwOV-aoHa1P146F4khpVDmif7rq58yZX3U,15306
|
28
28
|
lfss/src/stat.py,sha256=hTMtQyM_Ukmhc33Bb9FGCfBMIX02KrGHQg8nL7sC8sU,2082
|
29
29
|
lfss/src/utils.py,sha256=8VkrtpSmurbMiX7GyK-n7Grvzy3uwSJXHdONEsuLCDI,2272
|
30
|
-
lfss-0.7.
|
31
|
-
lfss-0.7.
|
32
|
-
lfss-0.7.
|
33
|
-
lfss-0.7.
|
30
|
+
lfss-0.7.2.dist-info/METADATA,sha256=pJcPBmMpKwNujl4CWZKRchT3uXyCPaEvi1mek7SUJUE,2040
|
31
|
+
lfss-0.7.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
32
|
+
lfss-0.7.2.dist-info/entry_points.txt,sha256=d_Ri3GXxUW-S0E6q953A8od0YMmUAnZGlJSKS46OiW8,172
|
33
|
+
lfss-0.7.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|