Compare commits

..

No commits in common. "37167a41a6a5f643acde3485d2a6e287a6e31415" and "d42f0f76018cf97bd473910802b1194e1b983124" have entirely different histories.

8 changed files with 36 additions and 71 deletions

View File

@ -138,8 +138,7 @@ const download = async () => {
} }
} }
// Otherwise, zip and download // Otherwise, zip and download
const name = sel.keys.length === 1 ? sel.docs[sel.keys[0]].name : 'download' linkdl(`/zip/${Array.from(sel.keys).join('+')}/download.zip`)
linkdl(`/zip/${Array.from(sel.keys).join('+')}/${name}.zip`)
documentStore.selected.clear() documentStore.selected.clear()
} }
</script> </script>

View File

@ -55,16 +55,11 @@ async function sendChunk(file: File, start: number, end: number) {
} }
} }
async function uploadHandler(event: Event) { async function uploadFileChangeHandler(event: Event) {
const target = event.target as HTMLInputElement const target = event.target as HTMLInputElement
const chunkSize = 1 << 20 const chunkSize = 1 << 20
if (!target?.files?.length) { if (target && target.files && target.files.length > 0) {
documentStore.error = 'No files selected' const file = target.files[0]
return
}
for (const idx in target.files) {
const file = target.files[idx]
console.log('Uploading', file)
const numChunks = Math.ceil(file.size / chunkSize) const numChunks = Math.ceil(file.size / chunkSize)
const document = documentStore.pushUploadingDocuments(file.name) const document = documentStore.pushUploadingDocuments(file.name)
open('bottomRight') open('bottomRight')
@ -83,14 +78,14 @@ async function uploadHandler(event: Event) {
<template> <template>
<input <input
ref="fileUploadButton" ref="fileUploadButton"
@change="uploadHandler" @change="uploadFileChangeHandler"
class="upload-input" class="upload-input"
type="file" type="file"
multiple multiple
/> />
<input <input
ref="folderUploadButton" ref="folderUploadButton"
@change="uploadHandler" @change="uploadFileChangeHandler"
class="upload-input" class="upload-input"
type="file" type="file"
webkitdirectory webkitdirectory

View File

@ -1,22 +1,16 @@
import asyncio import asyncio
import datetime
import mimetypes import mimetypes
from collections import deque
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from importlib.resources import files from importlib.resources import files
from pathlib import Path
from stat import S_IFDIR, S_IFREG
from urllib.parse import unquote from urllib.parse import unquote
from wsgiref.handlers import format_date_time from wsgiref.handlers import format_date_time
import brotli import brotli
import sanic.helpers import sanic.helpers
from blake3 import blake3 from blake3 import blake3
from natsort import natsorted, ns
from sanic import Blueprint, Sanic, empty, raw from sanic import Blueprint, Sanic, empty, raw
from sanic.exceptions import Forbidden, NotFound from sanic.exceptions import Forbidden, NotFound
from sanic.log import logging from sanic.log import logging
from stream_zip import ZIP_AUTO, stream_zip
from cista import auth, config, session, watching from cista import auth, config, session, watching
from cista.api import bp from cista.api import bp
@ -174,6 +168,14 @@ async def wwwroot(req, path=""):
return raw(data, headers=headers) return raw(data, headers=headers)
import datetime
from collections import deque
from pathlib import Path
from stat import S_IFREG
from stream_zip import ZIP_AUTO, stream_zip
@app.get("/zip/<keys>/<zipfile:ext=zip>") @app.get("/zip/<keys>/<zipfile:ext=zip>")
async def zip_download(req, keys, zipfile, ext): async def zip_download(req, keys, zipfile, ext):
"""Download a zip archive of the given keys""" """Download a zip archive of the given keys"""
@ -188,13 +190,17 @@ async def zip_download(req, keys, zipfile, ext):
rel = None rel = None
if relpar or attr.key in wanted: if relpar or attr.key in wanted:
rel = [*relpar, name] if relpar else [name] rel = [*relpar, name] if relpar else [name]
wanted.discard(attr.key) wanted.remove(attr.key)
isdir = isinstance(attr, DirEntry) if isinstance(attr, DirEntry):
if isdir:
q.append((loc, rel, attr.dir)) q.append((loc, rel, attr.dir))
if rel: elif rel:
files.append( files.append(
("/".join(rel), Path(watching.rootpath.joinpath(*loc))) (
"/".join(rel),
Path(watching.rootpath.joinpath(*loc)),
attr.mtime,
attr.size,
)
) )
if not files: if not files:
@ -205,17 +211,14 @@ async def zip_download(req, keys, zipfile, ext):
if wanted: if wanted:
raise NotFound("Files not found", context={"missing": wanted}) raise NotFound("Files not found", context={"missing": wanted})
files = natsorted(files, key=lambda f: f[0], alg=ns.IGNORECASE) for rel, p, mtime, size in files:
if not p.is_file():
raise NotFound(f"File not found {rel}")
def local_files(files): def local_files(files):
for rel, p in files: for rel, p, mtime, size in files:
s = p.stat() modified = datetime.datetime.fromtimestamp(mtime, datetime.UTC)
size = s.st_size yield rel, modified, S_IFREG | 0o644, ZIP_AUTO(size), contents(p)
modified = datetime.datetime.fromtimestamp(s.st_mtime, datetime.UTC)
if p.is_dir():
yield rel, modified, S_IFDIR | 0o755, ZIP_AUTO(size), b""
else:
yield rel, modified, S_IFREG | 0o644, ZIP_AUTO(size), contents(p)
def contents(name): def contents(name):
with name.open("rb") as f: with name.open("rb") as f:

View File

@ -71,7 +71,7 @@ def verify(request, *, privileged=False):
raise Forbidden("Access Forbidden: Only for privileged users") raise Forbidden("Access Forbidden: Only for privileged users")
elif config.config.public or request.ctx.user: elif config.config.public or request.ctx.user:
return return
raise Unauthorized("Login required", "cookie") raise Unauthorized("Login required", "cookie", context={"redirect": "/login"})
bp = Blueprint("auth") bp = Blueprint("auth")

View File

@ -30,10 +30,7 @@ def run(*, dev=False):
reload_dir={confdir, wwwroot}, reload_dir={confdir, wwwroot},
access_log=True, access_log=True,
) # type: ignore ) # type: ignore
if dev: Sanic.serve()
Sanic.serve()
else:
Sanic.serve_single()
def check_cert(certdir, domain): def check_cert(certdir, domain):

View File

@ -10,7 +10,4 @@ def sanitize(filename: str) -> str:
filename = filename.replace("\\", "-") filename = filename.replace("\\", "-")
filename = sanitize_filepath(filename) filename = sanitize_filepath(filename)
filename = filename.strip("/") filename = filename.strip("/")
p = PurePosixPath(filename) return PurePosixPath(filename).as_posix()
if any(n.startswith(".") for n in p.parts):
raise ValueError("Filenames starting with dot are not allowed")
return p.as_posix()

View File

@ -1,10 +1,10 @@
import asyncio import asyncio
import shutil import shutil
import sys
import threading import threading
import time import time
from pathlib import Path, PurePosixPath from pathlib import Path, PurePosixPath
import inotify.adapters
import msgspec import msgspec
from sanic.log import logging from sanic.log import logging
@ -31,7 +31,6 @@ disk_usage = None
def watcher_thread(loop): def watcher_thread(loop):
global disk_usage, rootpath global disk_usage, rootpath
import inotify.adapters
while True: while True:
rootpath = config.config.path rootpath = config.config.path
@ -40,6 +39,7 @@ def watcher_thread(loop):
with tree_lock: with tree_lock:
# Initialize the tree from filesystem # Initialize the tree from filesystem
tree[""] = walk(rootpath) tree[""] = walk(rootpath)
print(" ".join(tree[""].dir.keys()))
msg = format_tree() msg = format_tree()
if msg != old: if msg != old:
asyncio.run_coroutine_threadsafe(broadcast(msg), loop) asyncio.run_coroutine_threadsafe(broadcast(msg), loop)
@ -74,28 +74,6 @@ def watcher_thread(loop):
i = None # Free the inotify object i = None # Free the inotify object
def watcher_thread_poll(loop):
global disk_usage, rootpath
while not quit:
rootpath = config.config.path
old = format_tree() if tree[""] else None
with tree_lock:
# Initialize the tree from filesystem
tree[""] = walk(rootpath)
msg = format_tree()
if msg != old:
asyncio.run_coroutine_threadsafe(broadcast(msg), loop)
# Disk usage update
du = shutil.disk_usage(rootpath)
if du != disk_usage:
disk_usage = du
asyncio.run_coroutine_threadsafe(broadcast(format_du()), loop)
time.sleep(1.0)
def format_du(): def format_du():
return msgspec.json.encode( return msgspec.json.encode(
{ {
@ -223,10 +201,7 @@ async def broadcast(msg):
async def start(app, loop): async def start(app, loop):
config.load_config() config.load_config()
app.ctx.watcher = threading.Thread( app.ctx.watcher = threading.Thread(target=watcher_thread, args=[loop])
target=watcher_thread if sys.platform == "linux" else watcher_thread_poll,
args=[loop],
)
app.ctx.watcher.start() app.ctx.watcher.start()

View File

@ -20,12 +20,11 @@ dependencies = [
"docopt", "docopt",
"inotify", "inotify",
"msgspec", "msgspec",
"natsort",
"pathvalidate", "pathvalidate",
"pyjwt", "pyjwt",
"sanic", "sanic",
"stream-zip",
"tomli_w", "tomli_w",
"stream-zip",
] ]
[project.urls] [project.urls]