lfss 0.4.0__py3-none-any.whl → 0.4.1__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/api.js +8 -2
- frontend/scripts.js +1 -1
- lfss/cli/cli.py +48 -0
- lfss/client/__init__.py +58 -0
- lfss/client/api.py +7 -2
- lfss/src/database.py +2 -2
- lfss/src/server.py +13 -6
- {lfss-0.4.0.dist-info → lfss-0.4.1.dist-info}/METADATA +1 -1
- {lfss-0.4.0.dist-info → lfss-0.4.1.dist-info}/RECORD +11 -10
- {lfss-0.4.0.dist-info → lfss-0.4.1.dist-info}/entry_points.txt +1 -0
- {lfss-0.4.0.dist-info → lfss-0.4.1.dist-info}/WHEEL +0 -0
frontend/api.js
CHANGED
@@ -54,10 +54,16 @@ export default class Connector {
|
|
54
54
|
* @param {File} file - the file to upload
|
55
55
|
* @returns {Promise<string>} - the promise of the request, the url of the file
|
56
56
|
*/
|
57
|
-
async put(path, file
|
57
|
+
async put(path, file, {
|
58
|
+
overwrite = false,
|
59
|
+
permission = 0
|
60
|
+
} = {}){
|
58
61
|
if (path.startsWith('/')){ path = path.slice(1); }
|
59
62
|
const fileBytes = await file.arrayBuffer();
|
60
|
-
const
|
63
|
+
const dst = new URL(this.config.endpoint + '/' + path);
|
64
|
+
dst.searchParams.append('overwrite', overwrite);
|
65
|
+
dst.searchParams.append('permission', permission);
|
66
|
+
const res = await fetch(dst.toString(), {
|
61
67
|
method: 'PUT',
|
62
68
|
headers: {
|
63
69
|
'Authorization': 'Bearer ' + this.config.token,
|
frontend/scripts.js
CHANGED
@@ -172,7 +172,7 @@ Are you sure you want to proceed?
|
|
172
172
|
async function uploadFile(...args){
|
173
173
|
const [file, path] = args;
|
174
174
|
try{
|
175
|
-
await conn.put(path, file);
|
175
|
+
await conn.put(path, file, {overwrite: true});
|
176
176
|
}
|
177
177
|
catch (err){
|
178
178
|
showPopup('Failed to upload file [' + file.name + ']: ' + err, {level: 'error', timeout: 5000});
|
lfss/cli/cli.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
from lfss.client import Connector, upload_directory
|
2
|
+
from lfss.src.database import FileReadPermission
|
3
|
+
from pathlib import Path
|
4
|
+
import argparse
|
5
|
+
|
6
|
+
def parse_arguments():
|
7
|
+
parser = argparse.ArgumentParser(description="Command line interface, please set LFSS_ENDPOINT and LFSS_TOKEN environment variables.")
|
8
|
+
|
9
|
+
sp = parser.add_subparsers(dest="command", required=True)
|
10
|
+
|
11
|
+
# upload
|
12
|
+
sp_upload = sp.add_parser("upload", help="Upload files")
|
13
|
+
sp_upload.add_argument("src", help="Source file or directory", type=str)
|
14
|
+
sp_upload.add_argument("dst", help="Destination path", type=str)
|
15
|
+
sp_upload.add_argument("-j", "--jobs", type=int, default=1, help="Number of concurrent uploads")
|
16
|
+
sp_upload.add_argument("--interval", type=float, default=0, help="Interval between retries, only works with directory upload")
|
17
|
+
sp_upload.add_argument("--overwrite", action="store_true", help="Overwrite existing files")
|
18
|
+
sp_upload.add_argument("--permission", type=FileReadPermission, default=FileReadPermission.UNSET, help="File permission")
|
19
|
+
sp_upload.add_argument("--retries", type=int, default=0, help="Number of retries, only works with directory upload")
|
20
|
+
|
21
|
+
return parser.parse_args()
|
22
|
+
|
23
|
+
def main():
|
24
|
+
args = parse_arguments()
|
25
|
+
connector = Connector()
|
26
|
+
if args.command == "upload":
|
27
|
+
src_path = Path(args.src)
|
28
|
+
if src_path.is_dir():
|
29
|
+
upload_directory(
|
30
|
+
connector, args.src, args.dst,
|
31
|
+
verbose=True,
|
32
|
+
n_concurrent=args.jobs,
|
33
|
+
n_reties=args.retries,
|
34
|
+
interval=args.interval,
|
35
|
+
overwrite=args.overwrite,
|
36
|
+
permission=args.permission
|
37
|
+
)
|
38
|
+
else:
|
39
|
+
with open(args.src, 'rb') as f:
|
40
|
+
connector.put(
|
41
|
+
args.dst,
|
42
|
+
f.read(),
|
43
|
+
overwrite=args.overwrite,
|
44
|
+
permission=args.permission
|
45
|
+
)
|
46
|
+
else:
|
47
|
+
raise NotImplementedError(f"Command {args.command} not implemented.")
|
48
|
+
|
lfss/client/__init__.py
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
import os, time
|
2
|
+
from threading import Lock
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
4
|
+
from .api import Connector
|
5
|
+
|
6
|
+
def upload_directory(
|
7
|
+
connector: Connector,
|
8
|
+
directory: str,
|
9
|
+
path: str,
|
10
|
+
n_concurrent: int = 1,
|
11
|
+
n_reties: int = 0,
|
12
|
+
interval: float = 0,
|
13
|
+
verbose: bool = False,
|
14
|
+
**put_kwargs
|
15
|
+
) -> list[str]:
|
16
|
+
assert path.endswith('/'), "Path must end with a slash."
|
17
|
+
if path.startswith('/'):
|
18
|
+
path = path[1:]
|
19
|
+
|
20
|
+
_counter = 0
|
21
|
+
_counter_lock = Lock()
|
22
|
+
|
23
|
+
faild_files = []
|
24
|
+
def put_file(file_path):
|
25
|
+
with _counter_lock:
|
26
|
+
nonlocal _counter
|
27
|
+
_counter += 1
|
28
|
+
this_count = _counter
|
29
|
+
dst_path = f"{path}{os.path.relpath(file_path, directory)}"
|
30
|
+
if verbose:
|
31
|
+
print(f"[{this_count}] Uploading {file_path} to {dst_path}")
|
32
|
+
|
33
|
+
this_try = 0
|
34
|
+
with open(file_path, 'rb') as f:
|
35
|
+
blob = f.read()
|
36
|
+
|
37
|
+
while this_try <= n_reties:
|
38
|
+
try:
|
39
|
+
connector.put(dst_path, blob, **put_kwargs)
|
40
|
+
break
|
41
|
+
except Exception as e:
|
42
|
+
if verbose:
|
43
|
+
print(f"[{this_count}] Error uploading {file_path}: {e}, retrying...")
|
44
|
+
this_try += 1
|
45
|
+
finally:
|
46
|
+
time.sleep(interval)
|
47
|
+
|
48
|
+
if this_try > n_reties:
|
49
|
+
faild_files.append(file_path)
|
50
|
+
if verbose:
|
51
|
+
print(f"[{this_count}] Failed to upload {file_path} after {n_reties} retries.")
|
52
|
+
|
53
|
+
with ThreadPoolExecutor(n_concurrent) as executor:
|
54
|
+
for root, dirs, files in os.walk(directory):
|
55
|
+
for file in files:
|
56
|
+
executor.submit(put_file, os.path.join(root, file))
|
57
|
+
|
58
|
+
return faild_files
|
lfss/client/api.py
CHANGED
@@ -34,9 +34,14 @@ class Connector:
|
|
34
34
|
return response
|
35
35
|
return f
|
36
36
|
|
37
|
-
def put(self, path: str, file_data: bytes):
|
37
|
+
def put(self, path: str, file_data: bytes, permission: int | FileReadPermission = 0, overwrite: bool = False):
|
38
38
|
"""Uploads a file to the specified path."""
|
39
|
-
|
39
|
+
if path.startswith('/'):
|
40
|
+
path = path[1:]
|
41
|
+
response = self._fetch('PUT', path, search_params={
|
42
|
+
'permission': int(permission),
|
43
|
+
'overwrite': overwrite
|
44
|
+
})(
|
40
45
|
data=file_data,
|
41
46
|
headers={'Content-Type': 'application/octet-stream'}
|
42
47
|
)
|
lfss/src/database.py
CHANGED
@@ -543,7 +543,7 @@ class Database:
|
|
543
543
|
if _g_conn is not None:
|
544
544
|
await _g_conn.rollback()
|
545
545
|
|
546
|
-
async def save_file(self, u: int | str, url: str, blob: bytes):
|
546
|
+
async def save_file(self, u: int | str, url: str, blob: bytes, permission: FileReadPermission = FileReadPermission.UNSET):
|
547
547
|
validate_url(url)
|
548
548
|
assert isinstance(blob, bytes), "blob must be bytes"
|
549
549
|
|
@@ -571,7 +571,7 @@ class Database:
|
|
571
571
|
f_id = uuid.uuid4().hex
|
572
572
|
async with transaction(self):
|
573
573
|
await self.file.set_file_blob(f_id, blob)
|
574
|
-
await self.file.set_file_record(url, owner_id=user.id, file_id=f_id, file_size=file_size)
|
574
|
+
await self.file.set_file_record(url, owner_id=user.id, file_id=f_id, file_size=file_size, permission=permission)
|
575
575
|
await self.user.set_active(user.username)
|
576
576
|
|
577
577
|
# async def read_file_stream(self, url: str): ...
|
lfss/src/server.py
CHANGED
@@ -162,7 +162,12 @@ async def get_file(path: str, download = False, user: UserRecord = Depends(get_c
|
|
162
162
|
|
163
163
|
@router_fs.put("/{path:path}")
|
164
164
|
@handle_exception
|
165
|
-
async def put_file(
|
165
|
+
async def put_file(
|
166
|
+
request: Request,
|
167
|
+
path: str,
|
168
|
+
overwrite: Optional[bool] = False,
|
169
|
+
permission: int = 0,
|
170
|
+
user: UserRecord = Depends(get_current_user)):
|
166
171
|
path = ensure_uri_compnents(path)
|
167
172
|
if user.id == 0:
|
168
173
|
logger.debug("Reject put request from DECOY_USER")
|
@@ -182,8 +187,10 @@ async def put_file(request: Request, path: str, user: UserRecord = Depends(get_c
|
|
182
187
|
exists_flag = False
|
183
188
|
file_record = await conn.file.get_file_record(path)
|
184
189
|
if file_record:
|
185
|
-
|
190
|
+
if not overwrite:
|
191
|
+
raise HTTPException(status_code=409, detail="File exists")
|
186
192
|
# remove the old file
|
193
|
+
exists_flag = True
|
187
194
|
await conn.delete_file(path)
|
188
195
|
|
189
196
|
# check content-type
|
@@ -191,20 +198,20 @@ async def put_file(request: Request, path: str, user: UserRecord = Depends(get_c
|
|
191
198
|
logger.debug(f"Content-Type: {content_type}")
|
192
199
|
if content_type == "application/json":
|
193
200
|
body = await request.json()
|
194
|
-
await conn.save_file(user.id, path, json.dumps(body).encode('utf-8'))
|
201
|
+
await conn.save_file(user.id, path, json.dumps(body).encode('utf-8'), permission = FileReadPermission(permission))
|
195
202
|
elif content_type == "application/x-www-form-urlencoded":
|
196
203
|
# may not work...
|
197
204
|
body = await request.form()
|
198
205
|
file = body.get("file")
|
199
206
|
if isinstance(file, str) or file is None:
|
200
207
|
raise HTTPException(status_code=400, detail="Invalid form data, file required")
|
201
|
-
await conn.save_file(user.id, path, await file.read())
|
208
|
+
await conn.save_file(user.id, path, await file.read(), permission = FileReadPermission(permission))
|
202
209
|
elif content_type == "application/octet-stream":
|
203
210
|
body = await request.body()
|
204
|
-
await conn.save_file(user.id, path, body)
|
211
|
+
await conn.save_file(user.id, path, body, permission = FileReadPermission(permission))
|
205
212
|
else:
|
206
213
|
body = await request.body()
|
207
|
-
await conn.save_file(user.id, path, body)
|
214
|
+
await conn.save_file(user.id, path, body, permission = FileReadPermission(permission))
|
208
215
|
|
209
216
|
# https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods/PUT
|
210
217
|
if exists_flag:
|
@@ -1,27 +1,28 @@
|
|
1
1
|
Readme.md,sha256=8ZAADV1K20gEDhwiL-Qq_rBAePlwWQn8c5uJ_X7OCIg,1128
|
2
2
|
docs/Known_issues.md,sha256=rfdG3j1OJF-59S9E06VPyn0nZKbW-ybPxkoZ7MEZWp8,81
|
3
3
|
docs/Permission.md,sha256=EY1Y4tT5PDdHDb2pXsVgMAJXxkUihTZfPT0_z7Q53FA,1485
|
4
|
-
frontend/api.js,sha256=
|
4
|
+
frontend/api.js,sha256=vt6r9X0sWZTQwS7DEpysX7QocuXO7HQgZb98NwHknp8,7506
|
5
5
|
frontend/index.html,sha256=VPJDs2LG8ep9kjlsKzjWzpN9vc1VGgdvOUlNTZWyQoQ,2088
|
6
6
|
frontend/popup.css,sha256=VzkjG1ZTLxhHMtTyobnlvqYmVsTmdbJJed2Pu1cc06c,1007
|
7
7
|
frontend/popup.js,sha256=dH5n7C2Vo9gCsMfQ4ajL4D1ETl0Wk9rIldxUb7x0f_c,4634
|
8
|
-
frontend/scripts.js,sha256=
|
8
|
+
frontend/scripts.js,sha256=xx7jybD8kAwuHheC4zDGAaKk0RgQ4g29SfbLamKHdC8,18285
|
9
9
|
frontend/styles.css,sha256=Ql_-W5dbh6LlJL2Kez8qsqPSF2fckFgmOYP4SMfKJVs,4065
|
10
10
|
frontend/utils.js,sha256=biE2te5ezswZyuwlDTYbHEt7VWKfCcUrusNt1lHjkLw,2263
|
11
|
+
lfss/cli/cli.py,sha256=DtxkJ8u96zVmn2pmsIy_jP1-LClyrLLUEVle4y2ZPA0,2052
|
11
12
|
lfss/cli/panel.py,sha256=iGdVmdWYjA_7a78ZzWEB_3ggIOBeUKTzg6F5zLaB25c,1401
|
12
13
|
lfss/cli/serve.py,sha256=bO3GT0kuylMGN-7bZWP4e71MlugGZ_lEMkYaYld_Ntg,985
|
13
14
|
lfss/cli/user.py,sha256=906MIQO1mr4XXzPpT8Kfri1G61bWlgjDzIglxypQ7z0,3251
|
14
|
-
lfss/client/__init__.py,sha256=
|
15
|
-
lfss/client/api.py,sha256=
|
15
|
+
lfss/client/__init__.py,sha256=mkSP7GoeNJgJV9uOuenQpqQymKukzmvtSsrAG57ZS58,1746
|
16
|
+
lfss/client/api.py,sha256=i8idyRzw_qDZ13CrX_9vXOGuaX9nE870blmqac6sntU,3680
|
16
17
|
lfss/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
18
|
lfss/src/config.py,sha256=mc8QoiWoPgiZ5JnSvKBH8jWbp5uLSyG3l5boDiQPXS0,325
|
18
|
-
lfss/src/database.py,sha256=
|
19
|
+
lfss/src/database.py,sha256=9HRz5ejAPj0_lIjdBoLMkBG45D2cGA2TchXFUngdTE0,28005
|
19
20
|
lfss/src/error.py,sha256=S5ui3tJ0uKX4EZnt2Db-KbDmJXlECI_OY95cNMkuegc,218
|
20
21
|
lfss/src/log.py,sha256=qNE04sHoZ2rMbQ5dR2zT7Xaz1KVAfYp5hKpWJX42S9g,5244
|
21
|
-
lfss/src/server.py,sha256=
|
22
|
+
lfss/src/server.py,sha256=lfLVQlOFIHINo3wIB3VZ_yiLsiPGoPCu5R5miDan0Q8,13546
|
22
23
|
lfss/src/stat.py,sha256=_4OaSvBm7D6mPgifwxnhGIEk1_q3SxfJr3lizaEoV_w,2081
|
23
24
|
lfss/src/utils.py,sha256=8VkrtpSmurbMiX7GyK-n7Grvzy3uwSJXHdONEsuLCDI,2272
|
24
|
-
lfss-0.4.
|
25
|
-
lfss-0.4.
|
26
|
-
lfss-0.4.
|
27
|
-
lfss-0.4.
|
25
|
+
lfss-0.4.1.dist-info/METADATA,sha256=H8hmvbd3bJYy6gLbrDV27eDXoZBI42FNz3RD4h1ixiw,1787
|
26
|
+
lfss-0.4.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
27
|
+
lfss-0.4.1.dist-info/entry_points.txt,sha256=_FXOyxodFtVBaxtBhdHcupqW_IolIIx-S6y6p7CkDFk,137
|
28
|
+
lfss-0.4.1.dist-info/RECORD,,
|
File without changes
|