lfss 0.11.4__py3-none-any.whl → 0.11.5__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.
- docs/Client.md +37 -0
- docs/changelog.md +16 -0
- lfss/api/__init__.py +3 -3
- lfss/api/connector.py +2 -2
- lfss/cli/cli.py +3 -3
- lfss/eng/database.py +3 -3
- lfss/eng/utils.py +4 -4
- lfss/svc/app_dav.py +6 -6
- lfss/svc/app_native.py +11 -11
- lfss/svc/common_impl.py +7 -7
- {lfss-0.11.4.dist-info → lfss-0.11.5.dist-info}/METADATA +1 -1
- {lfss-0.11.4.dist-info → lfss-0.11.5.dist-info}/RECORD +14 -13
- {lfss-0.11.4.dist-info → lfss-0.11.5.dist-info}/entry_points.txt +1 -0
- {lfss-0.11.4.dist-info → lfss-0.11.5.dist-info}/WHEEL +0 -0
docs/Client.md
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
# Client-side CLI tools
|
3
|
+
|
4
|
+
To install python CLI tools without dependencies (to avoid conflicts with your existing packages):
|
5
|
+
```sh
|
6
|
+
pip install requests
|
7
|
+
pip install lfss --no-deps
|
8
|
+
```
|
9
|
+
|
10
|
+
Then set the `LFSS_ENDPOINT`, `LFSS_TOKEN` environment variables,
|
11
|
+
then you can use the following commands:
|
12
|
+
```sh
|
13
|
+
# Query a path
|
14
|
+
lfss query remote/file[/or_dir/]
|
15
|
+
|
16
|
+
# List directories of a specified path
|
17
|
+
lfss list-dirs remote/dir/
|
18
|
+
|
19
|
+
# List files of a specified path,
|
20
|
+
# with pagination and sorting
|
21
|
+
lfss list-files --offset 0 --limit 100 --order access_time remote/dir/
|
22
|
+
|
23
|
+
# Upload a file
|
24
|
+
lfss upload local/file.txt remote/file.txt
|
25
|
+
|
26
|
+
# Upload a directory, note the ending slashes
|
27
|
+
lfss upload local/dir/ remote/dir/
|
28
|
+
|
29
|
+
# Download a file
|
30
|
+
lfss download remote/file.txt local/file.txt
|
31
|
+
|
32
|
+
# Download a directory, with verbose output and 8 concurrent jobs
|
33
|
+
# Overwrite existing files
|
34
|
+
lfss download -v -j 8 --conflict overwrite remote/dir/ local/dir/
|
35
|
+
```
|
36
|
+
|
37
|
+
More commands can be found using `lfss-cli --help`.
|
docs/changelog.md
CHANGED
@@ -1,5 +1,21 @@
|
|
1
1
|
## 0.11
|
2
2
|
|
3
|
+
### 0.11.5
|
4
|
+
- Script entry default to client CLI.
|
5
|
+
- Fix single file download name deduce with decoding.
|
6
|
+
- Fix code misspell (minor).
|
7
|
+
|
8
|
+
### 0.11.4
|
9
|
+
- Fix SQL query for LIKE clause to escape special characters in path.
|
10
|
+
|
11
|
+
### 0.11.3
|
12
|
+
- Add method to get multiple files, maybe with content, at once.
|
13
|
+
- Allow copy directory files that the user is not the owner of.
|
14
|
+
- Environment variables to set origin and disable file logging.
|
15
|
+
- Fix error handling for some endpoints.
|
16
|
+
- Redirect CLI error output to stderr.
|
17
|
+
- Increase thumb image size to 64x64.
|
18
|
+
|
3
19
|
### 0.11.2
|
4
20
|
- Improve frontend directory upload feedback.
|
5
21
|
- Set default large file threashold to 1M.
|
lfss/api/__init__.py
CHANGED
@@ -2,7 +2,7 @@ import os, time, pathlib
|
|
2
2
|
from threading import Lock
|
3
3
|
from .connector import Connector
|
4
4
|
from ..eng.datatype import FileRecord
|
5
|
-
from ..eng.utils import
|
5
|
+
from ..eng.utils import decode_uri_components
|
6
6
|
from ..eng.bounded_pool import BoundedThreadPoolExecutor
|
7
7
|
|
8
8
|
def upload_file(
|
@@ -105,7 +105,7 @@ def download_file(
|
|
105
105
|
assert not src_url.endswith('/'), "Source URL must not end with a slash."
|
106
106
|
while this_try <= n_retries:
|
107
107
|
if os.path.isdir(file_path):
|
108
|
-
fname = src_url.split('/')[-1]
|
108
|
+
fname = decode_uri_components(src_url.split('/')[-1])
|
109
109
|
file_path = os.path.join(file_path, fname)
|
110
110
|
|
111
111
|
if not overwrite and os.path.exists(file_path):
|
@@ -176,7 +176,7 @@ def download_directory(
|
|
176
176
|
with _counter_lock:
|
177
177
|
_counter += 1
|
178
178
|
this_count = _counter
|
179
|
-
dst_path = f"{directory}{os.path.relpath(
|
179
|
+
dst_path = f"{directory}{os.path.relpath(decode_uri_components(src_url), decode_uri_components(src_path))}"
|
180
180
|
if verbose:
|
181
181
|
print(f"[{this_count}/{file_count}] Downloading {src_url} to {dst_path}")
|
182
182
|
|
lfss/api/connector.py
CHANGED
@@ -11,7 +11,7 @@ from lfss.eng.datatype import (
|
|
11
11
|
FileReadPermission, FileRecord, DirectoryRecord, UserRecord, PathContents,
|
12
12
|
FileSortKey, DirSortKey
|
13
13
|
)
|
14
|
-
from lfss.eng.utils import
|
14
|
+
from lfss.eng.utils import ensure_uri_components
|
15
15
|
|
16
16
|
_default_endpoint = os.environ.get('LFSS_ENDPOINT', 'http://localhost:8000')
|
17
17
|
_default_token = os.environ.get('LFSS_TOKEN', '')
|
@@ -74,7 +74,7 @@ class Connector:
|
|
74
74
|
):
|
75
75
|
if path.startswith('/'):
|
76
76
|
path = path[1:]
|
77
|
-
path =
|
77
|
+
path = ensure_uri_components(path)
|
78
78
|
def f(**kwargs):
|
79
79
|
search_params_t = [
|
80
80
|
(k, str(v).lower() if isinstance(v, bool) else v)
|
lfss/cli/cli.py
CHANGED
@@ -2,7 +2,7 @@ from pathlib import Path
|
|
2
2
|
import argparse, typing, sys
|
3
3
|
from lfss.api import Connector, upload_directory, upload_file, download_file, download_directory
|
4
4
|
from lfss.eng.datatype import FileReadPermission, FileSortKey, DirSortKey
|
5
|
-
from lfss.eng.utils import
|
5
|
+
from lfss.eng.utils import decode_uri_components
|
6
6
|
from . import catch_request_error, line_sep
|
7
7
|
|
8
8
|
def parse_permission(s: str) -> FileReadPermission:
|
@@ -143,7 +143,7 @@ def main():
|
|
143
143
|
order_desc=args.reverse,
|
144
144
|
)
|
145
145
|
for i, f in enumerate(line_sep(res)):
|
146
|
-
f.url =
|
146
|
+
f.url = decode_uri_components(f.url)
|
147
147
|
print(f"[{i+1}] {f if args.long else f.url}")
|
148
148
|
|
149
149
|
if len(res) == args.limit:
|
@@ -160,7 +160,7 @@ def main():
|
|
160
160
|
order_desc=args.reverse,
|
161
161
|
)
|
162
162
|
for i, d in enumerate(line_sep(res)):
|
163
|
-
d.url =
|
163
|
+
d.url = decode_uri_components(d.url)
|
164
164
|
print(f"[{i+1}] {d if args.long else d.url}")
|
165
165
|
|
166
166
|
if len(res) == args.limit:
|
lfss/eng/database.py
CHANGED
@@ -21,7 +21,7 @@ from .datatype import (
|
|
21
21
|
)
|
22
22
|
from .config import LARGE_BLOB_DIR, CHUNK_SIZE, LARGE_FILE_BYTES, MAX_MEM_FILE_BYTES
|
23
23
|
from .log import get_logger
|
24
|
-
from .utils import
|
24
|
+
from .utils import decode_uri_components, hash_credential, concurrent_wrap, debounce_async, static_vars
|
25
25
|
from .error import *
|
26
26
|
|
27
27
|
class DBObjectBase(ABC):
|
@@ -1108,7 +1108,7 @@ class Database:
|
|
1108
1108
|
async def data_iter():
|
1109
1109
|
async for (r, blob) in self.iter_dir(top_url, None):
|
1110
1110
|
rel_path = r.url[len(top_url):]
|
1111
|
-
rel_path =
|
1111
|
+
rel_path = decode_uri_components(rel_path)
|
1112
1112
|
b_iter: AsyncIterable[bytes]
|
1113
1113
|
if isinstance(blob, bytes):
|
1114
1114
|
async def blob_iter(): yield blob
|
@@ -1138,7 +1138,7 @@ class Database:
|
|
1138
1138
|
with zipfile.ZipFile(buffer, 'w') as zf:
|
1139
1139
|
async for (r, blob) in self.iter_dir(top_url, None):
|
1140
1140
|
rel_path = r.url[len(top_url):]
|
1141
|
-
rel_path =
|
1141
|
+
rel_path = decode_uri_components(rel_path)
|
1142
1142
|
if r.external:
|
1143
1143
|
assert isinstance(blob, AsyncIterable)
|
1144
1144
|
zf.writestr(rel_path, b''.join([chunk async for chunk in blob]))
|
lfss/eng/utils.py
CHANGED
@@ -21,19 +21,19 @@ async def copy_file(source: str|pathlib.Path, destination: str|pathlib.Path):
|
|
21
21
|
def hash_credential(username: str, password: str):
|
22
22
|
return hashlib.sha256(f"{username}:{password}".encode()).hexdigest()
|
23
23
|
|
24
|
-
def
|
24
|
+
def encode_uri_components(path: str):
|
25
25
|
path_sp = path.split("/")
|
26
26
|
mapped = map(lambda x: urllib.parse.quote(x), path_sp)
|
27
27
|
return "/".join(mapped)
|
28
28
|
|
29
|
-
def
|
29
|
+
def decode_uri_components(path: str):
|
30
30
|
path_sp = path.split("/")
|
31
31
|
mapped = map(lambda x: urllib.parse.unquote(x), path_sp)
|
32
32
|
return "/".join(mapped)
|
33
33
|
|
34
|
-
def
|
34
|
+
def ensure_uri_components(path: str):
|
35
35
|
""" Ensure the path components are safe to use """
|
36
|
-
return
|
36
|
+
return encode_uri_components(decode_uri_components(path))
|
37
37
|
|
38
38
|
class TaskManager:
|
39
39
|
def __init__(self):
|
lfss/svc/app_dav.py
CHANGED
@@ -11,7 +11,7 @@ from ..eng.error import *
|
|
11
11
|
from ..eng.config import DATA_HOME, DEBUG_MODE
|
12
12
|
from ..eng.datatype import UserRecord, FileRecord, DirectoryRecord, AccessLevel
|
13
13
|
from ..eng.database import FileConn, UserConn, check_path_permission
|
14
|
-
from ..eng.utils import
|
14
|
+
from ..eng.utils import ensure_uri_components, decode_uri_components, format_last_modified, static_vars
|
15
15
|
from .app_base import *
|
16
16
|
from .common_impl import copy_impl
|
17
17
|
|
@@ -36,7 +36,7 @@ async def eval_path(path: str) -> tuple[ptype, str, Optional[FileRecord | Direct
|
|
36
36
|
and should end with / if it is a directory, otherwise it is a file
|
37
37
|
record is the FileRecord or DirectoryRecord object, it is None if the path does not exist
|
38
38
|
"""
|
39
|
-
path =
|
39
|
+
path = decode_uri_components(path)
|
40
40
|
if "://" in path:
|
41
41
|
if not path.startswith("http://") and not path.startswith("https://"):
|
42
42
|
raise HTTPException(status_code=400, detail="Bad Request, unsupported protocol")
|
@@ -47,7 +47,7 @@ async def eval_path(path: str) -> tuple[ptype, str, Optional[FileRecord | Direct
|
|
47
47
|
assert path.startswith(route_prefix), "Path should start with the route prefix, got: " + path
|
48
48
|
path = path[len(route_prefix):]
|
49
49
|
|
50
|
-
path =
|
50
|
+
path = ensure_uri_components(path)
|
51
51
|
if path.startswith("/"): path = path[1:]
|
52
52
|
|
53
53
|
# path now is url-safe and without leading slash
|
@@ -160,7 +160,7 @@ async def create_file_xml_element(frecord: FileRecord) -> ET.Element:
|
|
160
160
|
href.text = f"/{frecord.url}"
|
161
161
|
propstat = ET.SubElement(file_el, f"{{{DAV_NS}}}propstat")
|
162
162
|
prop = ET.SubElement(propstat, f"{{{DAV_NS}}}prop")
|
163
|
-
ET.SubElement(prop, f"{{{DAV_NS}}}displayname").text =
|
163
|
+
ET.SubElement(prop, f"{{{DAV_NS}}}displayname").text = decode_uri_components(frecord.url.split("/")[-1])
|
164
164
|
ET.SubElement(prop, f"{{{DAV_NS}}}resourcetype")
|
165
165
|
ET.SubElement(prop, f"{{{DAV_NS}}}getcontentlength").text = str(frecord.file_size)
|
166
166
|
ET.SubElement(prop, f"{{{DAV_NS}}}getlastmodified").text = format_last_modified(frecord.create_time)
|
@@ -178,7 +178,7 @@ async def create_dir_xml_element(drecord: DirectoryRecord) -> ET.Element:
|
|
178
178
|
href.text = f"/{drecord.url}"
|
179
179
|
propstat = ET.SubElement(dir_el, f"{{{DAV_NS}}}propstat")
|
180
180
|
prop = ET.SubElement(propstat, f"{{{DAV_NS}}}prop")
|
181
|
-
ET.SubElement(prop, f"{{{DAV_NS}}}displayname").text =
|
181
|
+
ET.SubElement(prop, f"{{{DAV_NS}}}displayname").text = decode_uri_components(drecord.url.split("/")[-2])
|
182
182
|
ET.SubElement(prop, f"{{{DAV_NS}}}resourcetype").append(ET.Element(f"{{{DAV_NS}}}collection"))
|
183
183
|
if drecord.size >= 0:
|
184
184
|
ET.SubElement(prop, f"{{{DAV_NS}}}getlastmodified").text = format_last_modified(drecord.create_time)
|
@@ -211,7 +211,7 @@ async def dav_options(request: Request, path: str):
|
|
211
211
|
@handle_exception
|
212
212
|
async def dav_propfind(request: Request, path: str, user: UserRecord = Depends(registered_user), body: Optional[ET.Element] = Depends(xml_request_body)):
|
213
213
|
if path.startswith("/"): path = path[1:]
|
214
|
-
path =
|
214
|
+
path = ensure_uri_components(path)
|
215
215
|
|
216
216
|
if body and DEBUG_MODE:
|
217
217
|
print("Propfind-body:", ET.tostring(body, encoding="utf-8", method="xml"))
|
lfss/svc/app_native.py
CHANGED
@@ -5,7 +5,7 @@ from fastapi import Depends, Request, Response, UploadFile, Query
|
|
5
5
|
from fastapi.responses import StreamingResponse, JSONResponse
|
6
6
|
from fastapi.exceptions import HTTPException
|
7
7
|
|
8
|
-
from ..eng.utils import
|
8
|
+
from ..eng.utils import ensure_uri_components
|
9
9
|
from ..eng.config import MAX_MEM_FILE_BYTES
|
10
10
|
from ..eng.connection_pool import unique_cursor
|
11
11
|
from ..eng.database import check_file_read_permission, check_path_permission, FileConn, delayed_log_access
|
@@ -83,7 +83,7 @@ async def delete_file(path: str, user: UserRecord = Depends(registered_user)):
|
|
83
83
|
@handle_exception
|
84
84
|
async def bundle_files(path: str, user: UserRecord = Depends(registered_user)):
|
85
85
|
logger.info(f"GET bundle({path}), user: {user.username}")
|
86
|
-
path =
|
86
|
+
path = ensure_uri_components(path)
|
87
87
|
if not path.endswith("/"):
|
88
88
|
raise HTTPException(status_code=400, detail="Path must end with /")
|
89
89
|
if path[0] == "/": # adapt to both /path and path
|
@@ -123,7 +123,7 @@ async def bundle_files(path: str, user: UserRecord = Depends(registered_user)):
|
|
123
123
|
@handle_exception
|
124
124
|
async def get_file_meta(path: str, user: UserRecord = Depends(registered_user)):
|
125
125
|
logger.info(f"GET meta({path}), user: {user.username}")
|
126
|
-
path =
|
126
|
+
path = ensure_uri_components(path)
|
127
127
|
is_file = not path.endswith("/")
|
128
128
|
async with unique_cursor() as cur:
|
129
129
|
fconn = FileConn(cur)
|
@@ -147,7 +147,7 @@ async def update_file_meta(
|
|
147
147
|
new_path: Optional[str] = None,
|
148
148
|
user: UserRecord = Depends(registered_user)
|
149
149
|
):
|
150
|
-
path =
|
150
|
+
path = ensure_uri_components(path)
|
151
151
|
if path.startswith("/"):
|
152
152
|
path = path[1:]
|
153
153
|
|
@@ -162,7 +162,7 @@ async def update_file_meta(
|
|
162
162
|
)
|
163
163
|
|
164
164
|
if new_path is not None:
|
165
|
-
new_path =
|
165
|
+
new_path = ensure_uri_components(new_path)
|
166
166
|
logger.info(f"Update path of {path} to {new_path}")
|
167
167
|
await db.move_file(path, new_path, user)
|
168
168
|
|
@@ -170,7 +170,7 @@ async def update_file_meta(
|
|
170
170
|
else:
|
171
171
|
assert perm is None, "Permission is not supported for directory"
|
172
172
|
if new_path is not None:
|
173
|
-
new_path =
|
173
|
+
new_path = ensure_uri_components(new_path)
|
174
174
|
logger.info(f"Update path of {path} to {new_path}")
|
175
175
|
# will raise duplicate path error if same name path exists in the new path
|
176
176
|
await db.move_dir(path, new_path, user)
|
@@ -194,7 +194,7 @@ async def validate_path_read_permission(path: str, user: UserRecord):
|
|
194
194
|
@handle_exception
|
195
195
|
async def count_files(path: str, flat: bool = False, user: UserRecord = Depends(registered_user)):
|
196
196
|
await validate_path_read_permission(path, user)
|
197
|
-
path =
|
197
|
+
path = ensure_uri_components(path)
|
198
198
|
async with unique_cursor() as conn:
|
199
199
|
fconn = FileConn(conn)
|
200
200
|
return { "count": await fconn.count_dir_files(url = path, flat = flat) }
|
@@ -206,7 +206,7 @@ async def list_files(
|
|
206
206
|
flat: bool = False, user: UserRecord = Depends(registered_user)
|
207
207
|
):
|
208
208
|
await validate_path_read_permission(path, user)
|
209
|
-
path =
|
209
|
+
path = ensure_uri_components(path)
|
210
210
|
async with unique_cursor() as conn:
|
211
211
|
fconn = FileConn(conn)
|
212
212
|
return await fconn.list_dir_files(
|
@@ -219,7 +219,7 @@ async def list_files(
|
|
219
219
|
@handle_exception
|
220
220
|
async def count_dirs(path: str, user: UserRecord = Depends(registered_user)):
|
221
221
|
await validate_path_read_permission(path, user)
|
222
|
-
path =
|
222
|
+
path = ensure_uri_components(path)
|
223
223
|
async with unique_cursor() as conn:
|
224
224
|
fconn = FileConn(conn)
|
225
225
|
return { "count": await fconn.count_path_dirs(url = path) }
|
@@ -231,7 +231,7 @@ async def list_dirs(
|
|
231
231
|
skim: bool = True, user: UserRecord = Depends(registered_user)
|
232
232
|
):
|
233
233
|
await validate_path_read_permission(path, user)
|
234
|
-
path =
|
234
|
+
path = ensure_uri_components(path)
|
235
235
|
async with unique_cursor() as conn:
|
236
236
|
fconn = FileConn(conn)
|
237
237
|
return await fconn.list_path_dirs(
|
@@ -263,7 +263,7 @@ async def get_multiple_files(
|
|
263
263
|
upath2path = OrderedDict[str, str]()
|
264
264
|
for p in path:
|
265
265
|
p_ = p if not p.startswith("/") else p[1:]
|
266
|
-
upath2path[
|
266
|
+
upath2path[ensure_uri_components(p_)] = p
|
267
267
|
upaths = list(upath2path.keys())
|
268
268
|
|
269
269
|
# get files
|
lfss/svc/common_impl.py
CHANGED
@@ -6,7 +6,7 @@ from ..eng.connection_pool import unique_cursor
|
|
6
6
|
from ..eng.datatype import UserRecord, FileRecord, PathContents, AccessLevel, FileReadPermission
|
7
7
|
from ..eng.database import FileConn, UserConn, delayed_log_access, check_file_read_permission, check_path_permission
|
8
8
|
from ..eng.thumb import get_thumb
|
9
|
-
from ..eng.utils import format_last_modified,
|
9
|
+
from ..eng.utils import format_last_modified, ensure_uri_components
|
10
10
|
from ..eng.config import CHUNK_SIZE, DEBUG_MODE
|
11
11
|
|
12
12
|
from .app_base import skip_request_log, db, logger
|
@@ -100,7 +100,7 @@ async def get_impl(
|
|
100
100
|
thumb: bool = False,
|
101
101
|
is_head = False,
|
102
102
|
):
|
103
|
-
path =
|
103
|
+
path = ensure_uri_components(path)
|
104
104
|
if path.startswith("/"): path = path[1:]
|
105
105
|
|
106
106
|
# handle directory query
|
@@ -194,7 +194,7 @@ async def put_file_impl(
|
|
194
194
|
conflict: Literal["overwrite", "skip", "abort"] = "overwrite",
|
195
195
|
permission: int = 0,
|
196
196
|
):
|
197
|
-
path =
|
197
|
+
path = ensure_uri_components(path)
|
198
198
|
assert not path.endswith("/"), "Path must not end with /"
|
199
199
|
|
200
200
|
access_level = await check_path_permission(path, user)
|
@@ -249,7 +249,7 @@ async def post_file_impl(
|
|
249
249
|
conflict: Literal["overwrite", "skip", "abort"] = "overwrite",
|
250
250
|
permission: int = 0,
|
251
251
|
):
|
252
|
-
path =
|
252
|
+
path = ensure_uri_components(path)
|
253
253
|
assert not path.endswith("/"), "Path must not end with /"
|
254
254
|
|
255
255
|
access_level = await check_path_permission(path, user)
|
@@ -288,7 +288,7 @@ async def post_file_impl(
|
|
288
288
|
}, content=json.dumps({"url": path}))
|
289
289
|
|
290
290
|
async def delete_impl(path: str, user: UserRecord):
|
291
|
-
path =
|
291
|
+
path = ensure_uri_components(path)
|
292
292
|
if await check_path_permission(path, user) < AccessLevel.WRITE:
|
293
293
|
raise HTTPException(status_code=403, detail="Permission denied")
|
294
294
|
|
@@ -307,8 +307,8 @@ async def delete_impl(path: str, user: UserRecord):
|
|
307
307
|
async def copy_impl(
|
308
308
|
op_user: UserRecord, src_path: str, dst_path: str,
|
309
309
|
):
|
310
|
-
src_path =
|
311
|
-
dst_path =
|
310
|
+
src_path = ensure_uri_components(src_path)
|
311
|
+
dst_path = ensure_uri_components(dst_path)
|
312
312
|
copy_type = "file" if not src_path[-1] == "/" else "directory"
|
313
313
|
if (src_path[-1] == "/") != (dst_path[-1] == "/"):
|
314
314
|
raise HTTPException(status_code=400, detail="Source and destination must be same type")
|
@@ -1,9 +1,10 @@
|
|
1
1
|
Readme.md,sha256=B-foESzFWoSI5MEd89AWUzKcVRrTwipM28TK8GN0o8c,1920
|
2
|
+
docs/Client.md,sha256=2GSKrcKkjYHxxA6Afa2vhHj7UFEQ-_EdmNRkZ06jPHU,975
|
2
3
|
docs/Enviroment_variables.md,sha256=CZ5DrrXSLU5RLBEVQ-gLMaOIuFthd7dEiTzO7ODrPRQ,788
|
3
4
|
docs/Known_issues.md,sha256=ZqETcWP8lzTOel9b2mxEgCnADFF8IxOrEtiVO1NoMAk,251
|
4
5
|
docs/Permission.md,sha256=thUJx7YRoU63Pb-eqo5l5450DrZN3QYZ36GCn8r66no,3152
|
5
6
|
docs/Webdav.md,sha256=-Ja-BTWSY1BEMAyZycvEMNnkNTPZ49gSPzmf3Lbib70,1547
|
6
|
-
docs/changelog.md,sha256=
|
7
|
+
docs/changelog.md,sha256=7Pa-yRqF2E0uU69e32bquAGmumgV3I-6irBiW-Jt5Nw,2120
|
7
8
|
frontend/api.js,sha256=F35jQjWF2LITkuO-wZJuEKyafLWFx_M4C2tEYJV8zak,22631
|
8
9
|
frontend/index.html,sha256=-k0bJ5FRqdl_H-O441D_H9E-iejgRCaL_z5UeYaS2qc,3384
|
9
10
|
frontend/info.css,sha256=Ny0N3GywQ3a9q1_Qph_QFEKB4fEnTe_2DJ1Y5OsLLmQ,595
|
@@ -18,11 +19,11 @@ frontend/styles.css,sha256=xcNLqI3KBsY5TLnku8UIP0Jfr7QLajr1_KNlZj9eheM,4935
|
|
18
19
|
frontend/thumb.css,sha256=rNsx766amYS2DajSQNabhpQ92gdTpNoQKmV69OKvtpI,295
|
19
20
|
frontend/thumb.js,sha256=46ViD2TlTTWy0fx6wjoAs_5CQ4ajYB90vVzM7UO2IHw,6182
|
20
21
|
frontend/utils.js,sha256=XP5hM_mROYaxK5dqn9qZVwv7GdQuiDzByilFskbrnxA,6068
|
21
|
-
lfss/api/__init__.py,sha256=
|
22
|
-
lfss/api/connector.py,sha256=
|
22
|
+
lfss/api/__init__.py,sha256=MdRsQSldbV4tZpAdzgr0sws8ru9GPvqUWVcsT6iVRFY,6844
|
23
|
+
lfss/api/connector.py,sha256=sz2mvGsHOREhev0FCd9p9RdyrJxRwtt-X63Kt4_E_dQ,13933
|
23
24
|
lfss/cli/__init__.py,sha256=lPwPmqpa7EXQ4zlU7E7LOe6X2kw_xATGdwoHphUEirA,827
|
24
25
|
lfss/cli/balance.py,sha256=fUbKKAUyaDn74f7mmxMfBL4Q4voyBLHu6Lg_g8GfMOQ,4121
|
25
|
-
lfss/cli/cli.py,sha256=
|
26
|
+
lfss/cli/cli.py,sha256=l4_hU7DLJ_QqTG69oKZSVXSA46PjfpxiUc_8ruTY6n0,8196
|
26
27
|
lfss/cli/log.py,sha256=TBlt8mhHMouv8ZBUMHYfGZiV6-0yPdajJQ5mkGHEojI,3016
|
27
28
|
lfss/cli/panel.py,sha256=Xq3I_n-ctveym-Gh9LaUpzHiLlvt3a_nuDiwUS-MGrg,1597
|
28
29
|
lfss/cli/serve.py,sha256=vTo6_BiD7Dn3VLvHsC5RKRBC3lMu45JVr_0SqpgHdj0,1086
|
@@ -32,21 +33,21 @@ lfss/eng/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
33
|
lfss/eng/bounded_pool.py,sha256=BI1dU-MBf82TMwJBYbjhEty7w1jIUKc5Bn9SnZ_-hoY,1288
|
33
34
|
lfss/eng/config.py,sha256=0dncHYn3tYw4pKBwXuP_huz0u7ud23fJ6SUmUPfLmeM,967
|
34
35
|
lfss/eng/connection_pool.py,sha256=1aq7nSgd7hB9YNV4PjD1RDRyl_moDw3ubBtSLyfgGBs,6320
|
35
|
-
lfss/eng/database.py,sha256=
|
36
|
+
lfss/eng/database.py,sha256=obJU3rD_AvwVzIUiSB1gqsC2k7_E6p7dw8PW7eYvi84,57241
|
36
37
|
lfss/eng/datatype.py,sha256=27UB7-l9SICy5lAvKjdzpTL_GohZjzstQcr9PtAq7nM,2709
|
37
38
|
lfss/eng/error.py,sha256=JGf5NV-f4rL6tNIDSAx5-l9MG8dEj7F2w_MuOjj1d1o,732
|
38
39
|
lfss/eng/log.py,sha256=yciFQ7Utz1AItNekS4YtdP6bM7i1krA6qSAU2wVQv24,7698
|
39
40
|
lfss/eng/thumb.py,sha256=AFyWEkkpuCKGWOB9bLlaDwPKzQ9JtCSSmHMhX2Gu3CI,3096
|
40
|
-
lfss/eng/utils.py,sha256=
|
41
|
+
lfss/eng/utils.py,sha256=SlgiC5S0V42n9JczFF04jl5tKC7R6gN-FkuqqNoF7co,6688
|
41
42
|
lfss/sql/init.sql,sha256=FBmVzkNjYUnWjEELRFzf7xb50GngmzmeDVffT1Uk8u8,1625
|
42
43
|
lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
|
43
44
|
lfss/svc/app.py,sha256=r1KUO3sPaaJWbkJF0bcVTD7arPKLs2jFlq52Ixicomo,220
|
44
45
|
lfss/svc/app_base.py,sha256=s5ieQVI4BT0CBYavRx0dyBqwts7PYnjyCovHNYPHul8,7014
|
45
|
-
lfss/svc/app_dav.py,sha256=
|
46
|
-
lfss/svc/app_native.py,sha256=
|
47
|
-
lfss/svc/common_impl.py,sha256=
|
46
|
+
lfss/svc/app_dav.py,sha256=DRMgByUAQ3gD6wL9xmikV5kvVmATN7QkxGSttFTYxFU,18245
|
47
|
+
lfss/svc/app_native.py,sha256=K2vlNs6b4DE2bPgRuHxic9oDp8V3D5M5wNjIibGjQyg,10612
|
48
|
+
lfss/svc/common_impl.py,sha256=wlTQm8zEGAfyw9FJvK9zqgLQw47MzNq6IT3OgwdUaCw,13736
|
48
49
|
lfss/svc/request_log.py,sha256=v8yXEIzPjaksu76Oh5vgdbUEUrw8Kt4etLAXBWSGie8,3207
|
49
|
-
lfss-0.11.
|
50
|
-
lfss-0.11.
|
51
|
-
lfss-0.11.
|
52
|
-
lfss-0.11.
|
50
|
+
lfss-0.11.5.dist-info/METADATA,sha256=h1KBhb0MjCtgrlaUqfDSqLH_6Jxy1q4KaETLEMauIyY,2732
|
51
|
+
lfss-0.11.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
52
|
+
lfss-0.11.5.dist-info/entry_points.txt,sha256=M4ubn9oLYcTc9wxlLKWwljnluStPWpCDlCGuTVU8twg,255
|
53
|
+
lfss-0.11.5.dist-info/RECORD,,
|
File without changes
|