lfss 0.7.5__py3-none-any.whl → 0.7.7__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 CHANGED
@@ -20,6 +20,7 @@
20
20
  * @property {number} file_size - the size of the file, in bytes
21
21
  * @property {string} create_time - the time the file was created
22
22
  * @property {string} access_time - the time the file was last accessed
23
+ * @property {string} mime_type - the mime type of the file
23
24
  *
24
25
  * Partially complete...
25
26
  * @typedef {Object} DirectoryRecord
frontend/index.html CHANGED
@@ -31,10 +31,9 @@
31
31
  <table id="files">
32
32
  <thead>
33
33
  <tr>
34
- <th>File</th>
34
+ <th>Name</th>
35
35
  <th>Size</th>
36
36
  <th>Accessed</th>
37
- <th>Created</th>
38
37
  <th>Read access</th>
39
38
  <th>Actions</th>
40
39
  </tr>
frontend/info.css ADDED
@@ -0,0 +1,36 @@
1
+
2
+ div.info-container {
3
+ display: flex;
4
+ flex-direction: column;
5
+ margin: 1rem;
6
+ gap: 1rem;
7
+ }
8
+ div.info-container-left {
9
+ margin: 0 auto;
10
+ padding: 10px;
11
+ border: 1px solid #ccc;
12
+ border-radius: 5px;
13
+ background-color: #f9f9f9;
14
+ }
15
+
16
+ td {
17
+ text-align: left;
18
+ text-wrap: nowrap;
19
+ width: fit-content;
20
+ }
21
+ td.info-table-key {
22
+ font-weight: bold;
23
+ padding-right: 1rem;
24
+ }
25
+
26
+ div.info-container-right {
27
+ display: flex;
28
+ flex-direction: column;
29
+ gap: 0.5rem;
30
+ width: 100%;
31
+ }
32
+ div.info-path-copy {
33
+ display: flex;
34
+ gap: 0.5rem;
35
+ align-items: center;
36
+ }
frontend/info.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * @import { UserRecord, FileRecord, DirectoryRecord } from "./api.js";
3
+ */
4
+ import Connector from "./api.js";
5
+ import { createFloatingWindow, showPopup } from "./popup.js";
6
+ import { cvtGMT2Local, formatSize, decodePathURI, copyToClipboard } from "./utils.js";
7
+
8
+ const ensureSlashEnd = (path) => {
9
+ return path.endsWith('/') ? path : path + '/';
10
+ }
11
+
12
+ /**
13
+ * @param {FileRecord} r
14
+ * @param {UserRecord} u
15
+ */
16
+ export function showInfoPanel(r, u){
17
+ const innerHTML = `
18
+ <div class="info-container">
19
+ <div class="info-container-left">
20
+ <table class="info-table">
21
+ <tr>
22
+ <td class="info-table-key">Filename</td>
23
+ <td class="info-table-value">${decodePathURI(r.url).split('/').pop()}</td>
24
+ </tr>
25
+ <tr>
26
+ <td class="info-table-key">File-Type</td>
27
+ <td class="info-table-value">${r.mime_type}</td>
28
+ </tr>
29
+ <tr>
30
+ <td class="info-table-key">Size</td>
31
+ <td class="info-table-value">${formatSize(r.file_size)}</td>
32
+ </tr>
33
+ <tr>
34
+ <td class="info-table-key">Owner</td>
35
+ <td class="info-table-value">${r.owner_id}</td>
36
+ </tr>
37
+ <tr>
38
+ <td class="info-table-key">Create-Time</td>
39
+ <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
+ </tr>
45
+ </table>
46
+ </div>
47
+ <div class="info-container-right">
48
+ <div class="info-path-copy">
49
+ <input type="text" value="${window.location.origin}/${r.url}" readonly>
50
+ <button class="copy-button" id='copy-btn-full-path'>📋</button>
51
+ </div>
52
+ <div class="info-path-copy">
53
+ <input type="text" value="${r.url}" readonly>
54
+ <button class="copy-button" id='copy-btn-rel-path'>📋</button>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ `
59
+ const [win, closeWin] = createFloatingWindow(innerHTML, {title: 'File Info'});
60
+ document.getElementById('copy-btn-full-path').onclick = () => {
61
+ copyToClipboard(window.location.origin + '/' + r.url);
62
+ showPopup('Path copied to clipboard', {timeout: 2000, level: 'success'});
63
+ }
64
+ document.getElementById('copy-btn-rel-path').onclick = () => {
65
+ copyToClipboard(r.url);
66
+ showPopup('Path copied to clipboard', {timeout: 2000, level: 'success'});
67
+ }
68
+ }
69
+
70
+ /**
71
+ * @param {DirectoryRecord} r
72
+ * @param {UserRecord} u
73
+ * @param {Connector} c
74
+ */
75
+ export function showDirInfoPanel(r, u, c){
76
+ let fmtPath = decodePathURI(r.url);
77
+ if (fmtPath.endsWith('/')) {
78
+ fmtPath = fmtPath.slice(0, -1);
79
+ }
80
+ const innerHTML = `
81
+ <div class="info-container">
82
+ <div class="info-container-left">
83
+ <table class="info-table">
84
+ <tr>
85
+ <td class="info-table-key">Pathname</td>
86
+ <td class="info-table-value" id="info-table-pathname">${fmtPath.split('/').pop()}</td>
87
+ </tr>
88
+ <tr>
89
+ <td class="info-table-key">Size</td>
90
+ <td class="info-table-value" id="info-table-pathsize">N/A</td>
91
+ </tr>
92
+ <tr>
93
+ <td class="info-table-key">Access-Time</td>
94
+ <td class="info-table-value" id="info-table-accesstime">1970-01-01 00:00:00</td>
95
+ </tr>
96
+ <tr>
97
+ <td class="info-table-key">Create-Time</td>
98
+ <td class="info-table-value" id="info-table-createtime">1970-01-01 00:00:00</td>
99
+ </td>
100
+ </table>
101
+ </div>
102
+ <div class="info-container-right">
103
+ <div class="info-path-copy">
104
+ <input type="text" value="${window.location.origin}/${ensureSlashEnd(r.url)}" readonly>
105
+ <button class="copy-button" id='copy-btn-full-path'>📋</button>
106
+ </div>
107
+ <div class="info-path-copy">
108
+ <input type="text" value="${ensureSlashEnd(r.url)}" readonly>
109
+ <button class="copy-button" id='copy-btn-rel-path'>📋</button>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ `
114
+ const [win, closeWin] = createFloatingWindow(innerHTML, {title: 'File Info'});
115
+ document.getElementById('copy-btn-full-path').onclick = () => {
116
+ copyToClipboard(window.location.origin + '/' + ensureSlashEnd(r.url));
117
+ showPopup('Path copied to clipboard', {timeout: 2000, level: 'success'});
118
+ }
119
+ document.getElementById('copy-btn-rel-path').onclick = () => {
120
+ copyToClipboard(ensureSlashEnd(r.url));
121
+ showPopup('Path copied to clipboard', {timeout: 2000, level: 'success'});
122
+ }
123
+
124
+ const sizeValTd = document.querySelector('.info-table-value#info-table-pathsize');
125
+ const createTimeValTd = document.querySelector('.info-table-value#info-table-createtime');
126
+ const accessTimeValTd = document.querySelector('.info-table-value#info-table-accesstime');
127
+ // console.log(sizeValTd, createTimeValTd, accessTimeValTd)
128
+ c.getMetadata(ensureSlashEnd(r.url)).then((meta) => {
129
+ if (!meta) {
130
+ console.error('Failed to fetch metadata for: ' + r.url);
131
+ return;
132
+ }
133
+ sizeValTd.textContent = formatSize(meta.size);
134
+ createTimeValTd.textContent = cvtGMT2Local(meta.create_time);
135
+ accessTimeValTd.textContent = cvtGMT2Local(meta.access_time);
136
+ });
137
+ }
frontend/popup.css CHANGED
@@ -39,6 +39,7 @@ div.popup-window{
39
39
  display: block;
40
40
  text-align: left;
41
41
  animation: popup-appear 0.5s ease;
42
+ z-index: 102;
42
43
  }
43
44
 
44
45
  @keyframes popup-appear{
frontend/scripts.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import Connector from './api.js';
2
2
  import { permMap } from './api.js';
3
3
  import { showFloatingWindowLineInput, showPopup } from './popup.js';
4
- import { formatSize, decodePathURI, ensurePathURI, copyToClipboard, getRandomString, cvtGMT2Local, debounce, encodePathURI } from './utils.js';
4
+ import { formatSize, decodePathURI, ensurePathURI, getRandomString, cvtGMT2Local, debounce, encodePathURI } from './utils.js';
5
+ import { showInfoPanel, showDirInfoPanel } from './info.js';
5
6
 
6
7
  const conn = new Connector();
7
8
  let userRecord = null;
@@ -235,7 +236,6 @@ function refreshFileList(){
235
236
  const tr = document.createElement('tr');
236
237
  const sizeTd = document.createElement('td');
237
238
  const accessTimeTd = document.createElement('td');
238
- const createTimeTd = document.createElement('td');
239
239
  {
240
240
  const nameTd = document.createElement('td');
241
241
  if (dir.url.endsWith('/')){
@@ -262,8 +262,6 @@ function refreshFileList(){
262
262
  tr.appendChild(sizeTd);
263
263
  accessTimeTd.textContent = cvtGMT2Local(dir.access_time);
264
264
  tr.appendChild(accessTimeTd);
265
- createTimeTd.textContent = cvtGMT2Local(dir.create_time);
266
- tr.appendChild(createTimeTd);
267
265
  }
268
266
  {
269
267
  const accessTd = document.createElement('td');
@@ -275,21 +273,14 @@ function refreshFileList(){
275
273
  const actContainer = document.createElement('div');
276
274
  actContainer.classList.add('action-container');
277
275
 
278
- const showMetaButton = document.createElement('a');
279
- showMetaButton.textContent = 'Reveal';
280
- showMetaButton.style.cursor = 'pointer';
281
- showMetaButton.addEventListener('click', () => {
282
- const dirUrlEncap = dirurl;
283
- conn.getMetadata(dirUrlEncap).then(
284
- (meta) => {
285
- sizeTd.textContent = formatSize(meta.size);
286
- accessTimeTd.textContent = cvtGMT2Local(meta.access_time);
287
- createTimeTd.textContent = cvtGMT2Local(meta.create_time);
288
- }
289
- );
290
- showPopup('Fetching metadata...', {level: 'info', timeout: 3000});
276
+ const infoButton = document.createElement('a');
277
+ infoButton.style.cursor = 'pointer';
278
+ infoButton.textContent = 'Details';
279
+ infoButton.style.width = '100%';
280
+ infoButton.addEventListener('click', () => {
281
+ showDirInfoPanel(dir, userRecord, conn);
291
282
  });
292
- actContainer.appendChild(showMetaButton);
283
+ actContainer.appendChild(infoButton);
293
284
 
294
285
  const moveButton = document.createElement('a');
295
286
  moveButton.textContent = 'Move';
@@ -369,13 +360,6 @@ function refreshFileList(){
369
360
  tr.appendChild(dateTd);
370
361
  }
371
362
 
372
- {
373
- const dateTd = document.createElement('td');
374
- const createTime = file.create_time;
375
- dateTd.textContent = cvtGMT2Local(createTime);
376
- tr.appendChild(dateTd);
377
- }
378
-
379
363
  {
380
364
  const accessTd = document.createElement('td');
381
365
  if (file.owner_id === userRecord.id || userRecord.is_admin){
@@ -416,14 +400,13 @@ function refreshFileList(){
416
400
  const actContainer = document.createElement('div');
417
401
  actContainer.classList.add('action-container');
418
402
 
419
- const copyButton = document.createElement('a');
420
- copyButton.style.cursor = 'pointer';
421
- copyButton.textContent = 'Share';
422
- copyButton.addEventListener('click', () => {
423
- copyToClipboard(conn.config.endpoint + '/' + file.url);
424
- showPopup('Link copied to clipboard', {level: "success"});
403
+ const infoButton = document.createElement('a');
404
+ infoButton.style.cursor = 'pointer';
405
+ infoButton.textContent = 'Details';
406
+ infoButton.addEventListener('click', () => {
407
+ showInfoPanel(file, userRecord);
425
408
  });
426
- actContainer.appendChild(copyButton);
409
+ actContainer.appendChild(infoButton);
427
410
 
428
411
  const viewButton = document.createElement('a');
429
412
  viewButton.textContent = 'View';
frontend/styles.css CHANGED
@@ -1,4 +1,5 @@
1
1
  @import "./popup.css";
2
+ @import "./info.css";
2
3
 
3
4
  body{
4
5
  font-family: Arial, sans-serif;
@@ -173,10 +174,10 @@ table#files tr:hover {
173
174
  background-color: #eaeaea;
174
175
  transition: all 0.2s;
175
176
  }
176
- table#files tr td:nth-child(2), table#files tr td:nth-child(5){
177
+ table#files tr td:nth-child(2), table#files tr td:nth-child(4){
177
178
  width: 1%;
178
179
  }
179
- table#files tr td:nth-child(3), table#files tr td:nth-child(4), table#files tr td:nth-child(6){
180
+ table#files tr td:nth-child(3), table#files tr td:nth-child(5){
180
181
  width: 12%;
181
182
  }
182
183
 
lfss/cli/cli.py CHANGED
@@ -1,7 +1,18 @@
1
1
  from lfss.client import Connector, upload_directory, upload_file, download_file, download_directory
2
- from lfss.src.datatype import FileReadPermission
3
2
  from pathlib import Path
4
3
  import argparse
4
+ from lfss.src.datatype import FileReadPermission
5
+
6
+ def parse_permission(s: str) -> FileReadPermission:
7
+ if s.lower() == "public":
8
+ return FileReadPermission.PUBLIC
9
+ if s.lower() == "protected":
10
+ return FileReadPermission.PROTECTED
11
+ if s.lower() == "private":
12
+ return FileReadPermission.PRIVATE
13
+ if s.lower() == "unset":
14
+ return FileReadPermission.UNSET
15
+ raise ValueError(f"Invalid permission {s}")
5
16
 
6
17
  def parse_arguments():
7
18
  parser = argparse.ArgumentParser(description="Command line interface, please set LFSS_ENDPOINT and LFSS_TOKEN environment variables.")
@@ -16,7 +27,7 @@ def parse_arguments():
16
27
  sp_upload.add_argument("-j", "--jobs", type=int, default=1, help="Number of concurrent uploads")
17
28
  sp_upload.add_argument("--interval", type=float, default=0, help="Interval between files, only works with directory upload")
18
29
  sp_upload.add_argument("--conflict", choices=["overwrite", "abort", "skip", "skip-ahead"], default="abort", help="Conflict resolution")
19
- sp_upload.add_argument("--permission", type=FileReadPermission, default=FileReadPermission.UNSET, help="File permission")
30
+ sp_upload.add_argument("--permission", type=parse_permission, default=FileReadPermission.UNSET, help="File permission, can be public, protected, private, or unset")
20
31
  sp_upload.add_argument("--retries", type=int, default=0, help="Number of retries")
21
32
 
22
33
  # download
lfss/cli/user.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import argparse, asyncio
2
2
  from contextlib import asynccontextmanager
3
+ from .cli import parse_permission, FileReadPermission
3
4
  from ..src.database import Database, FileReadPermission, transaction, UserConn
4
5
  from ..src.connection_pool import global_entrance
5
6
 
@@ -21,9 +22,9 @@ async def _main():
21
22
  sp_add = sp.add_parser('add')
22
23
  sp_add.add_argument('username', type=str)
23
24
  sp_add.add_argument('password', type=str)
24
- sp_add.add_argument('--admin', action='store_true')
25
- sp_add.add_argument('--permission', type=FileReadPermission, default=FileReadPermission.UNSET)
26
- sp_add.add_argument('--max-storage', type=parse_storage_size, default="1G")
25
+ sp_add.add_argument('--admin', action='store_true', help='Set user as admin')
26
+ sp_add.add_argument("--permission", type=parse_permission, default=FileReadPermission.UNSET, help="File permission, can be public, protected, private, or unset")
27
+ sp_add.add_argument('--max-storage', type=parse_storage_size, default="1G", help="Maximum storage size, e.g. 1G, 100M, 10K")
27
28
 
28
29
  sp_delete = sp.add_parser('delete')
29
30
  sp_delete.add_argument('username', type=str)
lfss/src/database.py CHANGED
@@ -13,7 +13,7 @@ from .connection_pool import execute_sql, unique_cursor, transaction
13
13
  from .datatype import UserRecord, FileReadPermission, FileRecord, DirectoryRecord, PathContents
14
14
  from .config import LARGE_BLOB_DIR
15
15
  from .log import get_logger
16
- from .utils import decode_uri_compnents, hash_credential
16
+ from .utils import decode_uri_compnents, hash_credential, concurrent_wrap
17
17
  from .error import *
18
18
 
19
19
  class DBObjectBase(ABC):
@@ -661,6 +661,7 @@ class Database:
661
661
  continue
662
662
  yield r, blob
663
663
 
664
+ @concurrent_wrap()
664
665
  async def zip_path(self, top_url: str, urls: Optional[list[str]]) -> io.BytesIO:
665
666
  if top_url.startswith('/'):
666
667
  top_url = top_url[1:]
lfss/src/utils.py CHANGED
@@ -3,6 +3,10 @@ import urllib.parse
3
3
  import asyncio
4
4
  import functools
5
5
  import hashlib
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from typing import TypeVar, Callable, Awaitable
8
+ from functools import wraps, partial
9
+ import os
6
10
 
7
11
  def hash_credential(username: str, password: str):
8
12
  return hashlib.sha256((username + password).encode()).hexdigest()
@@ -56,4 +60,33 @@ def now_stamp() -> float:
56
60
  return datetime.datetime.now().timestamp()
57
61
 
58
62
  def stamp_to_str(stamp: float) -> str:
59
- return datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d %H:%M:%S')
63
+ return datetime.datetime.fromtimestamp(stamp).strftime('%Y-%m-%d %H:%M:%S')
64
+
65
+
66
+ _FnReturnT = TypeVar('_FnReturnT')
67
+ _AsyncReturnT = Awaitable[_FnReturnT]
68
+ _g_executor = None
69
+ def get_global_executor():
70
+ global _g_executor
71
+ if _g_executor is None:
72
+ _g_executor = ThreadPoolExecutor(max_workers=4 if (cpu_count:=os.cpu_count()) and cpu_count > 4 else cpu_count)
73
+ return _g_executor
74
+ def async_wrap(executor=None):
75
+ if executor is None:
76
+ executor = get_global_executor()
77
+ def _async_wrap(func: Callable[..., _FnReturnT]) -> Callable[..., Awaitable[_FnReturnT]]:
78
+ @wraps(func)
79
+ async def run(*args, **kwargs):
80
+ loop = asyncio.get_event_loop()
81
+ pfunc = partial(func, *args, **kwargs)
82
+ return await loop.run_in_executor(executor, pfunc)
83
+ return run
84
+ return _async_wrap
85
+ def concurrent_wrap(executor=None):
86
+ def _concurrent_wrap(func: Callable[..., _AsyncReturnT]) -> Callable[..., _AsyncReturnT]:
87
+ @async_wrap(executor)
88
+ def sync_fn(*args, **kwargs):
89
+ loop = asyncio.new_event_loop()
90
+ return loop.run_until_complete(func(*args, **kwargs))
91
+ return sync_fn
92
+ return _concurrent_wrap
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: lfss
3
- Version: 0.7.5
3
+ Version: 0.7.7
4
4
  Summary: Lightweight file storage service
5
5
  Home-page: https://github.com/MenxLi/lfss
6
6
  Author: li, mengxun
@@ -1,18 +1,20 @@
1
1
  Readme.md,sha256=vsPotlwPAaHI5plh4aaszpi3rr7ZGDn7-wLdEYTWQ0k,1275
2
2
  docs/Known_issues.md,sha256=rfdG3j1OJF-59S9E06VPyn0nZKbW-ybPxkoZ7MEZWp8,81
3
3
  docs/Permission.md,sha256=X0VNfBKU52f93QYqcVyiBFJ3yURiSkhIo9S_5fdSgzM,2265
4
- frontend/api.js,sha256=lHqT7zGmsUZItE-FRR0LfTl_WYCcqlNssfa00XYo-EY,7865
5
- frontend/index.html,sha256=VPJDs2LG8ep9kjlsKzjWzpN9vc1VGgdvOUlNTZWyQoQ,2088
6
- frontend/popup.css,sha256=VzkjG1ZTLxhHMtTyobnlvqYmVsTmdbJJed2Pu1cc06c,1007
4
+ frontend/api.js,sha256=DxWmqO0AAOsWLXYtbgAzEnSmVyEJyzcxXSCH7H3STUk,7925
5
+ frontend/index.html,sha256=Mem8de9vwmZoe4x1DKqpu_aFgIBURqT3mIGdeOOTbIs,2051
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
9
  frontend/popup.js,sha256=3PgaGZmxSdV1E-D_MWgcR7aHWkcsHA1BNKSOkmP66tA,5191
8
- frontend/scripts.js,sha256=hQ8m3L7P-LplLqrPUWD6pBo4C_tCUl2XZKRNtkWBy8I,21155
9
- frontend/styles.css,sha256=wly8O-zF4EUgV12Tv1bATSfmJsLITv2u3_SiyXVaxv4,4096
10
+ frontend/scripts.js,sha256=zp_ENEnWxiwZRM_gE7HP0j_MXEI8yActO6cQfiTkaD8,20171
11
+ frontend/styles.css,sha256=l5_SECKR6vkEe4llydRIFxx0C2SOqcacmdMrtdLZRVM,4086
10
12
  frontend/utils.js,sha256=Ts4nlef8pkrEgpwX-uQwAhWvwxlIzex8ijDLNCa22ps,2372
11
13
  lfss/cli/balance.py,sha256=heOgwH6oNnfYsKJfA4VxWKdEXPstdVbbRXWxcDqLIS0,4176
12
- lfss/cli/cli.py,sha256=Yup3xIVEQPu10uM8dq1bvre1fK5ngweQHxXZsgQq4Hc,4187
14
+ lfss/cli/cli.py,sha256=LH1nx5wI1K2DZ3hvHz7oq5HcXVDoW2V6sr7q9gJ8gqo,4621
13
15
  lfss/cli/panel.py,sha256=iGdVmdWYjA_7a78ZzWEB_3ggIOBeUKTzg6F5zLaB25c,1401
14
16
  lfss/cli/serve.py,sha256=bO3GT0kuylMGN-7bZWP4e71MlugGZ_lEMkYaYld_Ntg,985
15
- lfss/cli/user.py,sha256=h-USWF6lB0Ztm9vwQznqsghKJ5INq5mBmaQeX2D5F-w,3490
17
+ lfss/cli/user.py,sha256=-ePmx_jhqfPQDl_9i_C_St9ujlCyyWSqNJUvc26v4_4,3686
16
18
  lfss/client/__init__.py,sha256=8uvcKs3PYQamDd_cjfN-fX9QUohEzJqeJlOYkBlzC3M,4556
17
19
  lfss/client/api.py,sha256=kSkB4wADTu012-1wl6v90OiZrw6aTQ42GU4jtV4KO0k,5764
18
20
  lfss/sql/init.sql,sha256=C-JtQAlaOjESI8uoF1Y_9dKukEVSw5Ll-7yA3gG-XHU,1210
@@ -20,14 +22,14 @@ lfss/sql/pragma.sql,sha256=uENx7xXjARmro-A3XAK8OM8v5AxDMdCCRj47f86UuXg,206
20
22
  lfss/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
23
  lfss/src/config.py,sha256=aTfjWORE9Mx7LSEjbfmHnULlrmIWEvEBSZ4fJKWZNjM,530
22
24
  lfss/src/connection_pool.py,sha256=teW_4DMiwlCN_bS7AhjkbY9cHZqUFlmHE_J2yPjHVsA,5125
23
- lfss/src/database.py,sha256=G9U_Iijp7euuGj3fcWdSGJPetMhn56X0vI8iWr6ZUr8,31904
25
+ lfss/src/database.py,sha256=-itbpGb7cQrywZLFk4aNcuy38Krsyemtyiz8GIt4i7M,31944
24
26
  lfss/src/datatype.py,sha256=BLS7vuuKnFZQg0nrKeP9SymqUhcN6HwPgejU0yBd_Ak,1622
25
27
  lfss/src/error.py,sha256=imbhwnbhnI3HLhkbfICROe3F0gleKrOk4XnqHJDOtuI,285
26
28
  lfss/src/log.py,sha256=xOnkuH-gB_jSVGqNnDVEW05iki6SCJ2xdEhjz5eEsMo,5136
27
29
  lfss/src/server.py,sha256=EA5fK4qc98tF8qoS9F6VaxIE65D5X8Ztkjqy8EUYIv8,16276
28
30
  lfss/src/stat.py,sha256=hTMtQyM_Ukmhc33Bb9FGCfBMIX02KrGHQg8nL7sC8sU,2082
29
- lfss/src/utils.py,sha256=miGsv7udupDtSpVSd66IvUt0_QMi3JXYCp_BjdPJY-M,2134
30
- lfss-0.7.5.dist-info/METADATA,sha256=cGOrCSM3pjCqYJdrFNrLfO2P08zx1cmwgfmAmTT4MJg,1967
31
- lfss-0.7.5.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
32
- lfss-0.7.5.dist-info/entry_points.txt,sha256=d_Ri3GXxUW-S0E6q953A8od0YMmUAnZGlJSKS46OiW8,172
33
- lfss-0.7.5.dist-info/RECORD,,
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,,
File without changes