cista 1.0.0__py3-none-any.whl → 1.1.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.
- cista/__main__.py +41 -16
- cista/_version.py +1 -1
- cista/api.py +5 -1
- cista/app.py +5 -3
- cista/config.py +44 -24
- cista/preview.py +33 -21
- cista/serve.py +0 -9
- cista/watching.py +2 -2
- {cista-1.0.0.dist-info → cista-1.1.1.dist-info}/METADATA +1 -1
- {cista-1.0.0.dist-info → cista-1.1.1.dist-info}/RECORD +12 -12
- {cista-1.0.0.dist-info → cista-1.1.1.dist-info}/WHEEL +0 -0
- {cista-1.0.0.dist-info → cista-1.1.1.dist-info}/entry_points.txt +0 -0
cista/__main__.py
CHANGED
|
@@ -10,8 +10,24 @@ from cista.util import pwgen
|
|
|
10
10
|
|
|
11
11
|
del app, server80.app # Only import needed, for Sanic multiprocessing
|
|
12
12
|
|
|
13
|
-
doc = f"""Cista {cista.__version__} - A file storage for the web.
|
|
14
13
|
|
|
14
|
+
def create_banner():
|
|
15
|
+
"""Create a framed banner with the Cista version."""
|
|
16
|
+
title = f"Cista {cista.__version__}"
|
|
17
|
+
subtitle = "A file storage for the web"
|
|
18
|
+
width = max(len(title), len(subtitle)) + 4
|
|
19
|
+
|
|
20
|
+
return f"""\
|
|
21
|
+
╭{"─" * width}╮
|
|
22
|
+
│{title:^{width}}│
|
|
23
|
+
│{subtitle:^{width}}│
|
|
24
|
+
╰{"─" * width}╯
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
banner = create_banner()
|
|
29
|
+
|
|
30
|
+
doc = """\
|
|
15
31
|
Usage:
|
|
16
32
|
cista [-c <confdir>] [-l <host>] [--import-droppy] [--dev] [<path>]
|
|
17
33
|
cista [-c <confdir>] --user <name> [--privileged] [--password]
|
|
@@ -35,6 +51,14 @@ User management:
|
|
|
35
51
|
--password Reset password
|
|
36
52
|
"""
|
|
37
53
|
|
|
54
|
+
first_time_help = """\
|
|
55
|
+
No config file found! Get started with:
|
|
56
|
+
cista --user yourname --privileged # If you want user accounts
|
|
57
|
+
cista -l :8000 /path/to/files # Run the server on localhost:8000
|
|
58
|
+
|
|
59
|
+
See cista --help for other options!
|
|
60
|
+
"""
|
|
61
|
+
|
|
38
62
|
|
|
39
63
|
def main():
|
|
40
64
|
# Dev mode doesn't catch exceptions
|
|
@@ -44,11 +68,19 @@ def main():
|
|
|
44
68
|
try:
|
|
45
69
|
return _main()
|
|
46
70
|
except Exception as e:
|
|
47
|
-
|
|
71
|
+
sys.stderr.write(f"Error: {e}\n")
|
|
48
72
|
return 1
|
|
49
73
|
|
|
50
74
|
|
|
51
75
|
def _main():
|
|
76
|
+
# The banner printing differs by mode, and needs to be done before docopt() printing its messages
|
|
77
|
+
if any(arg in sys.argv for arg in ("--help", "-h")):
|
|
78
|
+
sys.stdout.write(banner)
|
|
79
|
+
elif "--version" in sys.argv:
|
|
80
|
+
sys.stdout.write(f"cista {cista.__version__}\n")
|
|
81
|
+
return 0
|
|
82
|
+
else:
|
|
83
|
+
sys.stderr.write(banner)
|
|
52
84
|
args = docopt(doc)
|
|
53
85
|
if args["--user"]:
|
|
54
86
|
return _user(args)
|
|
@@ -62,18 +94,11 @@ def _main():
|
|
|
62
94
|
path = None
|
|
63
95
|
_confdir(args)
|
|
64
96
|
exists = config.conffile.exists()
|
|
65
|
-
print(config.conffile, exists)
|
|
66
97
|
import_droppy = args["--import-droppy"]
|
|
67
98
|
necessary_opts = exists or import_droppy or path
|
|
68
99
|
if not necessary_opts:
|
|
69
100
|
# Maybe run without arguments
|
|
70
|
-
|
|
71
|
-
print(
|
|
72
|
-
"No config file found! Get started with one of:\n"
|
|
73
|
-
" cista --user yourname --privileged\n"
|
|
74
|
-
" cista --import-droppy\n"
|
|
75
|
-
" cista -l :8000 /path/to/files\n"
|
|
76
|
-
)
|
|
101
|
+
sys.stderr.write(first_time_help)
|
|
77
102
|
return 1
|
|
78
103
|
settings = {}
|
|
79
104
|
if import_droppy:
|
|
@@ -94,7 +119,7 @@ def _main():
|
|
|
94
119
|
# We have no users, so make it public
|
|
95
120
|
settings["public"] = True
|
|
96
121
|
operation = config.update_config(settings)
|
|
97
|
-
|
|
122
|
+
sys.stderr.write(f"Config {operation}: {config.conffile}\n")
|
|
98
123
|
# Prepare to serve
|
|
99
124
|
unix = None
|
|
100
125
|
url, _ = serve.parse_listen(config.config.listen)
|
|
@@ -104,7 +129,7 @@ def _main():
|
|
|
104
129
|
dev = args["--dev"]
|
|
105
130
|
if dev:
|
|
106
131
|
extra += " (dev mode)"
|
|
107
|
-
|
|
132
|
+
sys.stderr.write(f"Serving {config.config.path} at {url}{extra}\n")
|
|
108
133
|
# Run the server
|
|
109
134
|
serve.run(dev=dev)
|
|
110
135
|
return 0
|
|
@@ -137,7 +162,7 @@ def _user(args):
|
|
|
137
162
|
"public": False,
|
|
138
163
|
}
|
|
139
164
|
)
|
|
140
|
-
|
|
165
|
+
sys.stderr.write(f"Config {operation}: {config.conffile}\n\n")
|
|
141
166
|
|
|
142
167
|
name = args["--user"]
|
|
143
168
|
if not name or not name.isidentifier():
|
|
@@ -155,12 +180,12 @@ def _user(args):
|
|
|
155
180
|
changes["password"] = pw = pwgen.generate()
|
|
156
181
|
info += f"\n Password: {pw}\n"
|
|
157
182
|
res = config.update_user(name, changes)
|
|
158
|
-
|
|
183
|
+
sys.stderr.write(f"{info}\n")
|
|
159
184
|
if res == "read":
|
|
160
|
-
|
|
185
|
+
sys.stderr.write(" No changes\n")
|
|
161
186
|
|
|
162
187
|
if operation == "created":
|
|
163
|
-
|
|
188
|
+
sys.stderr.write(
|
|
164
189
|
"Now you can run the server:\n cista # defaults set: -l :8000 ~/Downloads\n"
|
|
165
190
|
)
|
|
166
191
|
|
cista/_version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
# This file is automatically generated by hatch build.
|
|
2
|
-
__version__ = '1.
|
|
2
|
+
__version__ = '1.1.1'
|
cista/api.py
CHANGED
|
@@ -119,8 +119,12 @@ async def watch(req, ws):
|
|
|
119
119
|
# Send updates
|
|
120
120
|
while True:
|
|
121
121
|
await ws.send(await q.get())
|
|
122
|
+
except RuntimeError as e:
|
|
123
|
+
if str(e) == "cannot schedule new futures after shutdown":
|
|
124
|
+
return # Server shutting down, drop the WebSocket
|
|
125
|
+
raise
|
|
122
126
|
finally:
|
|
123
|
-
|
|
127
|
+
watching.pubsub.pop(uuid, None) # Remove whether it got added yet or not
|
|
124
128
|
|
|
125
129
|
|
|
126
130
|
def subscribe(uuid, ws):
|
cista/app.py
CHANGED
|
@@ -43,14 +43,16 @@ async def main_start(app, loop):
|
|
|
43
43
|
app.ctx.threadexec = ThreadPoolExecutor(
|
|
44
44
|
max_workers=workers, thread_name_prefix="cista-ioworker"
|
|
45
45
|
)
|
|
46
|
-
|
|
46
|
+
watching.start(app, loop)
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
# Sanic sometimes fails to execute after_server_stop, so we do it before instead (potentially interrupting handlers)
|
|
50
|
+
@app.before_server_stop
|
|
50
51
|
async def main_stop(app, loop):
|
|
51
52
|
quit.set()
|
|
52
|
-
|
|
53
|
+
watching.stop(app)
|
|
53
54
|
app.ctx.threadexec.shutdown()
|
|
55
|
+
logger.debug("Cista worker threads all finished")
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
@app.on_request
|
cista/config.py
CHANGED
|
@@ -7,9 +7,11 @@ from contextlib import suppress
|
|
|
7
7
|
from functools import wraps
|
|
8
8
|
from hashlib import sha256
|
|
9
9
|
from pathlib import Path, PurePath
|
|
10
|
-
from time import time
|
|
10
|
+
from time import sleep, time
|
|
11
|
+
from typing import Callable, Concatenate, Literal, ParamSpec
|
|
11
12
|
|
|
12
13
|
import msgspec
|
|
14
|
+
import msgspec.toml
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class Config(msgspec.Struct):
|
|
@@ -22,6 +24,13 @@ class Config(msgspec.Struct):
|
|
|
22
24
|
links: dict[str, Link] = {}
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
# Typing: arguments for config-modifying functions
|
|
28
|
+
P = ParamSpec("P")
|
|
29
|
+
ResultStr = Literal["modified", "created", "read"]
|
|
30
|
+
RawModifyFunc = Callable[Concatenate[Config, P], Config]
|
|
31
|
+
ModifyPublic = Callable[P, ResultStr]
|
|
32
|
+
|
|
33
|
+
|
|
25
34
|
class User(msgspec.Struct, omit_defaults=True):
|
|
26
35
|
privileged: bool = False
|
|
27
36
|
hash: str = ""
|
|
@@ -34,11 +43,13 @@ class Link(msgspec.Struct, omit_defaults=True):
|
|
|
34
43
|
expires: int = 0
|
|
35
44
|
|
|
36
45
|
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
# Global variables - initialized during application startup
|
|
47
|
+
config: Config
|
|
48
|
+
conffile: Path
|
|
39
49
|
|
|
40
50
|
|
|
41
|
-
def init_confdir():
|
|
51
|
+
def init_confdir() -> None:
|
|
52
|
+
global conffile
|
|
42
53
|
if p := os.environ.get("CISTA_HOME"):
|
|
43
54
|
home = Path(p)
|
|
44
55
|
else:
|
|
@@ -49,8 +60,6 @@ def init_confdir():
|
|
|
49
60
|
if not home.is_dir():
|
|
50
61
|
home.mkdir(parents=True, exist_ok=True)
|
|
51
62
|
home.chmod(0o700)
|
|
52
|
-
|
|
53
|
-
global conffile
|
|
54
63
|
conffile = home / "db.toml"
|
|
55
64
|
|
|
56
65
|
|
|
@@ -77,10 +86,10 @@ def dec_hook(typ, obj):
|
|
|
77
86
|
raise TypeError
|
|
78
87
|
|
|
79
88
|
|
|
80
|
-
def config_update(
|
|
89
|
+
def config_update(
|
|
90
|
+
modify: RawModifyFunc,
|
|
91
|
+
) -> ResultStr | Literal["collision"]:
|
|
81
92
|
global config
|
|
82
|
-
if conffile is None:
|
|
83
|
-
init_confdir()
|
|
84
93
|
tmpname = conffile.with_suffix(".tmp")
|
|
85
94
|
try:
|
|
86
95
|
f = tmpname.open("xb")
|
|
@@ -95,7 +104,7 @@ def config_update(modify):
|
|
|
95
104
|
c = msgspec.toml.decode(old, type=Config, dec_hook=dec_hook)
|
|
96
105
|
except FileNotFoundError:
|
|
97
106
|
old = b""
|
|
98
|
-
c =
|
|
107
|
+
c = Config(path=Path(), listen="", secret=secrets.token_hex(12))
|
|
99
108
|
c = modify(c)
|
|
100
109
|
new = msgspec.toml.encode(c, enc_hook=enc_hook)
|
|
101
110
|
if old == new:
|
|
@@ -118,17 +127,23 @@ def config_update(modify):
|
|
|
118
127
|
return "modified" if old else "created"
|
|
119
128
|
|
|
120
129
|
|
|
121
|
-
def modifies_config(
|
|
122
|
-
|
|
130
|
+
def modifies_config(
|
|
131
|
+
modify: Callable[Concatenate[Config, P], Config],
|
|
132
|
+
) -> Callable[P, ResultStr]:
|
|
133
|
+
"""Decorator for functions that modify the config file
|
|
134
|
+
|
|
135
|
+
The decorated function takes as first arg Config and returns it modified.
|
|
136
|
+
The wrapper handles atomic modification and returns a string indicating the result.
|
|
137
|
+
"""
|
|
123
138
|
|
|
124
139
|
@wraps(modify)
|
|
125
|
-
def wrapper(*args, **kwargs):
|
|
126
|
-
def m(c):
|
|
140
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> ResultStr:
|
|
141
|
+
def m(c: Config) -> Config:
|
|
127
142
|
return modify(c, *args, **kwargs)
|
|
128
143
|
|
|
129
144
|
# Retry modification in case of write collision
|
|
130
145
|
while (c := config_update(m)) == "collision":
|
|
131
|
-
|
|
146
|
+
sleep(0.01)
|
|
132
147
|
return c
|
|
133
148
|
|
|
134
149
|
return wrapper
|
|
@@ -136,8 +151,7 @@ def modifies_config(modify):
|
|
|
136
151
|
|
|
137
152
|
def load_config():
|
|
138
153
|
global config
|
|
139
|
-
|
|
140
|
-
init_confdir()
|
|
154
|
+
init_confdir()
|
|
141
155
|
config = msgspec.toml.decode(conffile.read_bytes(), type=Config, dec_hook=dec_hook)
|
|
142
156
|
|
|
143
157
|
|
|
@@ -145,7 +159,7 @@ def load_config():
|
|
|
145
159
|
def update_config(conf: Config, changes: dict) -> Config:
|
|
146
160
|
"""Create/update the config with new values, respecting changes done by others."""
|
|
147
161
|
# Encode into dict, update values with new, convert to Config
|
|
148
|
-
settings =
|
|
162
|
+
settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
|
|
149
163
|
settings.update(changes)
|
|
150
164
|
return msgspec.convert(settings, Config, dec_hook=dec_hook)
|
|
151
165
|
|
|
@@ -155,8 +169,13 @@ def update_user(conf: Config, name: str, changes: dict) -> Config:
|
|
|
155
169
|
"""Create/update a user with new values, respecting changes done by others."""
|
|
156
170
|
# Encode into dict, update values with new, convert to Config
|
|
157
171
|
try:
|
|
158
|
-
|
|
159
|
-
|
|
172
|
+
# Copy user by converting to dict and back
|
|
173
|
+
u = msgspec.convert(
|
|
174
|
+
msgspec.to_builtins(conf.users[name], enc_hook=enc_hook),
|
|
175
|
+
User,
|
|
176
|
+
dec_hook=dec_hook,
|
|
177
|
+
)
|
|
178
|
+
except KeyError:
|
|
160
179
|
u = User()
|
|
161
180
|
if "password" in changes:
|
|
162
181
|
from . import auth
|
|
@@ -165,7 +184,7 @@ def update_user(conf: Config, name: str, changes: dict) -> Config:
|
|
|
165
184
|
del changes["password"]
|
|
166
185
|
udict = msgspec.to_builtins(u, enc_hook=enc_hook)
|
|
167
186
|
udict.update(changes)
|
|
168
|
-
settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
|
|
187
|
+
settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
|
|
169
188
|
settings["users"][name] = msgspec.convert(udict, User, dec_hook=dec_hook)
|
|
170
189
|
return msgspec.convert(settings, Config, dec_hook=dec_hook)
|
|
171
190
|
|
|
@@ -173,6 +192,7 @@ def update_user(conf: Config, name: str, changes: dict) -> Config:
|
|
|
173
192
|
@modifies_config
|
|
174
193
|
def del_user(conf: Config, name: str) -> Config:
|
|
175
194
|
"""Delete named user account."""
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
195
|
+
# Create a copy by converting to dict and back
|
|
196
|
+
settings = msgspec.to_builtins(conf, enc_hook=enc_hook)
|
|
197
|
+
settings["users"].pop(name)
|
|
198
|
+
return msgspec.convert(settings, Config, dec_hook=dec_hook)
|
cista/preview.py
CHANGED
|
@@ -24,6 +24,17 @@ pillow_heif.register_heif_opener()
|
|
|
24
24
|
|
|
25
25
|
bp = Blueprint("preview", url_prefix="/preview")
|
|
26
26
|
|
|
27
|
+
# Map EXIF Orientation value to a corresponding PIL transpose
|
|
28
|
+
EXIF_ORI = {
|
|
29
|
+
2: Image.Transpose.FLIP_LEFT_RIGHT,
|
|
30
|
+
3: Image.Transpose.ROTATE_180,
|
|
31
|
+
4: Image.Transpose.FLIP_TOP_BOTTOM,
|
|
32
|
+
5: Image.Transpose.TRANSPOSE,
|
|
33
|
+
6: Image.Transpose.ROTATE_270,
|
|
34
|
+
7: Image.Transpose.TRANSVERSE,
|
|
35
|
+
8: Image.Transpose.ROTATE_90,
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
|
|
28
39
|
@bp.get("/<path:path>")
|
|
29
40
|
async def preview(req, path):
|
|
@@ -69,34 +80,35 @@ def dispatch(path, quality, maxsize, maxzoom):
|
|
|
69
80
|
|
|
70
81
|
|
|
71
82
|
def process_image(path, *, maxsize, quality):
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
t_load = perf_counter()
|
|
84
|
+
with Image.open(path) as img:
|
|
85
|
+
# Force decode to include I/O in load timing
|
|
86
|
+
img.load()
|
|
87
|
+
t_proc = perf_counter()
|
|
88
|
+
# Resize
|
|
89
|
+
w, h = img.size
|
|
90
|
+
img.thumbnail((min(w, maxsize), min(h, maxsize)))
|
|
91
|
+
# Transpose pixels according to EXIF Orientation
|
|
92
|
+
orientation = img.getexif().get(274, 1)
|
|
93
|
+
if orientation in EXIF_ORI:
|
|
94
|
+
img = img.transpose(EXIF_ORI[orientation])
|
|
95
|
+
# Save as AVIF
|
|
96
|
+
imgdata = io.BytesIO()
|
|
97
|
+
t_save = perf_counter()
|
|
98
|
+
img.save(imgdata, format="avif", quality=quality, speed=10, max_threads=1)
|
|
99
|
+
|
|
100
|
+
t_end = perf_counter()
|
|
88
101
|
ret = imgdata.getvalue()
|
|
89
102
|
|
|
90
|
-
load_ms = (
|
|
91
|
-
proc_ms = (
|
|
92
|
-
save_ms = (
|
|
103
|
+
load_ms = (t_proc - t_load) * 1000
|
|
104
|
+
proc_ms = (t_save - t_proc) * 1000
|
|
105
|
+
save_ms = (t_end - t_save) * 1000
|
|
93
106
|
logger.debug(
|
|
94
|
-
"Preview image %s: load=%.1fms process=%.1fms save=%.1fms
|
|
107
|
+
"Preview image %s: load=%.1fms process=%.1fms save=%.1fms",
|
|
95
108
|
path.name,
|
|
96
109
|
load_ms,
|
|
97
110
|
proc_ms,
|
|
98
111
|
save_ms,
|
|
99
|
-
len(ret) / 1024,
|
|
100
112
|
)
|
|
101
113
|
|
|
102
114
|
return ret
|
cista/serve.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import re
|
|
3
|
-
import signal
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
from sanic import Sanic
|
|
@@ -12,14 +11,6 @@ def run(*, dev=False):
|
|
|
12
11
|
"""Run Sanic main process that spawns worker processes to serve HTTP requests."""
|
|
13
12
|
from .app import app
|
|
14
13
|
|
|
15
|
-
# Set up immediate exit on Ctrl+C for faster termination
|
|
16
|
-
def signal_handler(signum, frame):
|
|
17
|
-
print("\nReceived interrupt signal, exiting immediately...")
|
|
18
|
-
os._exit(0)
|
|
19
|
-
|
|
20
|
-
signal.signal(signal.SIGINT, signal_handler)
|
|
21
|
-
signal.signal(signal.SIGTERM, signal_handler)
|
|
22
|
-
|
|
23
14
|
url, opts = parse_listen(config.config.listen)
|
|
24
15
|
# Silence Sanic's warning about running in production rather than debug
|
|
25
16
|
os.environ["SANIC_IGNORE_PRODUCTION_WARNING"] = "1"
|
cista/watching.py
CHANGED
|
@@ -440,7 +440,7 @@ def watcher_poll(loop):
|
|
|
440
440
|
quit.wait(0.1 + 8 * dur)
|
|
441
441
|
|
|
442
442
|
|
|
443
|
-
|
|
443
|
+
def start(app, loop):
|
|
444
444
|
global rootpath
|
|
445
445
|
config.load_config()
|
|
446
446
|
rootpath = config.config.path
|
|
@@ -454,6 +454,6 @@ async def start(app, loop):
|
|
|
454
454
|
app.ctx.watcher.start()
|
|
455
455
|
|
|
456
456
|
|
|
457
|
-
|
|
457
|
+
def stop(app):
|
|
458
458
|
quit.set()
|
|
459
459
|
app.ctx.watcher.join()
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
cista/__init__.py,sha256=4IT57gQUTOV6q9zFGj9i6E9LmiRz3dIfHK3q4qCeOIc,66
|
|
2
|
-
cista/__main__.py,sha256=
|
|
3
|
-
cista/_version.py,sha256=
|
|
4
|
-
cista/api.py,sha256=
|
|
5
|
-
cista/app.py,sha256=
|
|
2
|
+
cista/__main__.py,sha256=1SgOKr-l9D4y2kGzccbNUU5uKUPXH4rQs_pmhTam4S8,6021
|
|
3
|
+
cista/_version.py,sha256=_R6weBNXXHULw_7cEiZ75FD1qpLME2NDFoL91tPT9P4,77
|
|
4
|
+
cista/api.py,sha256=bcryK5_HU12uDDFVqZGt-2jRjGlSuCsHnXRCbFL87mU,4205
|
|
5
|
+
cista/app.py,sha256=W5Lvr4qkeXNmkY7UgLCVRvqi8us7evP5VOClKP9d-pc,9534
|
|
6
6
|
cista/auth.py,sha256=FpJbeGwK9MYz4tm7J8yA6LuOCNtPAwT-GPPJh_uoMs0,5806
|
|
7
|
-
cista/config.py,sha256=
|
|
7
|
+
cista/config.py,sha256=iN0ZLlkOipyomVKwBfw58XmLT4wPAZO1-3NSCghtkZQ,5758
|
|
8
8
|
cista/droppy.py,sha256=04a7TyZjmgmaLifGyH3WMAbwCAGBGPEl9oFYc423dQA,1304
|
|
9
9
|
cista/fileio.py,sha256=A8OBsv_jN4IgHaZCr8s9t6uqASu3r6qxPCILPKVxXQ0,2939
|
|
10
|
-
cista/preview.py,sha256=
|
|
10
|
+
cista/preview.py,sha256=7lSaA-GVAgamhvnDl0EScx5hKLFLT4aNAk0wj-ryInA,8168
|
|
11
11
|
cista/protocol.py,sha256=GBLUwg7kjvCG8tUyIXmIN3rMkA4GbQNm9yAB8aatO_c,3226
|
|
12
|
-
cista/serve.py,sha256=
|
|
12
|
+
cista/serve.py,sha256=PP55WfKlsuuBYnLtK-Ne5p85w4gVGcvYp9BoKP9UHUg,1931
|
|
13
13
|
cista/server80.py,sha256=opjtARyheVXQfJ5wBOMyzGDAtpMHnOpKNoZ1EsIytnI,780
|
|
14
14
|
cista/session.py,sha256=zc-A0iPaepAOLIoO5PTAHy9qn2NZb2zQir3Rm4ZmaK0,1090
|
|
15
|
-
cista/watching.py,sha256=
|
|
15
|
+
cista/watching.py,sha256=MXxzylBkm6O64BtU6sifFBKCJP_ahvdmj0jPwIBQA8U,14316
|
|
16
16
|
cista/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
17
|
cista/util/apphelpers.py,sha256=0mrOeKMZdBfYRWe5iWXA_4p589E6Y9OTuD0V4nq4qvU,2406
|
|
18
18
|
cista/util/asynclink.py,sha256=3jl57o6f2AQGYDKV4hJFHSJUuJaKy3-dBctxNut8_9E,3122
|
|
@@ -80,7 +80,7 @@ cista/wwwroot/assets/window-d99968ac.js,sha256=To6zWdJ2Hma9V4qQCz1__mBE-WXEmUitA
|
|
|
80
80
|
cista/wwwroot/assets/wordwrap-cdcd3f93.js,sha256=_f2_PFqgpwoDBQfBgS_umwVi-qdPRmE94Nb1FvdAYDQ,426
|
|
81
81
|
cista/wwwroot/assets/zoomin-7d13bb9b.js,sha256=lK8ysnky14ErvQaTIVaDoJWGkC0iiwzELVNYT2Tgqq4,647
|
|
82
82
|
cista/wwwroot/assets/zoomout-daf52018.js,sha256=mbHHOgPcOEANSIFasLmKydrR1pQWb8X6qDnoVcOJoJ8,613
|
|
83
|
-
cista-1.
|
|
84
|
-
cista-1.
|
|
85
|
-
cista-1.
|
|
86
|
-
cista-1.
|
|
83
|
+
cista-1.1.1.dist-info/METADATA,sha256=FYQ84OZPtSmYYd02yrkWzp4IX2mGeoBsCV-wxK5W6Ck,6913
|
|
84
|
+
cista-1.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
85
|
+
cista-1.1.1.dist-info/entry_points.txt,sha256=_XK4tjdkpyX8yUrVMHboZ5EJ9Q1bNqhuIkKoA06WiKw,46
|
|
86
|
+
cista-1.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|