mirror of
https://github.com/9001/copyparty.git
synced 2025-10-24 16:43:55 +00:00
Compare commits
80 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04f1b2cf3a | ||
|
|
c06d928bb5 | ||
|
|
ab09927e7b | ||
|
|
779437db67 | ||
|
|
28cbdb652e | ||
|
|
2b2415a7d8 | ||
|
|
746a8208aa | ||
|
|
a2a041a98a | ||
|
|
10b436e449 | ||
|
|
4d62b34786 | ||
|
|
0546210687 | ||
|
|
f8c11faada | ||
|
|
16d6e9be1f | ||
|
|
aff8185f2e | ||
|
|
217d15fe81 | ||
|
|
171e93c201 | ||
|
|
acc1d2e9e3 | ||
|
|
49c2f37154 | ||
|
|
69e54497aa | ||
|
|
9aa1885669 | ||
|
|
4418508513 | ||
|
|
e897df3b34 | ||
|
|
8cd97ab0e7 | ||
|
|
bf4949353d | ||
|
|
98a944f7cc | ||
|
|
7c10f81c92 | ||
|
|
126ecc55c3 | ||
|
|
1034a51bd2 | ||
|
|
a2657887cc | ||
|
|
c14b17bfaf | ||
|
|
59ebc795e7 | ||
|
|
8e128d917e | ||
|
|
ea762b05e0 | ||
|
|
db374b19f1 | ||
|
|
ab3839ef36 | ||
|
|
9886c442f2 | ||
|
|
c8d1926d52 | ||
|
|
a6bd699e52 | ||
|
|
12143f2702 | ||
|
|
480705dee9 | ||
|
|
781d5094f4 | ||
|
|
5615cb94cd | ||
|
|
302302a2ac | ||
|
|
9761b4e3e9 | ||
|
|
0cf6924dca | ||
|
|
5fd81e9f90 | ||
|
|
52bf6f892b | ||
|
|
f3cce232a4 | ||
|
|
53d3c8b28e | ||
|
|
83fec3cca7 | ||
|
|
3cefc99b7d | ||
|
|
3a38dcbc05 | ||
|
|
7ff08bce57 | ||
|
|
fd490af434 | ||
|
|
1195b8f17e | ||
|
|
28dce13776 | ||
|
|
431f20177a | ||
|
|
87aff54d9d | ||
|
|
f50462de82 | ||
|
|
9bda8c7eb6 | ||
|
|
e83c63d239 | ||
|
|
b38533b0cc | ||
|
|
5ccca3fbd5 | ||
|
|
9e850fc3ab | ||
|
|
ffbfcd7e00 | ||
|
|
5ea7590748 | ||
|
|
290c3bc2bb | ||
|
|
b12131e91c | ||
|
|
3b354447b0 | ||
|
|
d09ec6feaa | ||
|
|
21405c3fda | ||
|
|
13e5c96cab | ||
|
|
426687b75e | ||
|
|
c8f59fb978 | ||
|
|
871dde79a9 | ||
|
|
e14d81bc6f | ||
|
|
514d046d1f | ||
|
|
4ed9528d36 | ||
|
|
625560e642 | ||
|
|
73ebd917d1 |
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@@ -14,6 +14,8 @@
|
||||
"-emp",
|
||||
"-e2dsa",
|
||||
"-e2ts",
|
||||
"-mtp",
|
||||
".bpm=f,bin/mtag/audio-bpm.py",
|
||||
"-a",
|
||||
"ed:wark",
|
||||
"-v",
|
||||
|
||||
35
.vscode/launch.py
vendored
Normal file
35
.vscode/launch.py
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
# takes arguments from launch.json
|
||||
# is used by no_dbg in tasks.json
|
||||
# launches 10x faster than mspython debugpy
|
||||
# and is stoppable with ^C
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shlex
|
||||
|
||||
sys.path.insert(0, os.getcwd())
|
||||
|
||||
import jstyleson
|
||||
from copyparty.__main__ import main as copyparty
|
||||
|
||||
with open(".vscode/launch.json", "r") as f:
|
||||
tj = f.read()
|
||||
|
||||
oj = jstyleson.loads(tj)
|
||||
argv = oj["configurations"][0]["args"]
|
||||
|
||||
try:
|
||||
sargv = " ".join([shlex.quote(x) for x in argv])
|
||||
print(sys.executable + " -m copyparty " + sargv + "\n")
|
||||
except:
|
||||
pass
|
||||
|
||||
argv = [os.path.expanduser(x) if x.startswith("~") else x for x in argv]
|
||||
try:
|
||||
copyparty(["a"] + argv)
|
||||
except SystemExit as ex:
|
||||
if ex.code:
|
||||
raise
|
||||
|
||||
print("\n\033[32mokke\033[0m")
|
||||
sys.exit(1)
|
||||
4
.vscode/tasks.json
vendored
4
.vscode/tasks.json
vendored
@@ -9,9 +9,7 @@
|
||||
{
|
||||
"label": "no_dbg",
|
||||
"type": "shell",
|
||||
"command": "${config:python.pythonPath} -m copyparty -ed -emp -e2dsa -e2ts -a ed:wark -v srv::r:aed:cnodupe -v dist:dist:r ;exit 1"
|
||||
// -v ~/Music/mt:mt:r:cmtp=.bpm=~/dev/copyparty/bin/mtag/audio-bpm.py:cmtp=key=~/dev/copyparty/bin/mtag/audio-key.py:ce2tsr
|
||||
// -v ~/Music/mt:mt:r:cmtp=.bpm=~/dev/copyparty/bin/mtag/audio-bpm.py:ce2tsr
|
||||
"command": "${config:python.pythonPath} .vscode/launch.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
75
README.md
75
README.md
@@ -21,11 +21,13 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
||||
* [status](#status)
|
||||
* [bugs](#bugs)
|
||||
* [usage](#usage)
|
||||
* [zip downloads](#zip-downloads)
|
||||
* [searching](#searching)
|
||||
* [search configuration](#search-configuration)
|
||||
* [metadata from audio files](#metadata-from-audio-files)
|
||||
* [file parser plugins](#file-parser-plugins)
|
||||
* [complete examples](#complete-examples)
|
||||
* [browser support](#browser-support)
|
||||
* [client examples](#client-examples)
|
||||
* [dependencies](#dependencies)
|
||||
* [optional gpl stuff](#optional-gpl-stuff)
|
||||
@@ -72,7 +74,7 @@ you may also want these, especially on servers:
|
||||
* ☑ symlink/discard existing files (content-matching)
|
||||
* download
|
||||
* ☑ single files in browser
|
||||
* ✖ folders as zip files
|
||||
* ☑ folders as zip / tar files
|
||||
* ☑ FUSE client (read-only)
|
||||
* browser
|
||||
* ☑ tree-view
|
||||
@@ -95,6 +97,8 @@ summary: it works! you can use it! (but technically not even close to beta)
|
||||
|
||||
* Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade
|
||||
* Windows: python 2.7 cannot index non-ascii filenames with `-e2d`
|
||||
* Windows: python 2.7 cannot handle filenames with mojibake
|
||||
* hiding the contents at url `/d1/d2/d3` using `-v :d1/d2/d3:cd2d` has the side-effect of creating databases (for files/tags) inside folders d1 and d2, and those databases take precedence over the main db at the top of the vfs - this means all files in d2 and below will be reindexed unless you already had a vfs entry at or below d2
|
||||
* probably more, pls let me know
|
||||
|
||||
|
||||
@@ -107,6 +111,25 @@ the browser has the following hotkeys
|
||||
* `I/K` prev/next folder
|
||||
* `P` parent folder
|
||||
|
||||
you can link a particular timestamp in an audio file by adding it to the URL, such as `&20` / `&20s` / `&1m20` / `&1:20` after the `.../#af-c8960dab`
|
||||
|
||||
|
||||
## zip downloads
|
||||
|
||||
the `zip` link next to folders can produce various types of zip/tar files using these alternatives in the browser settings tab:
|
||||
|
||||
| name | url-suffix | description |
|
||||
|--|--|--|
|
||||
| `tar` | `?tar` | plain gnutar, works great with `curl \| tar -xv` |
|
||||
| `zip` | `?zip=utf8` | works everywhere, glitchy filenames on win7 and older |
|
||||
| `zip_dos` | `?zip` | traditional cp437 (no unicode) to fix glitchy filenames |
|
||||
| `zip_crc` | `?zip=crc` | cp437 with crc32 computed early for truly ancient software |
|
||||
|
||||
* hidden files (dotfiles) are excluded unless `-ed`
|
||||
* the up2k.db is always excluded
|
||||
* `zip_crc` will take longer to download since the server has to read each file twice
|
||||
* please let me know if you find a program old enough to actually need this
|
||||
|
||||
|
||||
# searching
|
||||
|
||||
@@ -176,6 +199,41 @@ copyparty can invoke external programs to collect additional metadata for files
|
||||
`python copyparty-sfx.py -v /mnt/nas/music:/music:r -e2dsa -e2ts -mtp .bpm=f,audio-bpm.py -mtp key=f,audio-key.py`
|
||||
|
||||
|
||||
# browser support
|
||||
|
||||
`ie` = internet-explorer, `ff` = firefox, `c` = chrome, `iOS` = iPhone/iPad, `Andr` = Android
|
||||
|
||||
| feature | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
|
||||
| --------------- | --- | --- | ---- | ---- | ----- | ---- | --- | ---- |
|
||||
| browse files | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| basic uploader | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| make directory | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| send message | yep | yep | yep | yep | yep | yep | yep | yep |
|
||||
| set sort order | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| zip selection | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| directory tree | - | - | `*1` | yep | yep | yep | yep | yep |
|
||||
| up2k | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| icons work | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| markdown editor | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| markdown viewer | - | - | yep | yep | yep | yep | yep | yep |
|
||||
| play mp3/m4a | - | yep | yep | yep | yep | yep | yep | yep |
|
||||
| play ogg/opus | - | - | - | - | yep | yep | `*2` | yep |
|
||||
|
||||
* internet explorer 6 to 8 behave the same
|
||||
* firefox 52 and chrome 49 are the last winxp versions
|
||||
* `*1` only public folders (login session is dropped) and no history / back-button
|
||||
* `*2` using a wasm decoder which can sometimes get stuck and consumes a bit more power
|
||||
|
||||
quick summary of more eccentric web-browsers trying to view a directory index:
|
||||
* safari (14.0.3/macos) is chrome with janky wasm, so playing opus can deadlock the javascript engine
|
||||
* safari (14.0.1/iOS) same as macos, except it recovers from the deadlocks if you poke it a bit
|
||||
* links (2.21/macports) can browse, login, upload/mkdir/msg
|
||||
* lynx (2.8.9/macports) can browse, login, upload/mkdir/msg
|
||||
* w3m (0.5.3/macports) can browse, login, upload at 100kB/s, mkdir/msg
|
||||
* netsurf (3.10/arch) is basically ie6 with much better css (javascript has almost no effect)
|
||||
* netscape 4.0 and 4.5 can browse (text is yellow on white), upload with `?b=u`
|
||||
* SerenityOS (22d13d8) hits a page fault, works with `?b=u`, file input not-impl, url params are multiplying
|
||||
|
||||
# client examples
|
||||
|
||||
* javascript: dump some state into a file (two separate examples)
|
||||
@@ -283,15 +341,20 @@ in the `scripts` folder:
|
||||
|
||||
roughly sorted by priority
|
||||
|
||||
* separate sqlite table per tag
|
||||
* audio fingerprinting
|
||||
* readme.md as epilogue
|
||||
* reduce up2k roundtrips
|
||||
* start from a chunk index and just go
|
||||
* terminate client on bad data
|
||||
* drop onto folders
|
||||
* `os.copy_file_range` for up2k cloning
|
||||
* up2k partials ui
|
||||
* support pillow-simd
|
||||
* cache sha512 chunks on client
|
||||
* comment field
|
||||
* ~~look into android thumbnail cache file format~~ bad idea
|
||||
* figure out the deal with pixel3a not being connectable as hotspot
|
||||
* pixel3a having unpredictable 3sec latency in general :||||
|
||||
|
||||
discarded ideas
|
||||
|
||||
* up2k partials ui
|
||||
* cache sha512 chunks on client
|
||||
* comment field
|
||||
* look into android thumbnail cache file format
|
||||
|
||||
@@ -12,7 +12,6 @@ import re
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import signal
|
||||
import shutil
|
||||
import filecmp
|
||||
import locale
|
||||
@@ -56,6 +55,12 @@ class RiceFormatter(argparse.HelpFormatter):
|
||||
return "".join(indent + line + "\n" for line in text.splitlines())
|
||||
|
||||
|
||||
class Dodge11874(RiceFormatter):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs["width"] = 9003
|
||||
super(Dodge11874, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
def warn(msg):
|
||||
print("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg))
|
||||
|
||||
@@ -167,7 +172,7 @@ def configure_ssl_ciphers(al):
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def sighandler(signal=None, frame=None):
|
||||
def sighandler(sig=None, frame=None):
|
||||
msg = [""] * 5
|
||||
for th in threading.enumerate():
|
||||
msg.append(str(th))
|
||||
@@ -177,34 +182,9 @@ def sighandler(signal=None, frame=None):
|
||||
print("\n".join(msg))
|
||||
|
||||
|
||||
def main():
|
||||
time.strptime("19970815", "%Y%m%d") # python#7980
|
||||
if WINDOWS:
|
||||
os.system("rem") # enables colors
|
||||
|
||||
desc = py_desc().replace("[", "\033[1;30m[")
|
||||
|
||||
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0m\n'
|
||||
print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
|
||||
|
||||
ensure_locale()
|
||||
if HAVE_SSL:
|
||||
ensure_cert()
|
||||
|
||||
deprecated = [["-e2s", "-e2ds"]]
|
||||
for dk, nk in deprecated:
|
||||
try:
|
||||
idx = sys.argv.index(dk)
|
||||
except:
|
||||
continue
|
||||
|
||||
msg = "\033[1;31mWARNING:\033[0;1m\n {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
|
||||
print(msg.format(dk, nk))
|
||||
sys.argv[idx] = nk
|
||||
time.sleep(2)
|
||||
|
||||
def run_argparse(argv, formatter):
|
||||
ap = argparse.ArgumentParser(
|
||||
formatter_class=RiceFormatter,
|
||||
formatter_class=formatter,
|
||||
prog="copyparty",
|
||||
description="http file sharing hub v{} ({})".format(S_VERSION, S_BUILD_DT),
|
||||
epilog=dedent(
|
||||
@@ -216,6 +196,9 @@ def main():
|
||||
|
||||
list of cflags:
|
||||
"cnodupe" rejects existing files (instead of symlinking them)
|
||||
"ce2d" sets -e2d (all -e2* args can be set using ce2* cflags)
|
||||
"cd2t" disables metadata collection, overrides -e2t*
|
||||
"cd2d" disables all database stuff, overrides -e2*
|
||||
|
||||
example:\033[35m
|
||||
-a ed:hunter2 -v .::r:aed -v ../inc:dump:w:aed:cnodupe \033[36m
|
||||
@@ -261,6 +244,7 @@ def main():
|
||||
ap.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
||||
ap.add_argument("-nih", action="store_true", help="no info hostname")
|
||||
ap.add_argument("-nid", action="store_true", help="no info disk-usage")
|
||||
ap.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||
ap.add_argument("--no-sendfile", action="store_true", help="disable sendfile (for debugging)")
|
||||
ap.add_argument("--no-scandir", action="store_true", help="disable scandir (for debugging)")
|
||||
ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms")
|
||||
@@ -289,9 +273,44 @@ def main():
|
||||
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
||||
ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
|
||||
|
||||
al = ap.parse_args()
|
||||
return ap.parse_args(args=argv[1:])
|
||||
# fmt: on
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
time.strptime("19970815", "%Y%m%d") # python#7980
|
||||
if WINDOWS:
|
||||
os.system("rem") # enables colors
|
||||
|
||||
if argv is None:
|
||||
argv = sys.argv
|
||||
|
||||
desc = py_desc().replace("[", "\033[1;30m[")
|
||||
|
||||
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0m\n'
|
||||
print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
|
||||
|
||||
ensure_locale()
|
||||
if HAVE_SSL:
|
||||
ensure_cert()
|
||||
|
||||
deprecated = [["-e2s", "-e2ds"]]
|
||||
for dk, nk in deprecated:
|
||||
try:
|
||||
idx = argv.index(dk)
|
||||
except:
|
||||
continue
|
||||
|
||||
msg = "\033[1;31mWARNING:\033[0;1m\n {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
|
||||
print(msg.format(dk, nk))
|
||||
argv[idx] = nk
|
||||
time.sleep(2)
|
||||
|
||||
try:
|
||||
al = run_argparse(argv, RiceFormatter)
|
||||
except AssertionError:
|
||||
al = run_argparse(argv, Dodge11874)
|
||||
|
||||
# propagate implications
|
||||
for k1, k2 in IMPLICATIONS:
|
||||
if getattr(al, k1):
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# coding: utf-8
|
||||
|
||||
VERSION = (0, 9, 13)
|
||||
CODENAME = "the strongest music server"
|
||||
BUILD_DT = (2021, 3, 23)
|
||||
VERSION = (0, 10, 13)
|
||||
CODENAME = "zip it"
|
||||
BUILD_DT = (2021, 4, 20)
|
||||
|
||||
S_VERSION = ".".join(map(str, VERSION))
|
||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import threading
|
||||
|
||||
from .__init__ import PY2, WINDOWS
|
||||
@@ -53,6 +54,7 @@ class VFS(object):
|
||||
self.uwrite,
|
||||
self.flags,
|
||||
)
|
||||
self._trk(vn)
|
||||
self.nodes[name] = vn
|
||||
return self._trk(vn.add(src, dst))
|
||||
|
||||
@@ -109,7 +111,27 @@ class VFS(object):
|
||||
if rem:
|
||||
rp += "/" + rem
|
||||
|
||||
return fsdec(os.path.realpath(fsenc(rp)))
|
||||
try:
|
||||
return fsdec(os.path.realpath(fsenc(rp)))
|
||||
except:
|
||||
if not WINDOWS:
|
||||
raise
|
||||
|
||||
# cpython bug introduced in 3.8, still exists in 3.9.1;
|
||||
# some win7sp1 and win10:20H2 boxes cannot realpath a
|
||||
# networked drive letter such as b"n:" or b"n:\\"
|
||||
#
|
||||
# requirements to trigger:
|
||||
# * bytestring (not unicode str)
|
||||
# * just the drive letter (subfolders are ok)
|
||||
# * networked drive (regular disks and vmhgfs are ok)
|
||||
# * on an enterprise network (idk, cannot repro with samba)
|
||||
#
|
||||
# hits the following exceptions in succession:
|
||||
# * access denied at L601: "path = _getfinalpathname(path)"
|
||||
# * "cant concat str to bytes" at L621: "return path + tail"
|
||||
#
|
||||
return os.path.realpath(rp)
|
||||
|
||||
def ls(self, rem, uname, scandir, lstat=False):
|
||||
"""return user-readable [fsdir,real,virt] items at vpath"""
|
||||
@@ -127,6 +149,78 @@ class VFS(object):
|
||||
|
||||
return [abspath, real, virt_vis]
|
||||
|
||||
def walk(self, rel, rem, uname, dots, scandir, lstat=False):
|
||||
"""
|
||||
recursively yields from ./rem;
|
||||
rel is a unix-style user-defined vpath (not vfs-related)
|
||||
"""
|
||||
|
||||
fsroot, vfs_ls, vfs_virt = self.ls(rem, uname, scandir, lstat)
|
||||
rfiles = [x for x in vfs_ls if not stat.S_ISDIR(x[1].st_mode)]
|
||||
rdirs = [x for x in vfs_ls if stat.S_ISDIR(x[1].st_mode)]
|
||||
|
||||
rfiles.sort()
|
||||
rdirs.sort()
|
||||
|
||||
yield rel, fsroot, rfiles, rdirs, vfs_virt
|
||||
|
||||
for rdir, _ in rdirs:
|
||||
if not dots and rdir.startswith("."):
|
||||
continue
|
||||
|
||||
wrel = (rel + "/" + rdir).lstrip("/")
|
||||
wrem = (rem + "/" + rdir).lstrip("/")
|
||||
for x in self.walk(wrel, wrem, uname, scandir, lstat):
|
||||
yield x
|
||||
|
||||
for n, vfs in sorted(vfs_virt.items()):
|
||||
if not dots and n.startswith("."):
|
||||
continue
|
||||
|
||||
wrel = (rel + "/" + n).lstrip("/")
|
||||
for x in vfs.walk(wrel, "", uname, scandir, lstat):
|
||||
yield x
|
||||
|
||||
def zipgen(self, vrem, flt, uname, dots, scandir):
|
||||
if flt:
|
||||
flt = {k: True for k in flt}
|
||||
|
||||
for vpath, apath, files, rd, vd in self.walk("", vrem, uname, dots, scandir):
|
||||
if flt:
|
||||
files = [x for x in files if x[0] in flt]
|
||||
|
||||
rm = [x for x in rd if x[0] not in flt]
|
||||
[rd.remove(x) for x in rm]
|
||||
|
||||
rm = [x for x in vd.keys() if x not in flt]
|
||||
[vd.pop(x) for x in rm]
|
||||
|
||||
flt = None
|
||||
|
||||
# print(repr([vpath, apath, [x[0] for x in files]]))
|
||||
fnames = [n[0] for n in files]
|
||||
vpaths = [vpath + "/" + n for n in fnames] if vpath else fnames
|
||||
apaths = [os.path.join(apath, n) for n in fnames]
|
||||
files = list(zip(vpaths, apaths, files))
|
||||
|
||||
if not dots:
|
||||
# dotfile filtering based on vpath (intended visibility)
|
||||
files = [x for x in files if "/." not in "/" + x[0]]
|
||||
|
||||
rm = [x for x in rd if x[0].startswith(".")]
|
||||
for x in rm:
|
||||
rd.remove(x)
|
||||
|
||||
rm = [k for k in vd.keys() if k.startswith(".")]
|
||||
for x in rm:
|
||||
del vd[x]
|
||||
|
||||
# up2k filetring based on actual abspath
|
||||
files = [x for x in files if "{0}.hist{0}up2k.".format(os.sep) not in x[1]]
|
||||
|
||||
for f in [{"vp": v, "ap": a, "st": n[1]} for v, a, n in files]:
|
||||
yield f
|
||||
|
||||
def user_tree(self, uname, readable=False, writable=False):
|
||||
ret = []
|
||||
opt1 = readable and (uname in self.uread or "*" in self.uread)
|
||||
@@ -159,12 +253,6 @@ class AuthSrv(object):
|
||||
def log(self, msg, c=0):
|
||||
self.log_func("auth", msg, c)
|
||||
|
||||
def invert(self, orig):
|
||||
if PY2:
|
||||
return {v: k for k, v in orig.iteritems()}
|
||||
else:
|
||||
return {v: k for k, v in orig.items()}
|
||||
|
||||
def laggy_iter(self, iterable):
|
||||
"""returns [value,isFinalValue]"""
|
||||
it = iter(iterable)
|
||||
@@ -421,7 +509,7 @@ class AuthSrv(object):
|
||||
with self.mutex:
|
||||
self.vfs = vfs
|
||||
self.user = user
|
||||
self.iuser = self.invert(user)
|
||||
self.iuser = {v: k for k, v in user.items()}
|
||||
|
||||
# import pprint
|
||||
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
|
||||
|
||||
@@ -51,7 +51,7 @@ class BrokerMp(object):
|
||||
self.procs.append(proc)
|
||||
proc.start()
|
||||
|
||||
if True:
|
||||
if not self.args.q:
|
||||
thr = threading.Thread(target=self.debug_load_balancer)
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
|
||||
@@ -7,6 +7,7 @@ import gzip
|
||||
import time
|
||||
import copy
|
||||
import json
|
||||
import string
|
||||
import socket
|
||||
import ctypes
|
||||
from datetime import datetime
|
||||
@@ -14,6 +15,8 @@ import calendar
|
||||
|
||||
from .__init__ import E, PY2, WINDOWS
|
||||
from .util import * # noqa # pylint: disable=unused-wildcard-import
|
||||
from .szip import StreamZip
|
||||
from .star import StreamTar
|
||||
|
||||
if not PY2:
|
||||
unicode = str
|
||||
@@ -52,6 +55,10 @@ class HttpCli(object):
|
||||
if rem.startswith("/") or rem.startswith("../") or "/../" in rem:
|
||||
raise Exception("that was close")
|
||||
|
||||
def j2(self, name, **kwargs):
|
||||
tpl = self.conn.hsrv.j2[name]
|
||||
return tpl.render(**kwargs) if kwargs else tpl
|
||||
|
||||
def run(self):
|
||||
"""returns true if connection can be reused"""
|
||||
self.keepalive = False
|
||||
@@ -67,7 +74,7 @@ class HttpCli(object):
|
||||
headerlines.pop(0)
|
||||
|
||||
try:
|
||||
self.mode, self.req, _ = headerlines[0].split(" ")
|
||||
self.mode, self.req, self.http_ver = headerlines[0].split(" ")
|
||||
except:
|
||||
raise Pebkac(400, "bad headers:\n" + "\n".join(headerlines))
|
||||
|
||||
@@ -86,30 +93,13 @@ class HttpCli(object):
|
||||
self.headers[k.lower()] = v.strip()
|
||||
|
||||
v = self.headers.get("connection", "").lower()
|
||||
self.keepalive = not v.startswith("close")
|
||||
self.keepalive = not v.startswith("close") and self.http_ver != "HTTP/1.0"
|
||||
|
||||
v = self.headers.get("x-forwarded-for", None)
|
||||
if v is not None and self.conn.addr[0] in ["127.0.0.1", "::1"]:
|
||||
self.ip = v.split(",")[0]
|
||||
self.log_src = self.conn.set_rproxy(self.ip)
|
||||
|
||||
self.uname = "*"
|
||||
if "cookie" in self.headers:
|
||||
cookies = self.headers["cookie"].split(";")
|
||||
for k, v in [x.split("=", 1) for x in cookies]:
|
||||
if k.strip() != "cppwd":
|
||||
continue
|
||||
|
||||
v = unescape_cookie(v)
|
||||
if v in self.auth.iuser:
|
||||
self.uname = self.auth.iuser[v]
|
||||
|
||||
break
|
||||
|
||||
if self.uname:
|
||||
self.rvol = self.auth.vfs.user_tree(self.uname, readable=True)
|
||||
self.wvol = self.auth.vfs.user_tree(self.uname, writable=True)
|
||||
|
||||
# split req into vpath + uparam
|
||||
uparam = {}
|
||||
if "?" not in self.req:
|
||||
@@ -133,6 +123,22 @@ class HttpCli(object):
|
||||
self.uparam = uparam
|
||||
self.vpath = unquotep(vpath)
|
||||
|
||||
pwd = None
|
||||
if "cookie" in self.headers:
|
||||
cookies = self.headers["cookie"].split(";")
|
||||
for k, v in [x.split("=", 1) for x in cookies]:
|
||||
if k.strip() != "cppwd":
|
||||
continue
|
||||
|
||||
pwd = unescape_cookie(v)
|
||||
break
|
||||
|
||||
pwd = uparam.get("pw", pwd)
|
||||
self.uname = self.auth.iuser.get(pwd, "*")
|
||||
if self.uname:
|
||||
self.rvol = self.auth.vfs.user_tree(self.uname, readable=True)
|
||||
self.wvol = self.auth.vfs.user_tree(self.uname, writable=True)
|
||||
|
||||
ua = self.headers.get("user-agent", "")
|
||||
if ua.startswith("rclone/"):
|
||||
uparam["raw"] = False
|
||||
@@ -153,14 +159,18 @@ class HttpCli(object):
|
||||
except Pebkac as ex:
|
||||
try:
|
||||
# self.log("pebkac at httpcli.run #2: " + repr(ex))
|
||||
self.keepalive = self._check_nonfatal(ex)
|
||||
self.loud_reply("{}: {}".format(str(ex), self.vpath), status=ex.code)
|
||||
if not self._check_nonfatal(ex):
|
||||
self.keepalive = False
|
||||
|
||||
self.log("{}\033[0m, {}".format(str(ex), self.vpath), 3)
|
||||
msg = "<pre>{}\r\nURL: {}\r\n".format(str(ex), self.vpath)
|
||||
self.reply(msg.encode("utf-8", "replace"), status=ex.code)
|
||||
return self.keepalive
|
||||
except Pebkac:
|
||||
return False
|
||||
|
||||
def send_headers(self, length, status=200, mime=None, headers={}):
|
||||
response = ["HTTP/1.1 {} {}".format(status, HTTPCODE[status])]
|
||||
response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])]
|
||||
|
||||
if length is not None:
|
||||
response.append("Content-Length: " + unicode(length))
|
||||
@@ -204,6 +214,20 @@ class HttpCli(object):
|
||||
self.log(body.rstrip())
|
||||
self.reply(b"<pre>" + body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
|
||||
|
||||
def urlq(self, add={}, rm=[]):
|
||||
"""
|
||||
generates url query based on uparam (b, pw, all others)
|
||||
removing anything in rm, adding pairs in add
|
||||
"""
|
||||
|
||||
kv = {k: v for k, v in self.uparam.items() if k not in rm}
|
||||
kv.update(add)
|
||||
if not kv:
|
||||
return ""
|
||||
|
||||
r = ["{}={}".format(k, quotep(v)) if v else k for k, v in kv.items()]
|
||||
return "?" + "&".join(r)
|
||||
|
||||
def handle_get(self):
|
||||
logmsg = "{:4} {}".format(self.mode, self.req)
|
||||
|
||||
@@ -312,8 +336,19 @@ class HttpCli(object):
|
||||
elif "print" in opt:
|
||||
reader, _ = self.get_body_reader()
|
||||
for buf in reader:
|
||||
buf = buf.decode("utf-8", "replace")
|
||||
self.log("urlform @ {}\n {}\n".format(self.vpath, buf))
|
||||
orig = buf.decode("utf-8", "replace")
|
||||
m = "urlform_raw {} @ {}\n {}\n"
|
||||
self.log(m.format(len(orig), self.vpath, orig))
|
||||
try:
|
||||
plain = unquote(buf.replace(b"+", b" "))
|
||||
plain = plain.decode("utf-8", "replace")
|
||||
if buf.startswith(b"msg="):
|
||||
plain = plain[4:]
|
||||
|
||||
m = "urlform_dec {} @ {}\n {}\n"
|
||||
self.log(m.format(len(plain), self.vpath, plain))
|
||||
except Exception as ex:
|
||||
self.log(repr(ex))
|
||||
|
||||
if "get" in opt:
|
||||
return self.handle_get()
|
||||
@@ -388,8 +423,30 @@ class HttpCli(object):
|
||||
if act == "tput":
|
||||
return self.handle_text_upload()
|
||||
|
||||
if act == "zip":
|
||||
return self.handle_zip_post()
|
||||
|
||||
raise Pebkac(422, 'invalid action "{}"'.format(act))
|
||||
|
||||
def handle_zip_post(self):
|
||||
for k in ["zip", "tar"]:
|
||||
v = self.uparam.get(k)
|
||||
if v is not None:
|
||||
break
|
||||
|
||||
if v is None:
|
||||
raise Pebkac(422, "need zip or tar keyword")
|
||||
|
||||
vn, rem = self.auth.vfs.get(self.vpath, self.uname, True, False)
|
||||
items = self.parser.require("files", 1024 * 1024)
|
||||
if not items:
|
||||
raise Pebkac(422, "need files list")
|
||||
|
||||
items = items.replace("\r", "").split("\n")
|
||||
items = [unquotep(x) for x in items if items]
|
||||
|
||||
return self.tx_zip(k, v, vn, rem, items, self.args.ed)
|
||||
|
||||
def handle_post_json(self):
|
||||
try:
|
||||
remains = int(self.headers["content-length"])
|
||||
@@ -417,15 +474,18 @@ class HttpCli(object):
|
||||
if "srch" in self.uparam or "srch" in body:
|
||||
return self.handle_search(body)
|
||||
|
||||
# prefer this over undot; no reason to allow traversion
|
||||
if "/" in body["name"]:
|
||||
raise Pebkac(400, "folders verboten")
|
||||
|
||||
# up2k-php compat
|
||||
for k in "chunkpit.php", "handshake.php":
|
||||
if self.vpath.endswith(k):
|
||||
self.vpath = self.vpath[: -len(k)]
|
||||
|
||||
sub = None
|
||||
name = undot(body["name"])
|
||||
if "/" in name:
|
||||
sub, name = name.rsplit("/", 1)
|
||||
self.vpath = "/".join([self.vpath, sub]).strip("/")
|
||||
body["name"] = name
|
||||
|
||||
vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
|
||||
|
||||
body["vtop"] = vfs.vpath
|
||||
@@ -434,12 +494,22 @@ class HttpCli(object):
|
||||
body["addr"] = self.ip
|
||||
body["vcfg"] = vfs.flags
|
||||
|
||||
x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body)
|
||||
response = x.get()
|
||||
response = json.dumps(response)
|
||||
if sub:
|
||||
try:
|
||||
dst = os.path.join(vfs.realpath, rem)
|
||||
os.makedirs(dst)
|
||||
except:
|
||||
if not os.path.isdir(dst):
|
||||
raise Pebkac(400, "some file got your folder name")
|
||||
|
||||
self.log(response)
|
||||
self.reply(response.encode("utf-8"), mime="application/json")
|
||||
x = self.conn.hsrv.broker.put(True, "up2k.handle_json", body)
|
||||
ret = x.get()
|
||||
if sub:
|
||||
ret["name"] = "/".join([sub, ret["name"]])
|
||||
|
||||
ret = json.dumps(ret)
|
||||
self.log(ret)
|
||||
self.reply(ret.encode("utf-8"), mime="application/json")
|
||||
return True
|
||||
|
||||
def handle_search(self, body):
|
||||
@@ -580,7 +650,7 @@ class HttpCli(object):
|
||||
pwd = "x" # nosec
|
||||
|
||||
h = {"Set-Cookie": "cppwd={}; Path=/; SameSite=Lax".format(pwd)}
|
||||
html = self.conn.tpl_msg.render(h1=msg, h2='<a href="/">ack</a>', redir="/")
|
||||
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
|
||||
self.reply(html.encode("utf-8"), headers=h)
|
||||
return True
|
||||
|
||||
@@ -611,7 +681,8 @@ class HttpCli(object):
|
||||
|
||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||
esc_paths = [quotep(vpath), html_escape(vpath)]
|
||||
html = self.conn.tpl_msg.render(
|
||||
html = self.j2(
|
||||
"msg",
|
||||
h2='<a href="/{}">go to /{}</a>'.format(*esc_paths),
|
||||
pre="aight",
|
||||
click=True,
|
||||
@@ -643,7 +714,8 @@ class HttpCli(object):
|
||||
f.write(b"`GRUNNUR`\n")
|
||||
|
||||
vpath = "{}/{}".format(self.vpath, sanitized).lstrip("/")
|
||||
html = self.conn.tpl_msg.render(
|
||||
html = self.j2(
|
||||
"msg",
|
||||
h2='<a href="/{}?edit">go to /{}?edit</a>'.format(
|
||||
quotep(vpath), html_escape(vpath)
|
||||
),
|
||||
@@ -749,7 +821,8 @@ class HttpCli(object):
|
||||
).encode("utf-8")
|
||||
)
|
||||
|
||||
html = self.conn.tpl_msg.render(
|
||||
html = self.j2(
|
||||
"msg",
|
||||
h2='<a href="/{}">return to /{}</a>'.format(
|
||||
quotep(self.vpath), html_escape(self.vpath)
|
||||
),
|
||||
@@ -1037,16 +1110,75 @@ class HttpCli(object):
|
||||
self.log("{}, {}".format(logmsg, spd))
|
||||
return ret
|
||||
|
||||
def tx_zip(self, fmt, uarg, vn, rem, items, dots):
|
||||
if self.args.no_zip:
|
||||
raise Pebkac(400, "not enabled")
|
||||
|
||||
logmsg = "{:4} {} ".format("", self.req)
|
||||
self.keepalive = False
|
||||
|
||||
if not uarg:
|
||||
uarg = ""
|
||||
|
||||
if fmt == "tar":
|
||||
mime = "application/x-tar"
|
||||
packer = StreamTar
|
||||
else:
|
||||
mime = "application/zip"
|
||||
packer = StreamZip
|
||||
|
||||
fn = items[0] if items and items[0] else self.vpath
|
||||
if fn:
|
||||
fn = fn.rstrip("/").split("/")[-1]
|
||||
else:
|
||||
fn = self.headers.get("host", "hey")
|
||||
|
||||
afn = "".join(
|
||||
[x if x in (string.ascii_letters + string.digits) else "_" for x in fn]
|
||||
)
|
||||
|
||||
bascii = unicode(string.ascii_letters + string.digits).encode("utf-8")
|
||||
ufn = fn.encode("utf-8", "xmlcharrefreplace")
|
||||
if PY2:
|
||||
ufn = [unicode(x) if x in bascii else "%{:02x}".format(ord(x)) for x in ufn]
|
||||
else:
|
||||
ufn = [
|
||||
chr(x).encode("utf-8")
|
||||
if x in bascii
|
||||
else "%{:02x}".format(x).encode("ascii")
|
||||
for x in ufn
|
||||
]
|
||||
ufn = b"".join(ufn).decode("ascii")
|
||||
|
||||
cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
|
||||
cdis = cdis.format(afn, fmt, ufn, fmt)
|
||||
self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
|
||||
|
||||
fgen = vn.zipgen(rem, items, self.uname, dots, not self.args.no_scandir)
|
||||
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
||||
bgen = packer(fgen, utf8="utf" in uarg, pre_crc="crc" in uarg)
|
||||
bsent = 0
|
||||
for buf in bgen.gen():
|
||||
if not buf:
|
||||
break
|
||||
|
||||
try:
|
||||
self.s.sendall(buf)
|
||||
bsent += len(buf)
|
||||
except:
|
||||
logmsg += " \033[31m" + unicode(bsent) + "\033[0m"
|
||||
break
|
||||
|
||||
spd = self._spd(bsent)
|
||||
self.log("{}, {}".format(logmsg, spd))
|
||||
return True
|
||||
|
||||
def tx_md(self, fs_path):
|
||||
logmsg = "{:4} {} ".format("", self.req)
|
||||
if "edit2" in self.uparam:
|
||||
html_path = "web/mde.html"
|
||||
template = self.conn.tpl_mde
|
||||
else:
|
||||
html_path = "web/md.html"
|
||||
template = self.conn.tpl_md
|
||||
|
||||
html_path = os.path.join(E.mod, html_path)
|
||||
tpl = "mde" if "edit2" in self.uparam else "md"
|
||||
html_path = os.path.join(E.mod, "web", "{}.html".format(tpl))
|
||||
template = self.j2(tpl)
|
||||
|
||||
st = os.stat(fsenc(fs_path))
|
||||
# sz_md = st.st_size
|
||||
@@ -1096,9 +1228,10 @@ class HttpCli(object):
|
||||
return True
|
||||
|
||||
def tx_mounts(self):
|
||||
suf = self.urlq(rm=["h"])
|
||||
rvol = [x + "/" if x else x for x in self.rvol]
|
||||
wvol = [x + "/" if x else x for x in self.wvol]
|
||||
html = self.conn.tpl_mounts.render(this=self, rvol=rvol, wvol=wvol)
|
||||
html = self.j2("splash", this=self, rvol=rvol, wvol=wvol, url_suf=suf)
|
||||
self.reply(html.encode("utf-8"))
|
||||
return True
|
||||
|
||||
@@ -1187,6 +1320,11 @@ class HttpCli(object):
|
||||
|
||||
return self.tx_file(abspath)
|
||||
|
||||
for k in ["zip", "tar"]:
|
||||
v = self.uparam.get(k)
|
||||
if v is not None:
|
||||
return self.tx_zip(k, v, vn, rem, [], self.args.ed)
|
||||
|
||||
fsroot, vfs_ls, vfs_virt = vn.ls(rem, self.uname, not self.args.no_scandir)
|
||||
stats = {k: v for k, v in vfs_ls}
|
||||
vfs_ls = [x[0] for x in vfs_ls]
|
||||
@@ -1223,6 +1361,8 @@ class HttpCli(object):
|
||||
idx = self.conn.get_u2idx()
|
||||
icur = idx.get_cur(vn.realpath)
|
||||
|
||||
url_suf = self.urlq()
|
||||
|
||||
dirs = []
|
||||
files = []
|
||||
for fn in vfs_ls:
|
||||
@@ -1247,8 +1387,11 @@ class HttpCli(object):
|
||||
|
||||
is_dir = stat.S_ISDIR(inf.st_mode)
|
||||
if is_dir:
|
||||
margin = "DIR"
|
||||
href += "/"
|
||||
if self.args.no_zip:
|
||||
margin = "DIR"
|
||||
else:
|
||||
margin = '<a href="{}?zip">zip</a>'.format(quotep(href))
|
||||
elif fn in hist:
|
||||
margin = '<a href="{}.hist/{}">#{}</a>'.format(
|
||||
base, html_escape(hist[fn][2], quote=True), hist[fn][0]
|
||||
@@ -1372,7 +1515,12 @@ class HttpCli(object):
|
||||
|
||||
dirs.extend(files)
|
||||
|
||||
html = self.conn.tpl_browser.render(
|
||||
tpl = "browser"
|
||||
if "b" in self.uparam:
|
||||
tpl = "browser2"
|
||||
|
||||
html = self.j2(
|
||||
tpl,
|
||||
vdir=quotep(self.vpath),
|
||||
vpnodes=vpnodes,
|
||||
files=dirs,
|
||||
@@ -1384,6 +1532,9 @@ class HttpCli(object):
|
||||
),
|
||||
have_up2k_idx=("e2d" in vn.flags),
|
||||
have_tags_idx=("e2t" in vn.flags),
|
||||
have_zip=(not self.args.no_zip),
|
||||
have_b_u=(self.writable and self.uparam.get("b") == "u"),
|
||||
url_suf=url_suf,
|
||||
logues=logues,
|
||||
title=html_escape(self.vpath),
|
||||
srv_info=srv_info,
|
||||
|
||||
@@ -12,23 +12,6 @@ try:
|
||||
except:
|
||||
HAVE_SSL = False
|
||||
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError:
|
||||
print(
|
||||
"""\033[1;31m
|
||||
you do not have jinja2 installed,\033[33m
|
||||
choose one of these:\033[0m
|
||||
* apt install python-jinja2
|
||||
* {} -m pip install --user jinja2
|
||||
* (try another python version, if you have one)
|
||||
* (try copyparty.sfx instead)
|
||||
""".format(
|
||||
os.path.basename(sys.executable)
|
||||
)
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
from .__init__ import E
|
||||
from .util import Unrecv
|
||||
from .httpcli import HttpCli
|
||||
@@ -57,14 +40,6 @@ class HttpConn(object):
|
||||
self.log_func = hsrv.log
|
||||
self.set_rproxy()
|
||||
|
||||
env = jinja2.Environment()
|
||||
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
||||
self.tpl_mounts = env.get_template("splash.html")
|
||||
self.tpl_browser = env.get_template("browser.html")
|
||||
self.tpl_msg = env.get_template("msg.html")
|
||||
self.tpl_md = env.get_template("md.html")
|
||||
self.tpl_mde = env.get_template("mde.html")
|
||||
|
||||
def set_rproxy(self, ip=None):
|
||||
if ip is None:
|
||||
color = 36
|
||||
@@ -112,7 +87,9 @@ class HttpConn(object):
|
||||
err = "need at least 4 bytes in the first packet; got {}".format(
|
||||
len(method)
|
||||
)
|
||||
self.log(err)
|
||||
if method:
|
||||
self.log(err)
|
||||
|
||||
self.s.send(b"HTTP/1.1 400 Bad Request\r\n\r\n" + err.encode("utf-8"))
|
||||
return
|
||||
|
||||
|
||||
@@ -2,10 +2,28 @@
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
import threading
|
||||
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError:
|
||||
print(
|
||||
"""\033[1;31m
|
||||
you do not have jinja2 installed,\033[33m
|
||||
choose one of these:\033[0m
|
||||
* apt install python-jinja2
|
||||
* {} -m pip install --user jinja2
|
||||
* (try another python version, if you have one)
|
||||
* (try copyparty.sfx instead)
|
||||
""".format(
|
||||
os.path.basename(sys.executable)
|
||||
)
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
from .__init__ import E, MACOS
|
||||
from .httpconn import HttpConn
|
||||
from .authsrv import AuthSrv
|
||||
@@ -30,6 +48,13 @@ class HttpSrv(object):
|
||||
self.workload_thr_alive = False
|
||||
self.auth = AuthSrv(self.args, self.log)
|
||||
|
||||
env = jinja2.Environment()
|
||||
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
||||
self.j2 = {
|
||||
x: env.get_template(x + ".html")
|
||||
for x in ["splash", "browser", "browser2", "msg", "md", "mde"]
|
||||
}
|
||||
|
||||
cert_path = os.path.join(E.cfg, "cert.pem")
|
||||
if os.path.exists(cert_path):
|
||||
self.cert_path = cert_path
|
||||
|
||||
95
copyparty/star.py
Normal file
95
copyparty/star.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import os
|
||||
import tarfile
|
||||
import threading
|
||||
|
||||
from .sutil import errdesc
|
||||
from .util import Queue, fsenc
|
||||
|
||||
|
||||
class QFile(object):
|
||||
"""file-like object which buffers writes into a queue"""
|
||||
|
||||
def __init__(self):
|
||||
self.q = Queue(64)
|
||||
self.bq = []
|
||||
self.nq = 0
|
||||
|
||||
def write(self, buf):
|
||||
if buf is None or self.nq >= 240 * 1024:
|
||||
self.q.put(b"".join(self.bq))
|
||||
self.bq = []
|
||||
self.nq = 0
|
||||
|
||||
if buf is None:
|
||||
self.q.put(None)
|
||||
else:
|
||||
self.bq.append(buf)
|
||||
self.nq += len(buf)
|
||||
|
||||
|
||||
class StreamTar(object):
|
||||
"""construct in-memory tar file from the given path"""
|
||||
|
||||
def __init__(self, fgen, **kwargs):
|
||||
self.ci = 0
|
||||
self.co = 0
|
||||
self.qfile = QFile()
|
||||
self.fgen = fgen
|
||||
self.errf = None
|
||||
|
||||
# python 3.8 changed to PAX_FORMAT as default,
|
||||
# waste of space and don't care about the new features
|
||||
fmt = tarfile.GNU_FORMAT
|
||||
self.tar = tarfile.open(fileobj=self.qfile, mode="w|", format=fmt)
|
||||
|
||||
w = threading.Thread(target=self._gen)
|
||||
w.daemon = True
|
||||
w.start()
|
||||
|
||||
def gen(self):
|
||||
while True:
|
||||
buf = self.qfile.q.get()
|
||||
if not buf:
|
||||
break
|
||||
|
||||
self.co += len(buf)
|
||||
yield buf
|
||||
|
||||
yield None
|
||||
if self.errf:
|
||||
os.unlink(self.errf["ap"])
|
||||
|
||||
def ser(self, f):
|
||||
name = f["vp"]
|
||||
src = f["ap"]
|
||||
fsi = f["st"]
|
||||
|
||||
inf = tarfile.TarInfo(name=name)
|
||||
inf.mode = fsi.st_mode
|
||||
inf.size = fsi.st_size
|
||||
inf.mtime = fsi.st_mtime
|
||||
inf.uid = 0
|
||||
inf.gid = 0
|
||||
|
||||
self.ci += inf.size
|
||||
with open(fsenc(src), "rb", 512 * 1024) as f:
|
||||
self.tar.addfile(inf, f)
|
||||
|
||||
def _gen(self):
|
||||
errors = []
|
||||
for f in self.fgen:
|
||||
if "err" in f:
|
||||
errors.append([f["vp"], f["err"]])
|
||||
continue
|
||||
|
||||
try:
|
||||
self.ser(f)
|
||||
except Exception as ex:
|
||||
errors.append([f["vp"], repr(ex)])
|
||||
|
||||
if errors:
|
||||
self.errf = errdesc(errors)
|
||||
self.ser(self.errf)
|
||||
|
||||
self.tar.close()
|
||||
self.qfile.write(None)
|
||||
25
copyparty/sutil.py
Normal file
25
copyparty/sutil.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import os
|
||||
import time
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def errdesc(errors):
|
||||
report = ["copyparty failed to add the following files to the archive:", ""]
|
||||
|
||||
for fn, err in errors:
|
||||
report.extend([" file: {}".format(fn), "error: {}".format(err), ""])
|
||||
|
||||
with tempfile.NamedTemporaryFile(prefix="copyparty-", delete=False) as tf:
|
||||
tf_path = tf.name
|
||||
tf.write("\r\n".join(report).encode("utf-8", "replace"))
|
||||
|
||||
dt = datetime.utcfromtimestamp(time.time())
|
||||
dt = dt.strftime("%Y-%m%d-%H%M%S")
|
||||
|
||||
os.chmod(tf_path, 0o444)
|
||||
return {
|
||||
"vp": "archive-errors-{}.txt".format(dt),
|
||||
"ap": tf_path,
|
||||
"st": os.stat(tf_path),
|
||||
}
|
||||
271
copyparty/szip.py
Normal file
271
copyparty/szip.py
Normal file
@@ -0,0 +1,271 @@
|
||||
import os
|
||||
import time
|
||||
import zlib
|
||||
import struct
|
||||
from datetime import datetime
|
||||
|
||||
from .sutil import errdesc
|
||||
from .util import yieldfile, sanitize_fn
|
||||
|
||||
|
||||
def dostime2unix(buf):
|
||||
t, d = struct.unpack("<HH", buf)
|
||||
|
||||
ts = (t & 0x1F) * 2
|
||||
tm = (t >> 5) & 0x3F
|
||||
th = t >> 11
|
||||
|
||||
dd = d & 0x1F
|
||||
dm = (d >> 5) & 0xF
|
||||
dy = (d >> 9) + 1980
|
||||
|
||||
tt = (dy, dm, dd, th, tm, ts)
|
||||
tf = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}"
|
||||
iso = tf.format(*tt)
|
||||
|
||||
dt = datetime.strptime(iso, "%Y-%m-%d %H:%M:%S")
|
||||
return int(dt.timestamp())
|
||||
|
||||
|
||||
def unixtime2dos(ts):
|
||||
tt = time.gmtime(ts)
|
||||
dy, dm, dd, th, tm, ts = list(tt)[:6]
|
||||
|
||||
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
||||
bt = (th << 11) + (tm << 5) + ts // 2
|
||||
return struct.pack("<HH", bt, bd)
|
||||
|
||||
|
||||
def gen_fdesc(sz, crc32, z64):
|
||||
ret = b"\x50\x4b\x07\x08"
|
||||
fmt = "<LQQ" if z64 else "<LLL"
|
||||
ret += struct.pack(fmt, crc32, sz, sz)
|
||||
return ret
|
||||
|
||||
|
||||
def gen_hdr(h_pos, fn, sz, lastmod, utf8, crc32, pre_crc):
|
||||
"""
|
||||
does regular file headers
|
||||
and the central directory meme if h_pos is set
|
||||
(h_pos = absolute position of the regular header)
|
||||
"""
|
||||
|
||||
# appnote 4.5 / zip 3.0 (2008) / unzip 6.0 (2009) says to add z64
|
||||
# extinfo for values which exceed H, but that becomes an off-by-one
|
||||
# (can't tell if it was clamped or exactly maxval), make it obvious
|
||||
z64 = sz >= 0xFFFFFFFF
|
||||
z64v = [sz, sz] if z64 else []
|
||||
if h_pos and h_pos >= 0xFFFFFFFF:
|
||||
# central, also consider ptr to original header
|
||||
z64v.append(h_pos)
|
||||
|
||||
# confusingly this doesn't bump if h_pos
|
||||
req_ver = b"\x2d\x00" if z64 else b"\x0a\x00"
|
||||
|
||||
if crc32:
|
||||
crc32 = struct.pack("<L", crc32)
|
||||
else:
|
||||
crc32 = b"\x00" * 4
|
||||
|
||||
if h_pos is None:
|
||||
# 4b magic, 2b min-ver
|
||||
ret = b"\x50\x4b\x03\x04" + req_ver
|
||||
else:
|
||||
# 4b magic, 2b spec-ver, 2b min-ver
|
||||
ret = b"\x50\x4b\x01\x02\x1e\x03" + req_ver
|
||||
|
||||
ret += b"\x00" if pre_crc else b"\x08" # streaming
|
||||
ret += b"\x08" if utf8 else b"\x00" # appnote 6.3.2 (2007)
|
||||
|
||||
# 2b compression, 4b time, 4b crc
|
||||
ret += b"\x00\x00" + unixtime2dos(lastmod) + crc32
|
||||
|
||||
# spec says to put zeros when !crc if bit3 (streaming)
|
||||
# however infozip does actual sz and it even works on winxp
|
||||
# (same reasning for z64 extradata later)
|
||||
vsz = 0xFFFFFFFF if z64 else sz
|
||||
ret += struct.pack("<LL", vsz, vsz)
|
||||
|
||||
# windows support (the "?" replace below too)
|
||||
fn = sanitize_fn(fn, "/")
|
||||
bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
|
||||
|
||||
z64_len = len(z64v) * 8 + 4 if z64v else 0
|
||||
ret += struct.pack("<HH", len(bfn), z64_len)
|
||||
|
||||
if h_pos is not None:
|
||||
# 2b comment, 2b diskno
|
||||
ret += b"\x00" * 4
|
||||
|
||||
# 2b internal.attr, 4b external.attr
|
||||
# infozip-macos: 0100 0000 a481 file:644
|
||||
# infozip-macos: 0100 0100 0080 file:000
|
||||
ret += b"\x01\x00\x00\x00\xa4\x81"
|
||||
|
||||
# 4b local-header-ofs
|
||||
ret += struct.pack("<L", min(h_pos, 0xFFFFFFFF))
|
||||
|
||||
ret += bfn
|
||||
|
||||
if z64v:
|
||||
ret += struct.pack("<HH" + "Q" * len(z64v), 1, len(z64v) * 8, *z64v)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def gen_ecdr(items, cdir_pos, cdir_end):
|
||||
"""
|
||||
summary of all file headers,
|
||||
usually the zipfile footer unless something clamps
|
||||
"""
|
||||
|
||||
ret = b"\x50\x4b\x05\x06"
|
||||
|
||||
# 2b ndisk, 2b disk0
|
||||
ret += b"\x00" * 4
|
||||
|
||||
cdir_sz = cdir_end - cdir_pos
|
||||
|
||||
nitems = min(0xFFFF, len(items))
|
||||
csz = min(0xFFFFFFFF, cdir_sz)
|
||||
cpos = min(0xFFFFFFFF, cdir_pos)
|
||||
|
||||
need_64 = nitems == 0xFFFF or 0xFFFFFFFF in [csz, cpos]
|
||||
|
||||
# 2b tnfiles, 2b dnfiles, 4b dir sz, 4b dir pos
|
||||
ret += struct.pack("<HHLL", nitems, nitems, csz, cpos)
|
||||
|
||||
# 2b comment length
|
||||
ret += b"\x00\x00"
|
||||
|
||||
return [ret, need_64]
|
||||
|
||||
|
||||
def gen_ecdr64(items, cdir_pos, cdir_end):
|
||||
"""
|
||||
z64 end of central directory
|
||||
added when numfiles or a headerptr clamps
|
||||
"""
|
||||
|
||||
ret = b"\x50\x4b\x06\x06"
|
||||
|
||||
# 8b own length from hereon
|
||||
ret += b"\x2c" + b"\x00" * 7
|
||||
|
||||
# 2b spec-ver, 2b min-ver
|
||||
ret += b"\x1e\x03\x2d\x00"
|
||||
|
||||
# 4b ndisk, 4b disk0
|
||||
ret += b"\x00" * 8
|
||||
|
||||
# 8b tnfiles, 8b dnfiles, 8b dir sz, 8b dir pos
|
||||
cdir_sz = cdir_end - cdir_pos
|
||||
ret += struct.pack("<QQQQ", len(items), len(items), cdir_sz, cdir_pos)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def gen_ecdr64_loc(ecdr64_pos):
|
||||
"""
|
||||
z64 end of central directory locator
|
||||
points to ecdr64
|
||||
why
|
||||
"""
|
||||
|
||||
ret = b"\x50\x4b\x06\x07"
|
||||
|
||||
# 4b cdisk, 8b start of ecdr64, 4b ndisks
|
||||
ret += struct.pack("<LQL", 0, ecdr64_pos, 1)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class StreamZip(object):
|
||||
def __init__(self, fgen, utf8=False, pre_crc=False):
|
||||
self.fgen = fgen
|
||||
self.utf8 = utf8
|
||||
self.pre_crc = pre_crc
|
||||
|
||||
self.pos = 0
|
||||
self.items = []
|
||||
|
||||
def _ct(self, buf):
|
||||
self.pos += len(buf)
|
||||
return buf
|
||||
|
||||
def ser(self, f):
|
||||
name = f["vp"]
|
||||
src = f["ap"]
|
||||
st = f["st"]
|
||||
|
||||
sz = st.st_size
|
||||
ts = st.st_mtime + 1
|
||||
|
||||
crc = None
|
||||
if self.pre_crc:
|
||||
crc = 0
|
||||
for buf in yieldfile(src):
|
||||
crc = zlib.crc32(buf, crc)
|
||||
|
||||
crc &= 0xFFFFFFFF
|
||||
|
||||
h_pos = self.pos
|
||||
buf = gen_hdr(None, name, sz, ts, self.utf8, crc, self.pre_crc)
|
||||
yield self._ct(buf)
|
||||
|
||||
crc = crc or 0
|
||||
for buf in yieldfile(src):
|
||||
if not self.pre_crc:
|
||||
crc = zlib.crc32(buf, crc)
|
||||
|
||||
yield self._ct(buf)
|
||||
|
||||
crc &= 0xFFFFFFFF
|
||||
|
||||
self.items.append([name, sz, ts, crc, h_pos])
|
||||
|
||||
z64 = sz >= 4 * 1024 * 1024 * 1024
|
||||
|
||||
if z64 or not self.pre_crc:
|
||||
buf = gen_fdesc(sz, crc, z64)
|
||||
yield self._ct(buf)
|
||||
|
||||
def gen(self):
|
||||
errors = []
|
||||
for f in self.fgen:
|
||||
if "err" in f:
|
||||
errors.append([f["vp"], f["err"]])
|
||||
continue
|
||||
|
||||
try:
|
||||
for x in self.ser(f):
|
||||
yield x
|
||||
except Exception as ex:
|
||||
errors.append([f["vp"], repr(ex)])
|
||||
|
||||
if errors:
|
||||
errf = errdesc(errors)
|
||||
print(repr(errf))
|
||||
for x in self.ser(errf):
|
||||
yield x
|
||||
|
||||
cdir_pos = self.pos
|
||||
for name, sz, ts, crc, h_pos in self.items:
|
||||
buf = gen_hdr(h_pos, name, sz, ts, self.utf8, crc, self.pre_crc)
|
||||
yield self._ct(buf)
|
||||
cdir_end = self.pos
|
||||
|
||||
_, need_64 = gen_ecdr(self.items, cdir_pos, cdir_end)
|
||||
if need_64:
|
||||
ecdir64_pos = self.pos
|
||||
buf = gen_ecdr64(self.items, cdir_pos, cdir_end)
|
||||
yield self._ct(buf)
|
||||
|
||||
buf = gen_ecdr64_loc(ecdir64_pos)
|
||||
yield self._ct(buf)
|
||||
|
||||
ecdr, _ = gen_ecdr(self.items, cdir_pos, cdir_end)
|
||||
yield self._ct(ecdr)
|
||||
|
||||
if errors:
|
||||
os.unlink(errf["ap"])
|
||||
@@ -101,17 +101,18 @@ class Up2k(object):
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
|
||||
thr = threading.Thread(target=self._tagger)
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
|
||||
thr = threading.Thread(target=self._hasher)
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
|
||||
thr = threading.Thread(target=self._run_all_mtp)
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
if self.mtag:
|
||||
thr = threading.Thread(target=self._tagger)
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
|
||||
thr = threading.Thread(target=self._run_all_mtp)
|
||||
thr.daemon = True
|
||||
thr.start()
|
||||
|
||||
def log(self, msg, c=0):
|
||||
self.log_func("up2k", msg + "\033[K", c)
|
||||
@@ -232,7 +233,8 @@ class Up2k(object):
|
||||
(ft if v is True else ff if v is False else fv).format(k, str(v))
|
||||
for k, v in flags.items()
|
||||
]
|
||||
self.log(" ".join(sorted(a)) + "\033[0m")
|
||||
if a:
|
||||
self.log(" ".join(sorted(a)) + "\033[0m")
|
||||
|
||||
reg = {}
|
||||
path = os.path.join(ptop, ".hist", "up2k.snap")
|
||||
@@ -1067,6 +1069,8 @@ class Up2k(object):
|
||||
with self.mutex:
|
||||
job = self.registry[ptop].get(wark, None)
|
||||
if not job:
|
||||
known = " ".join([x for x in self.registry[ptop].keys()])
|
||||
self.log("unknown wark [{}], known: {}".format(wark, known))
|
||||
raise Pebkac(400, "unknown wark")
|
||||
|
||||
if chash not in job["need"]:
|
||||
@@ -1309,6 +1313,7 @@ class Up2k(object):
|
||||
self.log("no cursor to write tags with??", c=1)
|
||||
continue
|
||||
|
||||
# TODO is undef if vol 404 on startup
|
||||
entags = self.entags[ptop]
|
||||
if not entags:
|
||||
self.log("no entags okay.jpg", c=3)
|
||||
|
||||
@@ -576,11 +576,12 @@ def undot(path):
|
||||
return "/".join(ret)
|
||||
|
||||
|
||||
def sanitize_fn(fn):
|
||||
fn = fn.replace("\\", "/").split("/")[-1]
|
||||
def sanitize_fn(fn, ok=""):
|
||||
if "/" not in ok:
|
||||
fn = fn.replace("\\", "/").split("/")[-1]
|
||||
|
||||
if WINDOWS:
|
||||
for bad, good in [
|
||||
for bad, good in [x for x in [
|
||||
["<", "<"],
|
||||
[">", ">"],
|
||||
[":", ":"],
|
||||
@@ -590,7 +591,7 @@ def sanitize_fn(fn):
|
||||
["|", "|"],
|
||||
["?", "?"],
|
||||
["*", "*"],
|
||||
]:
|
||||
] if x[0] not in ok]:
|
||||
fn = fn.replace(bad, good)
|
||||
|
||||
bad = ["con", "prn", "aux", "nul"]
|
||||
@@ -780,6 +781,16 @@ def read_socket_chunked(sr, log=None):
|
||||
sr.recv(2) # \r\n after each chunk too
|
||||
|
||||
|
||||
def yieldfile(fn):
|
||||
with open(fsenc(fn), "rb", 512 * 1024) as f:
|
||||
while True:
|
||||
buf = f.read(64 * 1024)
|
||||
if not buf:
|
||||
break
|
||||
|
||||
yield buf
|
||||
|
||||
|
||||
def hashcopy(actor, fin, fout):
|
||||
u32_lim = int((2 ** 31) * 0.9)
|
||||
hashobj = hashlib.sha512()
|
||||
|
||||
@@ -182,6 +182,11 @@ a, #files tbody div a:last-child {
|
||||
color: #840;
|
||||
text-shadow: 0 0 .3em #b80;
|
||||
}
|
||||
#files tbody tr.sel td {
|
||||
color: #fff;
|
||||
background: #925;
|
||||
border-color: #c37;
|
||||
}
|
||||
#blocked {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -238,7 +243,7 @@ a, #files tbody div a:last-child {
|
||||
height: 100%;
|
||||
background: #3c3c3c;
|
||||
}
|
||||
#wtoggle {
|
||||
#wtico {
|
||||
cursor: url(/.cpr/dd/1.png), pointer;
|
||||
animation: cursor 500ms infinite;
|
||||
}
|
||||
@@ -268,6 +273,33 @@ a, #files tbody div a:last-child {
|
||||
padding: .2em 0 0 .07em;
|
||||
color: #fff;
|
||||
}
|
||||
#wzip {
|
||||
display: none;
|
||||
margin-right: .3em;
|
||||
padding-right: .3em;
|
||||
border-right: .1em solid #555;
|
||||
}
|
||||
#wtoggle,
|
||||
#wtoggle * {
|
||||
line-height: 1em;
|
||||
}
|
||||
#wtoggle.sel {
|
||||
width: 6.4em;
|
||||
}
|
||||
#wtoggle.sel #wzip {
|
||||
display: inline-block;
|
||||
}
|
||||
#wtoggle.sel #wzip a {
|
||||
font-size: .4em;
|
||||
padding: 0 .3em;
|
||||
margin: -.3em .2em;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
#wtoggle.sel #wzip #selzip {
|
||||
top: -.6em;
|
||||
padding: .4em .3em;
|
||||
}
|
||||
#barpos,
|
||||
#barbuf {
|
||||
position: absolute;
|
||||
@@ -463,7 +495,7 @@ input[type="checkbox"]:checked+label {
|
||||
}
|
||||
#tree {
|
||||
display: none;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 7em;
|
||||
@@ -598,7 +630,8 @@ input[type="checkbox"]:checked+label {
|
||||
#files td.min a {
|
||||
display: none;
|
||||
}
|
||||
#files tr.play td {
|
||||
#files tr.play td,
|
||||
#files tr.play div a {
|
||||
background: #fc4;
|
||||
border-color: transparent;
|
||||
color: #400;
|
||||
@@ -652,3 +685,173 @@ input[type="checkbox"]:checked+label {
|
||||
font-family: monospace, monospace;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
html.light {
|
||||
color: #333;
|
||||
background: #eee;
|
||||
text-shadow: none;
|
||||
}
|
||||
html.light #ops,
|
||||
html.light .opbox,
|
||||
html.light #srch_form {
|
||||
background: #f7f7f7;
|
||||
box-shadow: 0 0 .3em #ddd;
|
||||
border-color: #f7f7f7;
|
||||
}
|
||||
html.light #ops a.act {
|
||||
box-shadow: 0 .2em .2em #ccc;
|
||||
background: #f7f7f7;
|
||||
border-color: #07a;
|
||||
padding-top: .4em;
|
||||
}
|
||||
html.light #op_cfg h3 {
|
||||
border-color: #ccc;
|
||||
}
|
||||
html.light .tglbtn,
|
||||
html.light #tree > a + a {
|
||||
color: #666;
|
||||
background: #ddd;
|
||||
box-shadow: none;
|
||||
}
|
||||
html.light .tglbtn:hover,
|
||||
html.light #tree > a + a:hover {
|
||||
background: #caf;
|
||||
}
|
||||
html.light .tglbtn.on,
|
||||
html.light #tree > a + a.on {
|
||||
background: #4a0;
|
||||
color: #fff;
|
||||
}
|
||||
html.light #srv_info {
|
||||
color: #c83;
|
||||
text-shadow: 1px 1px 0 #fff;
|
||||
}
|
||||
html.light #srv_info span {
|
||||
color: #000;
|
||||
}
|
||||
html.light #treeul a+a {
|
||||
background: inherit;
|
||||
color: #06a;
|
||||
}
|
||||
html.light #treeul a.hl {
|
||||
background: #07a;
|
||||
color: #fff;
|
||||
}
|
||||
html.light #tree li {
|
||||
border-color: #ddd #fff #f7f7f7 #fff;
|
||||
}
|
||||
html.light #tree ul {
|
||||
border-color: #ccc;
|
||||
}
|
||||
html.light a,
|
||||
html.light #ops a,
|
||||
html.light #files tbody div a:last-child {
|
||||
color: #06a;
|
||||
}
|
||||
html.light #files tbody {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
html.light #files {
|
||||
box-shadow: 0 0 .3em #ccc;
|
||||
}
|
||||
html.light #files thead th {
|
||||
background: #eee;
|
||||
}
|
||||
html.light #files tr+tr td {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
html.light #files td {
|
||||
border-bottom: 1px solid #f7f7f7;
|
||||
}
|
||||
html.light #files tbody tr:last-child td {
|
||||
border-bottom: .2em solid #ccc;
|
||||
}
|
||||
html.light #files td:nth-child(2n) {
|
||||
color: #d38;
|
||||
}
|
||||
html.light #files tr:hover td {
|
||||
background: #fff;
|
||||
}
|
||||
html.light #files tbody a.play {
|
||||
color: #c0f;
|
||||
}
|
||||
html.light tr.play td {
|
||||
background: #fc5;
|
||||
}
|
||||
html.light tr.play a {
|
||||
color: #406;
|
||||
}
|
||||
html.light #files > thead > tr > th.min span {
|
||||
background: linear-gradient(90deg, rgba(68,68,68,0), rgba(68,68,68,0.2) 70%, rgba(68,68,68,0.5));
|
||||
}
|
||||
html.light #blocked {
|
||||
background: #eee;
|
||||
}
|
||||
html.light #blk_play a,
|
||||
html.light #blk_abrt a {
|
||||
background: #fff;
|
||||
box-shadow: 0 .2em .4em #ddd;
|
||||
}
|
||||
html.light #widget a {
|
||||
color: #fc5;
|
||||
}
|
||||
html.light #files tr.sel:hover td {
|
||||
background: #c37;
|
||||
}
|
||||
html.light #files tr.sel td {
|
||||
color: #fff;
|
||||
}
|
||||
html.light #files tr.sel a {
|
||||
color: #fff;
|
||||
}
|
||||
html.light input[type="checkbox"] + label {
|
||||
color: #333;
|
||||
}
|
||||
html.light .opview input[type="text"] {
|
||||
background: #fff;
|
||||
color: #333;
|
||||
box-shadow: 0 0 2px #888;
|
||||
border-color: #38d;
|
||||
}
|
||||
html.light #ops:hover #opdesc {
|
||||
background: #fff;
|
||||
box-shadow: 0 .3em 1em #ccc;
|
||||
}
|
||||
html.light #opdesc code {
|
||||
background: #060;
|
||||
color: #fff;
|
||||
}
|
||||
html.light #u2tab a>span,
|
||||
html.light #files td div span {
|
||||
color: #000;
|
||||
}
|
||||
html.light #path {
|
||||
background: #f7f7f7;
|
||||
text-shadow: none;
|
||||
box-shadow: 0 0 .3em #bbb;
|
||||
}
|
||||
html.light #path a {
|
||||
color: #333;
|
||||
}
|
||||
html.light #path a:not(:last-child)::after {
|
||||
border-color: #ccc;
|
||||
background: none;
|
||||
border-width: .1em .1em 0 0;
|
||||
margin: -.2em .3em -.2em -.3em;
|
||||
}
|
||||
html.light #path a:hover {
|
||||
background: none;
|
||||
color: #60a;
|
||||
}
|
||||
html.light #files tbody div a {
|
||||
color: #d38;
|
||||
}
|
||||
html.light #files a:hover,
|
||||
html.light #files tr.sel a:hover {
|
||||
color: #000;
|
||||
background: #fff;
|
||||
}
|
||||
@@ -13,8 +13,8 @@
|
||||
<body>
|
||||
<div id="ops">
|
||||
<a href="#" data-dest="" data-desc="close submenu">---</a>
|
||||
<a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.<br /><br /><code>foo bar</code> = must contain both foo and bar,<br /><code>foo -bar</code> = must contain foo but not bar,<br /><code>^yana .opus$</code> = must start with yana and have the opus extension">🔎</a>
|
||||
{%- if have_up2k_idx %}
|
||||
<a href="#" data-perm="read" data-dest="search" data-desc="search for files by attributes, path/name, music tags, or any combination of those.<br /><br /><code>foo bar</code> = must contain both foo and bar,<br /><code>foo -bar</code> = must contain foo but not bar,<br /><code>^yana .opus$</code> = must start with yana and have the opus extension">🔎</a>
|
||||
<a href="#" data-dest="up2k" data-desc="up2k: upload files (if you have write-access) or toggle into the search-mode and drag files onto the search button to see if they exist somewhere on the server">🚀</a>
|
||||
{%- else %}
|
||||
<a href="#" data-perm="write" data-dest="up2k" data-desc="up2k: upload files with resume support (close your browser and drop the same files in later)">🚀</a>
|
||||
@@ -39,12 +39,17 @@
|
||||
{%- include 'upload.html' %}
|
||||
|
||||
<div id="op_cfg" class="opview opbox">
|
||||
<h3>switches</h3>
|
||||
<div>
|
||||
<a id="tooltips" class="tglbtn" href="#">tooltips</a>
|
||||
<a id="lightmode" class="tglbtn" href="#">lightmode</a>
|
||||
</div>
|
||||
{%- if have_zip %}
|
||||
<h3>folder download</h3>
|
||||
<div id="arc_fmt"></div>
|
||||
{%- endif %}
|
||||
<h3>key notation</h3>
|
||||
<div id="key_notation"></div>
|
||||
<h3>tooltips</h3>
|
||||
<div>
|
||||
<a id="tooltips" class="tglbtn" href="#">enable</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 id="path">
|
||||
@@ -70,7 +75,7 @@
|
||||
<table id="files">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th name="lead"><span>c</span></th>
|
||||
<th name="href"><span>File Name</span></th>
|
||||
<th name="sz" sort="int"><span>Size</span></th>
|
||||
{%- for k in taglist %}
|
||||
@@ -110,7 +115,14 @@
|
||||
{%- endif %}
|
||||
|
||||
<div id="widget">
|
||||
<div id="wtoggle">♫</div>
|
||||
<div id="wtoggle">
|
||||
<span id="wzip">
|
||||
<a href="#" id="selall">sel.<br />all</a>
|
||||
<a href="#" id="selinv">sel.<br />inv.</a>
|
||||
<a href="#" id="selzip">zip</a>
|
||||
</span><a
|
||||
href="#" id="wtico">♫</a>
|
||||
</div>
|
||||
<div id="widgeti">
|
||||
<div id="pctl"><a href="#" id="bprev">⏮</a><a href="#" id="bplay">▶</a><a href="#" id="bnext">⏭</a></div>
|
||||
<canvas id="pvol" width="288" height="38"></canvas>
|
||||
|
||||
@@ -75,7 +75,7 @@ makeSortable(ebi('files'), mp.read_order.bind(mp));
|
||||
var widget = (function () {
|
||||
var ret = {};
|
||||
var widget = ebi('widget');
|
||||
var wtoggle = ebi('wtoggle');
|
||||
var wtico = ebi('wtico');
|
||||
var touchmode = false;
|
||||
var side_open = false;
|
||||
var was_paused = true;
|
||||
@@ -113,14 +113,7 @@ var widget = (function () {
|
||||
|
||||
return false;
|
||||
};
|
||||
if (window.Touch) {
|
||||
var touch_handler = function (e) {
|
||||
touchmode = true;
|
||||
return ret.toggle(e);
|
||||
};
|
||||
wtoggle.addEventListener('touchstart', touch_handler, false);
|
||||
}
|
||||
wtoggle.onclick = click_handler;
|
||||
wtico.onclick = click_handler;
|
||||
return ret;
|
||||
})();
|
||||
|
||||
@@ -321,10 +314,10 @@ function seek_au_sec(seek) {
|
||||
|
||||
mp.au.currentTime = seek;
|
||||
|
||||
// ogv.js breaks on .play() during playback
|
||||
if (mp.au === mp.au_native)
|
||||
// hack: ogv.js breaks on .play() during playback
|
||||
mp.au.play();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function song_skip(n) {
|
||||
@@ -336,7 +329,7 @@ function song_skip(n) {
|
||||
play(mp.order.indexOf(tid) + n);
|
||||
else
|
||||
play(mp.order[0]);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// hook up the widget buttons
|
||||
@@ -434,7 +427,7 @@ catch (ex) { }
|
||||
|
||||
|
||||
// plays the tid'th audio file on the page
|
||||
function play(tid, call_depth) {
|
||||
function play(tid, seek, call_depth) {
|
||||
if (mp.order.length == 0)
|
||||
return alert('no audio found wait what');
|
||||
|
||||
@@ -456,7 +449,7 @@ function play(tid, call_depth) {
|
||||
}
|
||||
|
||||
// ogv.js breaks on .play() unless directly user-triggered
|
||||
var hack_attempt_play = true;
|
||||
var attempt_play = true;
|
||||
|
||||
var url = mp.tracks[tid];
|
||||
if (need_ogv && /\.(ogg|opus)$/i.test(url)) {
|
||||
@@ -465,7 +458,7 @@ function play(tid, call_depth) {
|
||||
}
|
||||
else if (window['OGVPlayer']) {
|
||||
mp.au = mp.au_ogvjs = new OGVPlayer();
|
||||
hack_attempt_play = false;
|
||||
attempt_play = false;
|
||||
mp.au.addEventListener('error', evau_error, true);
|
||||
mp.au.addEventListener('progress', pbar.drawpos, false);
|
||||
widget.open();
|
||||
@@ -477,7 +470,7 @@ function play(tid, call_depth) {
|
||||
show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
|
||||
|
||||
import_js('/.cpr/deps/ogv.js', function () {
|
||||
play(tid, 1);
|
||||
play(tid, seek, 1);
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -500,26 +493,31 @@ function play(tid, call_depth) {
|
||||
setclass(oid, 'play act');
|
||||
var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
||||
for (var a = 0, aa = trs.length; a < aa; a++) {
|
||||
trs[a].className = trs[a].className.replace(/ *play */, "");
|
||||
clmod(trs[a], 'play');
|
||||
}
|
||||
ebi(oid).parentElement.parentElement.className += ' play';
|
||||
|
||||
try {
|
||||
if (hack_attempt_play)
|
||||
if (attempt_play)
|
||||
mp.au.play();
|
||||
|
||||
if (mp.au.paused)
|
||||
autoplay_blocked();
|
||||
autoplay_blocked(seek);
|
||||
else if (seek) {
|
||||
seek_au_sec(seek);
|
||||
}
|
||||
|
||||
var o = ebi(oid);
|
||||
o.setAttribute('id', 'thx_js');
|
||||
if (window.history && history.replaceState) {
|
||||
hist_replace(document.location.pathname + '#' + oid);
|
||||
if (!seek) {
|
||||
var o = ebi(oid);
|
||||
o.setAttribute('id', 'thx_js');
|
||||
if (window.history && history.replaceState) {
|
||||
hist_replace(document.location.pathname + '#' + oid);
|
||||
}
|
||||
else {
|
||||
document.location.hash = oid;
|
||||
}
|
||||
o.setAttribute('id', oid);
|
||||
}
|
||||
else {
|
||||
document.location.hash = oid;
|
||||
}
|
||||
o.setAttribute('id', oid);
|
||||
|
||||
pbar.drawbuf();
|
||||
return true;
|
||||
@@ -583,7 +581,7 @@ function unblocked() {
|
||||
|
||||
|
||||
// show ui to manually start playback of a linked song
|
||||
function autoplay_blocked() {
|
||||
function autoplay_blocked(seek) {
|
||||
show_modal(
|
||||
'<div id="blk_play"><a href="#" id="blk_go"></a></div>' +
|
||||
'<div id="blk_abrt"><a href="#" id="blk_na">Cancel<br />(show file list)</a></div>');
|
||||
@@ -599,6 +597,8 @@ function autoplay_blocked() {
|
||||
if (e) e.preventDefault();
|
||||
unblocked();
|
||||
mp.au.play();
|
||||
if (seek)
|
||||
seek_au_sec(seek);
|
||||
};
|
||||
na.onclick = unblocked;
|
||||
}
|
||||
@@ -607,8 +607,20 @@ function autoplay_blocked() {
|
||||
// autoplay linked track
|
||||
(function () {
|
||||
var v = location.hash;
|
||||
if (v && v.length == 12 && v.indexOf('#af-') === 0)
|
||||
play(v.slice(2));
|
||||
if (v && v.indexOf('#af-') === 0) {
|
||||
var id = v.slice(2).split('&');
|
||||
if (id[0].length != 10)
|
||||
return;
|
||||
|
||||
if (id.length == 1)
|
||||
return play(id[0]);
|
||||
|
||||
var m = /^[Tt=0]*([0-9]+[Mm:])?0*([0-9]+)[Ss]?$/.exec(id[1]);
|
||||
if (!m)
|
||||
return play(id[0]);
|
||||
|
||||
return play(id[0], parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -625,13 +637,16 @@ function tree_neigh(n) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
a += n;
|
||||
if (a < 0)
|
||||
a = links.length - 1;
|
||||
if (a >= links.length)
|
||||
a = 0;
|
||||
if (act == -1)
|
||||
return;
|
||||
|
||||
links[a].click();
|
||||
act += n;
|
||||
if (act < 0)
|
||||
act = links.length - 1;
|
||||
if (act >= links.length)
|
||||
act = 0;
|
||||
|
||||
links[act].click();
|
||||
}
|
||||
|
||||
|
||||
@@ -649,10 +664,13 @@ function tree_up() {
|
||||
|
||||
|
||||
document.onkeydown = function (e) {
|
||||
if (document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
||||
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
||||
return;
|
||||
|
||||
var k = e.code, pos = -1;
|
||||
if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || e.isComposing)
|
||||
return;
|
||||
|
||||
var k = (e.code + ''), pos = -1;
|
||||
if (k.indexOf('Digit') === 0)
|
||||
pos = parseInt(k.slice(-1)) * 0.1;
|
||||
|
||||
@@ -753,7 +771,7 @@ document.onkeydown = function (e) {
|
||||
clearTimeout(search_timeout);
|
||||
var now = new Date().getTime();
|
||||
if (now - search_in_progress > 30 * 1000)
|
||||
search_timeout = setTimeout(do_search, 100);
|
||||
search_timeout = setTimeout(do_search, 200);
|
||||
}
|
||||
|
||||
function do_search() {
|
||||
@@ -772,6 +790,7 @@ document.onkeydown = function (e) {
|
||||
// ebi('srch_q').textContent = JSON.stringify(params, null, 4);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', '/?srch', true);
|
||||
xhr.setRequestHeader('Content-Type', 'text/plain');
|
||||
xhr.onreadystatechange = xhr_search_results;
|
||||
xhr.ts = new Date().getTime();
|
||||
xhr.send(JSON.stringify(params));
|
||||
@@ -796,6 +815,8 @@ document.onkeydown = function (e) {
|
||||
var res = JSON.parse(this.responseText),
|
||||
tagord = res.tag_order;
|
||||
|
||||
sortfiles(res.hits);
|
||||
|
||||
var ofiles = ebi('files');
|
||||
if (ofiles.getAttribute('ts') > this.ts)
|
||||
return;
|
||||
@@ -814,7 +835,7 @@ document.onkeydown = function (e) {
|
||||
|
||||
var html = mk_files_header(tagord);
|
||||
html.push('<tbody>');
|
||||
html.push('<tr><td>-</td><td colspan="42"><a href="#" id="unsearch">close search results</a></td></tr>');
|
||||
html.push('<tr><td>-</td><td colspan="42"><a href="#" id="unsearch">! close search results</a></td></tr>');
|
||||
for (var a = 0; a < res.hits.length; a++) {
|
||||
var r = res.hits[a],
|
||||
ts = parseInt(r.ts),
|
||||
@@ -833,7 +854,7 @@ document.onkeydown = function (e) {
|
||||
v = r.tags[k] || "";
|
||||
|
||||
if (k == ".dur") {
|
||||
var sv = s2ms(v);
|
||||
var sv = v ? s2ms(v) : "";
|
||||
nodes[nodes.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
||||
continue;
|
||||
}
|
||||
@@ -867,6 +888,7 @@ document.onkeydown = function (e) {
|
||||
oldcfg = [];
|
||||
ebi('files').innerHTML = orig_html;
|
||||
orig_html = null;
|
||||
msel.render();
|
||||
reload_browser();
|
||||
}
|
||||
})();
|
||||
@@ -875,12 +897,16 @@ document.onkeydown = function (e) {
|
||||
var treectl = (function () {
|
||||
var treectl = {
|
||||
"hidden": false
|
||||
};
|
||||
var dyn = bcfg_get('dyntree', true);
|
||||
var treesz = icfg_get('treesz', 16);
|
||||
},
|
||||
entreed = false,
|
||||
fixedpos = false,
|
||||
prev_atop = null,
|
||||
prev_winh = null,
|
||||
dyn = bcfg_get('dyntree', true),
|
||||
treesz = icfg_get('treesz', 16);
|
||||
|
||||
treesz = Math.min(Math.max(treesz, 4), 50);
|
||||
console.log('treesz [' + treesz + ']');
|
||||
var entreed = false;
|
||||
|
||||
function entree(e) {
|
||||
ev(e);
|
||||
@@ -912,13 +938,43 @@ var treectl = (function () {
|
||||
if (!entreed || treectl.hidden)
|
||||
return;
|
||||
|
||||
var top = ebi('wrap').getBoundingClientRect().top;
|
||||
ebi('tree').style.top = Math.max(0, parseInt(top)) + 'px';
|
||||
var tree = ebi('tree'),
|
||||
wrap = ebi('wrap'),
|
||||
atop = wrap.getBoundingClientRect().top,
|
||||
winh = window.innerHeight;
|
||||
|
||||
if (atop === prev_atop && winh === prev_winh)
|
||||
return;
|
||||
|
||||
prev_atop = atop;
|
||||
prev_winh = winh;
|
||||
|
||||
if (fixedpos && atop >= 0) {
|
||||
tree.style.position = 'absolute';
|
||||
tree.style.bottom = '';
|
||||
fixedpos = false;
|
||||
}
|
||||
else if (!fixedpos && atop < 0) {
|
||||
tree.style.position = 'fixed';
|
||||
tree.style.height = 'auto';
|
||||
fixedpos = true;
|
||||
}
|
||||
|
||||
if (fixedpos) {
|
||||
tree.style.top = Math.max(0, parseInt(atop)) + 'px';
|
||||
}
|
||||
else {
|
||||
var top = Math.max(0, parseInt(wrap.offsetTop)),
|
||||
treeh = (winh - atop) - 4;
|
||||
|
||||
tree.style.top = top + 'px';
|
||||
tree.style.height = treeh < 10 ? '' : treeh + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
function periodic() {
|
||||
onscroll();
|
||||
setTimeout(periodic, document.visibilityState ? 200 : 5000);
|
||||
setTimeout(periodic, document.visibilityState ? 100 : 5000);
|
||||
}
|
||||
periodic();
|
||||
|
||||
@@ -995,8 +1051,6 @@ var treectl = (function () {
|
||||
var o = links[a].parentNode;
|
||||
if (!o.getElementsByTagName('li').length)
|
||||
o.innerHTML = html;
|
||||
//else
|
||||
// links[a].previousSibling.textContent = '-';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1085,32 +1139,8 @@ var treectl = (function () {
|
||||
}
|
||||
|
||||
ebi('srv_info').innerHTML = '<span>' + res.srvinf + '</span>';
|
||||
var nodes = res.dirs.concat(res.files),
|
||||
sopts = jread('fsort', []);
|
||||
|
||||
try {
|
||||
for (var a = sopts.length - 1; a >= 0; a--) {
|
||||
var name = sopts[a][0], rev = sopts[a][1], typ = sopts[a][2];
|
||||
if (name.indexOf('tags/') == -1) {
|
||||
nodes.sort(function (v1, v2) {
|
||||
if (!v1[name]) return -1 * rev;
|
||||
if (!v2[name]) return 1 * rev;
|
||||
return rev * (typ == 'int' ? (v1[name] - v2[name]) : (v1[name].localeCompare(v2[name])));
|
||||
});
|
||||
}
|
||||
else {
|
||||
name = name.slice(5);
|
||||
nodes.sort(function (v1, v2) {
|
||||
if (!v1.tags[name]) return -1 * rev;
|
||||
if (!v2.tags[name]) return 1 * rev;
|
||||
return rev * (typ == 'int' ? (v1.tags[name] - v2.tags[name]) : (v1.tags[name].localeCompare(v2.tags[name])));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
console.log("failed to apply sort config: " + ex);
|
||||
}
|
||||
var nodes = res.dirs.concat(res.files);
|
||||
nodes = sortfiles(nodes);
|
||||
|
||||
var top = this.top;
|
||||
var html = mk_files_header(res.taglist);
|
||||
@@ -1125,7 +1155,7 @@ var treectl = (function () {
|
||||
v = (r.tags || {})[k] || "";
|
||||
|
||||
if (k == ".dur") {
|
||||
var sv = s2ms(v);
|
||||
var sv = v ? s2ms(v) : "";
|
||||
ln[ln.length - 1] += '</td><td sortv="' + v + '">' + sv;
|
||||
continue;
|
||||
}
|
||||
@@ -1149,6 +1179,7 @@ var treectl = (function () {
|
||||
|
||||
filecols.set_style();
|
||||
mukey.render();
|
||||
msel.render();
|
||||
reload_tree();
|
||||
reload_browser();
|
||||
}
|
||||
@@ -1303,8 +1334,8 @@ function find_file_col(txt) {
|
||||
|
||||
function mk_files_header(taglist) {
|
||||
var html = [
|
||||
'<thead>',
|
||||
'<th></th>',
|
||||
'<thead><tr>',
|
||||
'<th name="lead"><span>c</span></th>',
|
||||
'<th name="href"><span>File Name</span></th>',
|
||||
'<th name="sz" sort="int"><span>Size</span></th>'
|
||||
];
|
||||
@@ -1322,7 +1353,7 @@ function mk_files_header(taglist) {
|
||||
html = html.concat([
|
||||
'<th name="ext"><span>T</span></th>',
|
||||
'<th name="ts"><span>Date</span></th>',
|
||||
'</thead>',
|
||||
'</tr></thead>',
|
||||
]);
|
||||
return html;
|
||||
}
|
||||
@@ -1356,13 +1387,13 @@ var filecols = (function () {
|
||||
continue;
|
||||
|
||||
var name = span[0].textContent,
|
||||
cls = '';
|
||||
cls = false;
|
||||
|
||||
if (has(hidden, name)) {
|
||||
ohidden.push(a);
|
||||
cls = ' min';
|
||||
cls = true;
|
||||
}
|
||||
ths[a].className = ths[a].className.replace(/ *min */, " ") + cls;
|
||||
clmod(ths[a], 'min', cls)
|
||||
}
|
||||
for (var a = 0; a < ncols; a++) {
|
||||
var cls = has(ohidden, a) ? 'min' : '';
|
||||
@@ -1407,8 +1438,8 @@ var filecols = (function () {
|
||||
if (!min)
|
||||
for (var a = 0, aa = rows.length; a < aa; a++) {
|
||||
var c = rows[a].cells[i];
|
||||
if (c)
|
||||
var v = c.textContent = s2ms(c.textContent);
|
||||
if (c && c.textContent)
|
||||
c.textContent = s2ms(c.textContent);
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
@@ -1479,8 +1510,11 @@ var mukey = (function () {
|
||||
}
|
||||
|
||||
function render() {
|
||||
var ci = find_file_col('Key'),
|
||||
i = ci[0],
|
||||
var ci = find_file_col('Key');
|
||||
if (!ci)
|
||||
return;
|
||||
|
||||
var i = ci[0],
|
||||
min = ci[1],
|
||||
rows = ebi('files').tBodies[0].rows;
|
||||
|
||||
@@ -1529,10 +1563,16 @@ var mukey = (function () {
|
||||
|
||||
|
||||
function addcrc() {
|
||||
var links = document.querySelectorAll('#files>tbody>tr>td:nth-child(2)>a');
|
||||
var links = document.querySelectorAll(
|
||||
'#files>tbody>tr>td:first-child+td>' + (
|
||||
ebi('unsearch') ? 'div>a:last-child' : 'a'));
|
||||
|
||||
for (var a = 0, aa = links.length; a < aa; a++)
|
||||
if (!links[a].getAttribute('id'))
|
||||
links[a].setAttribute('id', 'f-' + crc32(links[a].textContent));
|
||||
if (!links[a].getAttribute('id')) {
|
||||
var crc = crc32(links[a].textContent || links[a].innerText);
|
||||
crc = ('00000000' + crc).slice(-8);
|
||||
links[a].setAttribute('id', 'f-' + crc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1559,6 +1599,158 @@ function addcrc() {
|
||||
})();
|
||||
|
||||
|
||||
(function () {
|
||||
var light = bcfg_get('lightmode', false);
|
||||
|
||||
function freshen() {
|
||||
document.documentElement.setAttribute("class", light ? "light" : "");
|
||||
}
|
||||
|
||||
ebi('lightmode').onclick = function (e) {
|
||||
ev(e);
|
||||
light = !light;
|
||||
bcfg_set('lightmode', light);
|
||||
freshen();
|
||||
};
|
||||
|
||||
freshen();
|
||||
})();
|
||||
|
||||
|
||||
var arcfmt = (function () {
|
||||
if (!ebi('arc_fmt'))
|
||||
return { "render": function () { } };
|
||||
|
||||
var html = [],
|
||||
arcfmts = ["tar", "zip", "zip_dos", "zip_crc"],
|
||||
arcv = ["tar", "zip=utf8", "zip", "zip=crc"];
|
||||
|
||||
for (var a = 0; a < arcfmts.length; a++) {
|
||||
var k = arcfmts[a];
|
||||
html.push(
|
||||
'<span><input type="radio" name="arcfmt" value="' + k + '" id="arcfmt_' + k + '">' +
|
||||
'<label for="arcfmt_' + k + '">' + k + '</label></span>');
|
||||
}
|
||||
ebi('arc_fmt').innerHTML = html.join('\n');
|
||||
|
||||
var fmt = sread("arc_fmt") || "zip";
|
||||
ebi('arcfmt_' + fmt).checked = true;
|
||||
|
||||
function render() {
|
||||
var arg = arcv[arcfmts.indexOf(fmt)],
|
||||
tds = document.querySelectorAll('#files tbody td:first-child a');
|
||||
|
||||
for (var a = 0, aa = tds.length; a < aa; a++) {
|
||||
var o = tds[a], txt = o.textContent, href = o.getAttribute('href');
|
||||
if (txt != 'tar' && txt != 'zip')
|
||||
continue;
|
||||
|
||||
var ofs = href.lastIndexOf('?');
|
||||
if (ofs < 0)
|
||||
throw 'missing arg in url';
|
||||
|
||||
o.setAttribute("href", href.slice(0, ofs + 1) + arg);
|
||||
o.textContent = fmt.split('_')[0];
|
||||
}
|
||||
ebi('selzip').textContent = fmt.split('_')[0];
|
||||
ebi('selzip').setAttribute('fmt', arg);
|
||||
}
|
||||
|
||||
function try_render() {
|
||||
try {
|
||||
render();
|
||||
}
|
||||
catch (ex) {
|
||||
console.log("arcfmt failed: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
function change_fmt(e) {
|
||||
ev(e);
|
||||
fmt = this.getAttribute('value');
|
||||
swrite("arc_fmt", fmt);
|
||||
try_render();
|
||||
}
|
||||
|
||||
var o = document.querySelectorAll('#arc_fmt input');
|
||||
for (var a = 0; a < o.length; a++) {
|
||||
o[a].onchange = change_fmt;
|
||||
}
|
||||
|
||||
return {
|
||||
"render": try_render
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
var msel = (function () {
|
||||
function getsel() {
|
||||
var names = [];
|
||||
var links = document.querySelectorAll('#files tbody tr.sel td:nth-child(2) a');
|
||||
for (var a = 0, aa = links.length; a < aa; a++)
|
||||
names.push(links[a].getAttribute('href').replace(/\/$/, "").split('/').slice(-1));
|
||||
|
||||
return names;
|
||||
}
|
||||
function selui() {
|
||||
clmod(ebi('wtoggle'), 'sel', getsel().length);
|
||||
}
|
||||
function seltgl(e) {
|
||||
ev(e);
|
||||
var tr = this.parentNode;
|
||||
clmod(tr, 'sel', 't');
|
||||
selui();
|
||||
}
|
||||
function evsel(e, fun) {
|
||||
ev(e);
|
||||
var trs = document.querySelectorAll('#files tbody tr');
|
||||
for (var a = 0, aa = trs.length; a < aa; a++)
|
||||
clmod(trs[a], 'sel', fun);
|
||||
selui();
|
||||
}
|
||||
ebi('selall').onclick = function (e) {
|
||||
evsel(e, "add");
|
||||
};
|
||||
ebi('selinv').onclick = function (e) {
|
||||
evsel(e, "t");
|
||||
};
|
||||
ebi('selzip').onclick = function (e) {
|
||||
ev(e);
|
||||
var names = getsel();
|
||||
var arg = ebi('selzip').getAttribute('fmt');
|
||||
var txt = names.join('\n');
|
||||
var frm = document.createElement('form');
|
||||
frm.setAttribute('action', '?' + arg);
|
||||
frm.setAttribute('method', 'post');
|
||||
frm.setAttribute('target', '_blank');
|
||||
frm.setAttribute('enctype', 'multipart/form-data');
|
||||
frm.innerHTML = '<input name="act" value="zip" />' +
|
||||
'<textarea name="files" id="ziptxt"></textarea>';
|
||||
frm.style.display = 'none';
|
||||
|
||||
var oldform = document.querySelector('#widgeti>form');
|
||||
if (oldform)
|
||||
oldform.parentNode.removeChild(oldform);
|
||||
|
||||
ebi('widgeti').appendChild(frm);
|
||||
var obj = ebi('ziptxt');
|
||||
obj.value = txt;
|
||||
console.log(txt);
|
||||
frm.submit();
|
||||
};
|
||||
function render() {
|
||||
var tds = document.querySelectorAll('#files tbody td+td+td');
|
||||
for (var a = 0, aa = tds.length; a < aa; a++) {
|
||||
tds[a].onclick = seltgl;
|
||||
}
|
||||
arcfmt.render();
|
||||
}
|
||||
return {
|
||||
"render": render
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
function ev_row_tgl(e) {
|
||||
ev(e);
|
||||
filecols.toggle(this.parentElement.parentElement.getElementsByTagName('span')[0].textContent);
|
||||
@@ -1611,3 +1803,4 @@ function reload_browser(not_mp) {
|
||||
}
|
||||
reload_browser(true);
|
||||
mukey.render();
|
||||
msel.render();
|
||||
|
||||
55
copyparty/web/browser2.html
Normal file
55
copyparty/web/browser2.html
Normal file
@@ -0,0 +1,55 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ title }}</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{%- if srv_info %}
|
||||
<p><span>{{ srv_info }}</span></p>
|
||||
{%- endif %}
|
||||
|
||||
{%- if have_b_u %}
|
||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
||||
<input type="hidden" name="act" value="bput" />
|
||||
<input type="file" name="f" multiple /><br />
|
||||
<input type="submit" value="start upload" />
|
||||
</form>
|
||||
<br />
|
||||
{%- endif %}
|
||||
|
||||
{%- if logues[0] %}
|
||||
<div>{{ logues[0] }}</div><br />
|
||||
{%- endif %}
|
||||
|
||||
<table id="files">
|
||||
<thead>
|
||||
<tr>
|
||||
<th name="lead"><span>c</span></th>
|
||||
<th name="href"><span>File Name</span></th>
|
||||
<th name="sz" sort="int"><span>Size</span></th>
|
||||
<th name="ts"><span>Date</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td></td><td><a href="../{{ url_suf }}">parent folder</a></td><td>-</td><td>-</td></tr>
|
||||
|
||||
{%- for f in files %}
|
||||
<tr><td>{{ f.lead }}</td><td><a href="{{ f.href }}{{ url_suf }}">{{ f.name|e }}</a></td><td>{{ f.sz }}</td><td>{{ f.dt }}</td></tr>
|
||||
{%- endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{%- if logues[1] %}
|
||||
<div>{{ logues[1] }}</div><br />
|
||||
{%- endif %}
|
||||
|
||||
<h2><a href="{{ url_suf }}&h">control-panel</a></h2>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -138,16 +138,16 @@ var md_opt = {
|
||||
document.documentElement.setAttribute("class", dark ? "dark" : "");
|
||||
btn.innerHTML = "go " + (dark ? "light" : "dark");
|
||||
if (window.localStorage)
|
||||
localStorage.setItem('darkmode', dark ? 1 : 0);
|
||||
localStorage.setItem('lightmode', dark ? 0 : 1);
|
||||
};
|
||||
btn.onclick = toggle;
|
||||
if (window.localStorage && localStorage.getItem('darkmode') == 1)
|
||||
if (window.localStorage && localStorage.getItem('lightmode') != 1)
|
||||
toggle();
|
||||
})();
|
||||
|
||||
</script>
|
||||
<script src="/.cpr/util.js"></script>
|
||||
<script src="/.cpr/deps/marked.full.js"></script>
|
||||
<script src="/.cpr/deps/marked.js"></script>
|
||||
<script src="/.cpr/md.js"></script>
|
||||
{%- if edit %}
|
||||
<script src="/.cpr/md2.js"></script>
|
||||
|
||||
@@ -31,12 +31,12 @@ var md_opt = {
|
||||
|
||||
var lightswitch = (function () {
|
||||
var fun = function () {
|
||||
var dark = !!!document.documentElement.getAttribute("class");
|
||||
var dark = !document.documentElement.getAttribute("class");
|
||||
document.documentElement.setAttribute("class", dark ? "dark" : "");
|
||||
if (window.localStorage)
|
||||
localStorage.setItem('darkmode', dark ? 1 : 0);
|
||||
localStorage.setItem('lightmode', dark ? 0 : 1);
|
||||
};
|
||||
if (window.localStorage && localStorage.getItem('darkmode') == 1)
|
||||
if (window.localStorage && localStorage.getItem('lightmode') != 1)
|
||||
fun();
|
||||
|
||||
return fun;
|
||||
|
||||
@@ -16,20 +16,20 @@
|
||||
<h1>you can browse these:</h1>
|
||||
<ul>
|
||||
{% for mp in rvol %}
|
||||
<li><a href="/{{ mp }}">/{{ mp }}</a></li>
|
||||
<li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h1>you can upload to:</h1>
|
||||
<ul>
|
||||
{% for mp in wvol %}
|
||||
<li><a href="/{{ mp }}">/{{ mp }}</a></li>
|
||||
<li><a href="/{{ mp }}{{ url_suf }}">/{{ mp }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<h1>login for more:</h1>
|
||||
<ul>
|
||||
<form method="post" enctype="multipart/form-data" action="/">
|
||||
<form method="post" enctype="multipart/form-data" action="/{{ url_suf }}">
|
||||
<input type="hidden" name="act" value="login" />
|
||||
<input type="password" name="cppwd" />
|
||||
<input type="submit" value="Login" />
|
||||
@@ -38,7 +38,7 @@
|
||||
</div>
|
||||
<script>
|
||||
|
||||
if (window.localStorage && localStorage.getItem('darkmode') == 1)
|
||||
if (window.localStorage && localStorage.getItem('lightmode') != 1)
|
||||
document.documentElement.setAttribute("class", "dark");
|
||||
|
||||
</script>
|
||||
|
||||
@@ -31,6 +31,7 @@ catch (ex) {
|
||||
}
|
||||
catch (ex) { }
|
||||
}
|
||||
treectl.onscroll();
|
||||
|
||||
|
||||
function up2k_flagbus() {
|
||||
@@ -131,9 +132,285 @@ function up2k_flagbus() {
|
||||
return flag;
|
||||
}
|
||||
|
||||
|
||||
function U2pvis(act, btns) {
|
||||
this.act = act;
|
||||
this.ctr = { "ok": 0, "ng": 0, "bz": 0, "q": 0 };
|
||||
this.tab = [];
|
||||
this.head = 0;
|
||||
this.tail = -1;
|
||||
this.wsz = 3;
|
||||
|
||||
this.addfile = function (entry, sz) {
|
||||
this.tab.push({
|
||||
"hn": entry[0],
|
||||
"ht": entry[1],
|
||||
"hp": entry[2],
|
||||
"in": 'q',
|
||||
"nh": 0, //hashed
|
||||
"nd": 0, //done
|
||||
"cb": [], // bytes done in chunk
|
||||
"bt": sz, // bytes total
|
||||
"bd": 0, // bytes done
|
||||
"bd0": 0 // upload start
|
||||
});
|
||||
this.ctr["q"]++;
|
||||
this.drawcard("q");
|
||||
if (this.act == "q") {
|
||||
this.addrow(this.tab.length - 1);
|
||||
}
|
||||
if (this.act == "bz") {
|
||||
this.bzw();
|
||||
}
|
||||
};
|
||||
|
||||
this.is_act = function (card) {
|
||||
if (this.act == "done")
|
||||
return card == "ok" || card == "ng";
|
||||
|
||||
return this.act == card;
|
||||
}
|
||||
|
||||
this.seth = function (nfile, field, html) {
|
||||
var fo = this.tab[nfile];
|
||||
field = ['hn', 'ht', 'hp'][field];
|
||||
if (fo[field] === html)
|
||||
return;
|
||||
|
||||
fo[field] = html;
|
||||
if (!this.is_act(fo.in))
|
||||
return;
|
||||
|
||||
var obj = ebi('f{0}{1}'.format(nfile, field.slice(1)));
|
||||
obj.innerHTML = html;
|
||||
if (field == 'hp') {
|
||||
obj.style.color = '';
|
||||
obj.style.background = '';
|
||||
}
|
||||
};
|
||||
|
||||
this.setab = function (nfile, nblocks) {
|
||||
var t = [];
|
||||
for (var a = 0; a < nblocks; a++)
|
||||
t.push(0);
|
||||
|
||||
this.tab[nfile].cb = t;
|
||||
};
|
||||
|
||||
this.setat = function (nfile, blocktab) {
|
||||
this.tab[nfile].cb = blocktab;
|
||||
|
||||
var bd = 0;
|
||||
for (var a = 0; a < blocktab.length; a++)
|
||||
bd += blocktab[a];
|
||||
|
||||
this.tab[nfile].bd = bd;
|
||||
this.tab[nfile].bd0 = bd;
|
||||
};
|
||||
|
||||
this.perc = function (bd, bd0, sz, t0) {
|
||||
var td = new Date().getTime() - t0,
|
||||
p = bd * 100.0 / sz,
|
||||
nb = bd - bd0,
|
||||
spd = nb / (td / 1000),
|
||||
eta = (sz - bd) / spd;
|
||||
|
||||
return [p, s2ms(eta), spd / (1024 * 1024)];
|
||||
};
|
||||
|
||||
this.hashed = function (fobj) {
|
||||
var fo = this.tab[fobj.n];
|
||||
var nb = fo.bt * (++fo.nh / fo.cb.length);
|
||||
var p = this.perc(nb, 0, fobj.size, fobj.t1);
|
||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||
);
|
||||
if (!this.is_act(fo.in))
|
||||
return;
|
||||
|
||||
var obj = ebi('f{0}p'.format(fobj.n));
|
||||
obj.innerHTML = fo.hp;
|
||||
obj.style.color = '#fff';
|
||||
var o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
|
||||
obj.style.background = 'linear-gradient(90deg, #025, #06a ' + o1 + '%, #09d ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
||||
};
|
||||
|
||||
this.prog = function (fobj, nchunk, cbd) {
|
||||
var fo = this.tab[fobj.n];
|
||||
var delta = cbd - fo.cb[nchunk];
|
||||
fo.cb[nchunk] = cbd;
|
||||
fo.bd += delta;
|
||||
|
||||
var p = this.perc(fo.bd, fo.bd0, fo.bt, fobj.t3);
|
||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||
);
|
||||
|
||||
if (!this.is_act(fo.in))
|
||||
return;
|
||||
|
||||
var obj = ebi('f{0}p'.format(fobj.n));
|
||||
obj.innerHTML = fo.hp;
|
||||
obj.style.color = '#fff';
|
||||
var o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
|
||||
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
||||
};
|
||||
|
||||
this.move = function (nfile, newcat) {
|
||||
var fo = this.tab[nfile],
|
||||
oldcat = fo.in,
|
||||
bz_act = this.act == "bz";
|
||||
|
||||
if (oldcat == newcat) {
|
||||
throw 42;
|
||||
}
|
||||
|
||||
fo.in = newcat;
|
||||
this.ctr[oldcat]--;
|
||||
this.ctr[newcat]++;
|
||||
this.drawcard(oldcat);
|
||||
this.drawcard(newcat);
|
||||
if (this.is_act(newcat)) {
|
||||
this.tail++;
|
||||
if (!ebi('f' + nfile))
|
||||
this.addrow(nfile);
|
||||
}
|
||||
else if (this.is_act(oldcat)) {
|
||||
this.head++;
|
||||
if (!bz_act) {
|
||||
var tr = ebi("f" + nfile);
|
||||
tr.parentNode.removeChild(tr);
|
||||
}
|
||||
}
|
||||
if (bz_act) {
|
||||
this.bzw();
|
||||
}
|
||||
};
|
||||
|
||||
this.bzw_log = function (first, last) {
|
||||
console.log("first %d head %d tail %d last %d", first, this.head, this.tail, last);
|
||||
var trs = document.querySelectorAll('#u2tab>tbody>tr'), msg = [];
|
||||
for (var a = 0; a < trs.length; a++)
|
||||
msg.push(trs[a].getAttribute('id'));
|
||||
|
||||
console.log(msg.join(' '));
|
||||
}
|
||||
|
||||
this.bzw = function () {
|
||||
var first = document.querySelector('#u2tab>tbody>tr:first-child');
|
||||
if (!first)
|
||||
return;
|
||||
|
||||
var last = document.querySelector('#u2tab>tbody>tr:last-child');
|
||||
first = parseInt(first.getAttribute('id').slice(1));
|
||||
last = parseInt(last.getAttribute('id').slice(1));
|
||||
//this.bzw_log(first, last);
|
||||
|
||||
while (this.head - first > this.wsz) {
|
||||
var obj = ebi('f' + (first++));
|
||||
obj.parentNode.removeChild(obj);
|
||||
}
|
||||
while (last - this.tail < this.wsz && last < this.tab.length - 2) {
|
||||
var obj = ebi('f' + (++last));
|
||||
if (!obj)
|
||||
this.addrow(last);
|
||||
}
|
||||
//this.bzw_log(first, last);
|
||||
//console.log('--');
|
||||
};
|
||||
|
||||
this.drawcard = function (cat) {
|
||||
var cards = document.querySelectorAll('#u2cards>a>span');
|
||||
|
||||
if (cat == "q") {
|
||||
cards[4].innerHTML = this.ctr[cat];
|
||||
return;
|
||||
}
|
||||
if (cat == "bz") {
|
||||
cards[3].innerHTML = this.ctr[cat];
|
||||
return;
|
||||
}
|
||||
|
||||
cards[2].innerHTML = this.ctr["ok"] + this.ctr["ng"];
|
||||
|
||||
if (cat == "ng") {
|
||||
cards[1].innerHTML = this.ctr[cat];
|
||||
}
|
||||
if (cat == "ok") {
|
||||
cards[0].innerHTML = this.ctr[cat];
|
||||
}
|
||||
};
|
||||
|
||||
this.changecard = function (card) {
|
||||
this.act = card;
|
||||
var html = [];
|
||||
this.head = -1;
|
||||
this.tail = -1;
|
||||
for (var a = 0; a < this.tab.length; a++) {
|
||||
var rt = this.tab[a].in;
|
||||
if (this.is_act(rt)) {
|
||||
html.push(this.genrow(a, true));
|
||||
|
||||
this.tail = a;
|
||||
if (this.head == -1)
|
||||
this.head = a;
|
||||
}
|
||||
}
|
||||
if (this.head == -1) {
|
||||
this.head = this.tab.length;
|
||||
this.tail = this.head - 1;
|
||||
}
|
||||
if (card == "bz") {
|
||||
for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) {
|
||||
html.unshift(this.genrow(a, true).replace(/><td>/, "><td>a "));
|
||||
}
|
||||
for (var a = this.tail + 1; a <= this.tail + this.wsz && a < this.tab.length; a++) {
|
||||
html.push(this.genrow(a, true).replace(/><td>/, "><td>b "));
|
||||
}
|
||||
}
|
||||
ebi('u2tab').tBodies[0].innerHTML = html.join('\n');
|
||||
};
|
||||
|
||||
this.genrow = function (nfile, as_html) {
|
||||
var r = this.tab[nfile],
|
||||
td1 = '<td id="f' + nfile,
|
||||
td = '</td>' + td1,
|
||||
ret = td1 + 'n">' + r.hn +
|
||||
td + 't">' + r.ht +
|
||||
td + 'p" class="prog">' + r.hp + '</td>';
|
||||
|
||||
if (as_html)
|
||||
return '<tr id="f' + nfile + '">' + ret + '</tr>';
|
||||
|
||||
var obj = document.createElement('tr');
|
||||
obj.setAttribute('id', 'f' + nfile);
|
||||
obj.innerHTML = ret;
|
||||
return obj;
|
||||
};
|
||||
|
||||
this.addrow = function (nfile) {
|
||||
var tr = this.genrow(nfile);
|
||||
ebi('u2tab').tBodies[0].appendChild(tr);
|
||||
};
|
||||
|
||||
var that = this;
|
||||
btns = document.querySelectorAll(btns + '>a[act]');
|
||||
for (var a = 0; a < btns.length; a++) {
|
||||
btns[a].onclick = function (e) {
|
||||
ev(e);
|
||||
var newtab = this.getAttribute('act');
|
||||
for (var b = 0; b < btns.length; b++) {
|
||||
btns[b].className = (
|
||||
btns[b].getAttribute('act') == newtab) ? 'act' : '';
|
||||
}
|
||||
that.changecard(newtab);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function up2k_init(have_crypto) {
|
||||
//have_crypto = false;
|
||||
var need_filereader_cache = undefined;
|
||||
|
||||
// show modal message
|
||||
function showmodal(msg) {
|
||||
@@ -215,10 +492,6 @@ function up2k_init(have_crypto) {
|
||||
var flag_en = bcfg_get('flag_en', false);
|
||||
var fsearch = bcfg_get('fsearch', false);
|
||||
|
||||
var col_hashing = '#00bbff';
|
||||
var col_hashed = '#004466';
|
||||
var col_uploading = '#ffcc44';
|
||||
var col_uploaded = '#00bb00';
|
||||
var fdom_ctr = 0;
|
||||
var st = {
|
||||
"files": [],
|
||||
@@ -238,6 +511,8 @@ function up2k_init(have_crypto) {
|
||||
}
|
||||
};
|
||||
|
||||
var pvis = new U2pvis("bz", '#u2cards');
|
||||
|
||||
var bobslice = null;
|
||||
if (window.File)
|
||||
bobslice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
||||
@@ -278,18 +553,27 @@ function up2k_init(have_crypto) {
|
||||
}
|
||||
else files = e.target.files;
|
||||
|
||||
if (files.length == 0)
|
||||
if (!files || files.length == 0)
|
||||
return alert('no files selected??');
|
||||
|
||||
more_one_file();
|
||||
var bad_files = [];
|
||||
var good_files = [];
|
||||
var dirs = [];
|
||||
for (var a = 0; a < files.length; a++) {
|
||||
var fobj = files[a];
|
||||
if (is_itemlist) {
|
||||
if (fobj.kind !== 'file')
|
||||
continue;
|
||||
|
||||
try {
|
||||
var wi = fobj.webkitGetAsEntry();
|
||||
if (wi.isDirectory) {
|
||||
dirs.push(wi);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
fobj = fobj.getAsFile();
|
||||
}
|
||||
try {
|
||||
@@ -300,36 +584,93 @@ function up2k_init(have_crypto) {
|
||||
bad_files.push(fobj.name);
|
||||
continue;
|
||||
}
|
||||
good_files.push(fobj);
|
||||
good_files.push([fobj, fobj.name]);
|
||||
}
|
||||
if (dirs) {
|
||||
return read_dirs(null, [], dirs, good_files, bad_files);
|
||||
}
|
||||
}
|
||||
|
||||
function read_dirs(rd, pf, dirs, good, bad) {
|
||||
if (!dirs.length) {
|
||||
if (!pf.length)
|
||||
return gotallfiles(good, bad);
|
||||
|
||||
console.log("retry pf, " + pf.length);
|
||||
setTimeout(function () {
|
||||
read_dirs(rd, pf, dirs, good, bad);
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rd)
|
||||
rd = dirs[0].createReader();
|
||||
|
||||
rd.readEntries(function (ents) {
|
||||
var ngot = 0;
|
||||
ents.forEach(function (dn) {
|
||||
if (dn.isDirectory) {
|
||||
dirs.push(dn);
|
||||
}
|
||||
else {
|
||||
var name = dn.fullPath;
|
||||
if (name.indexOf('/') === 0)
|
||||
name = name.slice(1);
|
||||
|
||||
pf.push(name);
|
||||
dn.file(function (fobj) {
|
||||
var idx = pf.indexOf(name);
|
||||
pf.splice(idx, 1);
|
||||
try {
|
||||
if (fobj.size > 0) {
|
||||
good.push([fobj, name]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (ex) { }
|
||||
bad.push(name);
|
||||
});
|
||||
}
|
||||
ngot += 1;
|
||||
});
|
||||
// console.log("ngot: " + ngot);
|
||||
if (!ngot) {
|
||||
dirs.shift();
|
||||
rd = null;
|
||||
}
|
||||
return read_dirs(rd, pf, dirs, good, bad);
|
||||
});
|
||||
}
|
||||
|
||||
function gotallfiles(good_files, bad_files) {
|
||||
if (bad_files.length > 0) {
|
||||
var msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, files.length);
|
||||
for (var a = 0; a < bad_files.length; a++)
|
||||
var ntot = bad_files.length + good_files.length;
|
||||
var msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, ntot);
|
||||
for (var a = 0, aa = Math.min(20, bad_files.length); a < aa; a++)
|
||||
msg += '-- ' + bad_files[a] + '\n';
|
||||
|
||||
if (files.length - bad_files.length <= 1 && /(android)/i.test(navigator.userAgent))
|
||||
if (good_files.length - bad_files.length <= 1 && /(android)/i.test(navigator.userAgent))
|
||||
msg += '\nFirefox-Android has a bug which prevents selecting multiple files. Try selecting one file at a time. For more info, see firefox bug 1456557';
|
||||
|
||||
alert(msg);
|
||||
}
|
||||
|
||||
var msg = ['upload these ' + good_files.length + ' files?'];
|
||||
for (var a = 0; a < good_files.length; a++)
|
||||
msg.push(good_files[a].name);
|
||||
for (var a = 0, aa = Math.min(20, good_files.length); a < aa; a++)
|
||||
msg.push(good_files[a][1]);
|
||||
|
||||
if (ask_up && !fsearch && !confirm(msg.join('\n')))
|
||||
return;
|
||||
|
||||
for (var a = 0; a < good_files.length; a++) {
|
||||
var fobj = good_files[a];
|
||||
var fobj = good_files[a][0];
|
||||
var now = new Date().getTime();
|
||||
var lmod = fobj.lastModified || now;
|
||||
var entry = {
|
||||
"n": parseInt(st.files.length.toString()),
|
||||
"t0": now, // TODO remove probably
|
||||
"t0": now,
|
||||
"fobj": fobj,
|
||||
"name": fobj.name,
|
||||
"name": good_files[a][1],
|
||||
"size": fobj.size,
|
||||
"lmod": lmod / 1000,
|
||||
"purl": get_evpath(),
|
||||
@@ -346,11 +687,12 @@ function up2k_init(have_crypto) {
|
||||
if (skip)
|
||||
continue;
|
||||
|
||||
var tr = document.createElement('tr');
|
||||
tr.innerHTML = '<td id="f{0}n"></td><td id="f{0}t">hashing</td><td id="f{0}p" class="prog"></td>'.format(st.files.length);
|
||||
tr.getElementsByTagName('td')[0].innerHTML = fsearch ? entry.name : linksplit(esc(entry.purl + entry.name)).join(' ');
|
||||
ebi('u2tab').appendChild(tr);
|
||||
|
||||
pvis.addfile([
|
||||
fsearch ? esc(entry.name) : linksplit(
|
||||
esc(uricom_dec(entry.purl)[0] + entry.name)).join(' '),
|
||||
'📐 hash',
|
||||
''
|
||||
], fobj.size);
|
||||
st.files.push(entry);
|
||||
st.todo.hash.push(entry);
|
||||
}
|
||||
@@ -371,7 +713,10 @@ function up2k_init(have_crypto) {
|
||||
for (var a = 0; a < st.files.length; a++) {
|
||||
var t = st.files[a];
|
||||
if (t.done && t.name) {
|
||||
var tr = ebi('f{0}p'.format(t.n)).parentNode;
|
||||
var tr = ebi('f' + t.n);
|
||||
if (!tr)
|
||||
continue;
|
||||
|
||||
tr.parentNode.removeChild(tr);
|
||||
t.name = undefined;
|
||||
}
|
||||
@@ -394,7 +739,8 @@ function up2k_init(have_crypto) {
|
||||
function hashing_permitted() {
|
||||
if (multitask) {
|
||||
var ahead = st.bytes.hashed - st.bytes.uploaded;
|
||||
return ahead < 1024 * 1024 * 128;
|
||||
return ahead < 1024 * 1024 * 1024 * 4 &&
|
||||
st.todo.handshake.length + st.busy.handshake.length < 16;
|
||||
}
|
||||
return handshakes_permitted() && 0 ==
|
||||
st.todo.handshake.length +
|
||||
@@ -458,7 +804,7 @@ function up2k_init(have_crypto) {
|
||||
|
||||
if (st.todo.handshake.length > 0 &&
|
||||
st.busy.handshake.length == 0 && (
|
||||
st.todo.handshake[0].t3 || (
|
||||
st.todo.handshake[0].t4 || (
|
||||
handshakes_permitted() &&
|
||||
st.busy.upload.length < parallel_uploads
|
||||
)
|
||||
@@ -560,31 +906,6 @@ function up2k_init(have_crypto) {
|
||||
}
|
||||
}
|
||||
|
||||
function test_filereader_speed(segm_err) {
|
||||
var f = st.todo.hash[0].fobj,
|
||||
sz = Math.min(2, f.size),
|
||||
reader = new FileReader(),
|
||||
t0, ctr = 0;
|
||||
|
||||
var segm_next = function () {
|
||||
var t = new Date().getTime(),
|
||||
td = t - t0;
|
||||
|
||||
if (++ctr > 2) {
|
||||
need_filereader_cache = td > 50;
|
||||
st.busy.hash.pop();
|
||||
return;
|
||||
}
|
||||
t0 = t;
|
||||
reader.onload = segm_next;
|
||||
reader.onerror = segm_err;
|
||||
reader.readAsArrayBuffer(
|
||||
bobslice.call(f, 0, sz));
|
||||
};
|
||||
|
||||
segm_next();
|
||||
}
|
||||
|
||||
function ensure_rendered(func) {
|
||||
var hidden = false;
|
||||
var keys = ['hidden', 'msHidden', 'webkitHidden'];
|
||||
@@ -599,122 +920,88 @@ function up2k_init(have_crypto) {
|
||||
}
|
||||
|
||||
function exec_hash() {
|
||||
if (need_filereader_cache === undefined) {
|
||||
st.busy.hash.push(1);
|
||||
return test_filereader_speed(segm_err);
|
||||
}
|
||||
|
||||
var t = st.todo.hash.shift();
|
||||
st.busy.hash.push(t);
|
||||
st.bytes.hashed += t.size;
|
||||
t.bytes_uploaded = 0;
|
||||
t.t1 = new Date().getTime();
|
||||
|
||||
var nchunk = 0;
|
||||
var chunksize = get_chunksize(t.size);
|
||||
var nchunks = Math.ceil(t.size / chunksize);
|
||||
var bpend = 0,
|
||||
nchunk = 0,
|
||||
chunksize = get_chunksize(t.size),
|
||||
nchunks = Math.ceil(t.size / chunksize),
|
||||
hashtab = {};
|
||||
|
||||
// android-chrome has 180ms latency on FileReader calls,
|
||||
// detect this and do 32MB at a time
|
||||
var cache_buf = undefined,
|
||||
cache_ofs = 0,
|
||||
subchunks = 2;
|
||||
|
||||
while (subchunks * chunksize <= 32 * 1024 * 1024)
|
||||
subchunks++;
|
||||
|
||||
subchunks--;
|
||||
if (!need_filereader_cache)
|
||||
subchunks = 1;
|
||||
|
||||
var pb_html = '';
|
||||
var pb_perc = 99.9 / nchunks;
|
||||
for (var a = 0; a < nchunks; a++)
|
||||
pb_html += '<div id="f{0}p{1}" style="width:{2}%"><div></div></div>'.format(
|
||||
t.n, a, pb_perc);
|
||||
|
||||
ebi('f{0}p'.format(t.n)).innerHTML = pb_html;
|
||||
|
||||
var reader = new FileReader();
|
||||
pvis.setab(t.n, nchunks);
|
||||
pvis.move(t.n, 'bz');
|
||||
|
||||
var segm_next = function () {
|
||||
if (cache_buf) {
|
||||
return hash_calc();
|
||||
}
|
||||
reader.onload = segm_load;
|
||||
reader.onerror = segm_err;
|
||||
if (nchunk >= nchunks || (bpend > chunksize && bpend >= 32 * 1024 * 1024))
|
||||
return false;
|
||||
|
||||
var reader = new FileReader(),
|
||||
nch = nchunk++,
|
||||
car = nch * chunksize,
|
||||
cdr = car + chunksize;
|
||||
|
||||
var car = nchunk * chunksize;
|
||||
var cdr = car + chunksize * subchunks;
|
||||
if (cdr >= t.size)
|
||||
cdr = t.size;
|
||||
|
||||
bpend += cdr - car;
|
||||
|
||||
reader.onload = function (e) {
|
||||
hash_calc(nch, e.target.result);
|
||||
};
|
||||
reader.onerror = segm_err;
|
||||
reader.readAsArrayBuffer(
|
||||
bobslice.call(t.fobj, car, cdr));
|
||||
|
||||
prog(t.n, nchunk, col_hashing);
|
||||
return true;
|
||||
};
|
||||
|
||||
var segm_load = function (e) {
|
||||
cache_buf = e.target.result;
|
||||
cache_ofs = 0;
|
||||
hash_calc();
|
||||
};
|
||||
var hash_calc = function (nch, buf) {
|
||||
while (segm_next());
|
||||
|
||||
var hash_calc = function () {
|
||||
var buf = cache_buf;
|
||||
if (chunksize >= buf.byteLength)
|
||||
cache_buf = undefined;
|
||||
else {
|
||||
var ofs = cache_ofs;
|
||||
var ofs2 = ofs + Math.min(chunksize, cache_buf.byteLength - cache_ofs);
|
||||
cache_ofs = ofs2;
|
||||
buf = new Uint8Array(cache_buf).subarray(ofs, ofs2);
|
||||
if (ofs2 >= cache_buf.byteLength)
|
||||
cache_buf = undefined;
|
||||
}
|
||||
var hash_done = function (hashbuf) {
|
||||
var hslice = new Uint8Array(hashbuf).subarray(0, 32);
|
||||
var b64str = buf2b64(hslice).replace(/=$/, '');
|
||||
hashtab[nch] = b64str;
|
||||
t.hash.push(nch);
|
||||
pvis.hashed(t);
|
||||
|
||||
var func = function () {
|
||||
if (have_crypto)
|
||||
crypto.subtle.digest('SHA-512', buf).then(hash_done);
|
||||
else {
|
||||
var hasher = new asmCrypto.Sha512();
|
||||
hasher.process(new Uint8Array(buf));
|
||||
hasher.finish();
|
||||
hash_done(hasher.result);
|
||||
bpend -= buf.byteLength;
|
||||
if (t.hash.length < nchunks) {
|
||||
return segm_next();
|
||||
}
|
||||
t.hash = [];
|
||||
for (var a = 0; a < nchunks; a++) {
|
||||
t.hash.push(hashtab[a]);
|
||||
}
|
||||
|
||||
t.t2 = new Date().getTime();
|
||||
if (t.n == 0 && window.location.hash == '#dbg') {
|
||||
var spd = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
|
||||
alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
|
||||
}
|
||||
|
||||
pvis.seth(t.n, 2, 'hashing done');
|
||||
pvis.seth(t.n, 1, '📦 wait');
|
||||
st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
|
||||
st.todo.handshake.push(t);
|
||||
};
|
||||
|
||||
if (cache_buf)
|
||||
ensure_rendered(func);
|
||||
else
|
||||
func();
|
||||
};
|
||||
|
||||
var hash_done = function (hashbuf) {
|
||||
var hslice = new Uint8Array(hashbuf).subarray(0, 32);
|
||||
var b64str = buf2b64(hslice).replace(/=$/, '');
|
||||
t.hash.push(b64str);
|
||||
|
||||
prog(t.n, nchunk, col_hashed);
|
||||
if (++nchunk < nchunks) {
|
||||
prog(t.n, nchunk, col_hashing);
|
||||
return segm_next();
|
||||
if (have_crypto)
|
||||
crypto.subtle.digest('SHA-512', buf).then(hash_done);
|
||||
else {
|
||||
var hasher = new asmCrypto.Sha512();
|
||||
hasher.process(new Uint8Array(buf));
|
||||
hasher.finish();
|
||||
hash_done(hasher.result);
|
||||
}
|
||||
|
||||
t.t2 = new Date().getTime();
|
||||
if (t.n == 0 && window.location.hash == '#dbg') {
|
||||
var spd = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
|
||||
alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
|
||||
}
|
||||
|
||||
ebi('f{0}t'.format(t.n)).innerHTML = 'connecting';
|
||||
st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
|
||||
st.todo.handshake.push(t);
|
||||
};
|
||||
|
||||
var segm_err = function () {
|
||||
alert('y o u b r o k e i t\n\n(was that a folder? just files please)');
|
||||
alert('y o u b r o k e i t\nerror: ' + reader.error);
|
||||
};
|
||||
|
||||
segm_next();
|
||||
@@ -744,7 +1031,7 @@ function up2k_init(have_crypto) {
|
||||
else {
|
||||
smsg = 'found';
|
||||
var hit = response.hits[0],
|
||||
msg = linksplit(hit.rp).join(''),
|
||||
msg = linksplit(esc(hit.rp)).join(''),
|
||||
tr = unix2iso(hit.ts),
|
||||
tu = unix2iso(t.lmod),
|
||||
diff = parseInt(t.lmod) - parseInt(hit.ts),
|
||||
@@ -753,8 +1040,9 @@ function up2k_init(have_crypto) {
|
||||
|
||||
msg += '<br /><small>' + tr + ' (srv), ' + tu + ' (You), ' + sdiff + '</span></span>';
|
||||
}
|
||||
ebi('f{0}p'.format(t.n)).innerHTML = msg;
|
||||
ebi('f{0}t'.format(t.n)).innerHTML = smsg;
|
||||
pvis.seth(t.n, 2, msg);
|
||||
pvis.seth(t.n, 1, smsg);
|
||||
pvis.move(t.n, smsg == '404' ? 'ng' : 'ok');
|
||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
||||
st.bytes.uploaded += t.size;
|
||||
t.done = true;
|
||||
@@ -765,7 +1053,15 @@ function up2k_init(have_crypto) {
|
||||
if (response.name !== t.name) {
|
||||
// file exists; server renamed us
|
||||
t.name = response.name;
|
||||
ebi('f{0}n'.format(t.n)).innerHTML = linksplit(esc(t.purl + t.name)).join(' ');
|
||||
pvis.seth(t.n, 0, linksplit(esc(t.purl + t.name)).join(' '));
|
||||
}
|
||||
|
||||
var chunksize = get_chunksize(t.size);
|
||||
var cdr_idx = Math.ceil(t.size / chunksize) - 1;
|
||||
var cdr_sz = (t.size % chunksize) || chunksize;
|
||||
var cbd = [];
|
||||
for (var a = 0; a <= cdr_idx; a++) {
|
||||
cbd.push(a == cdr_idx ? cdr_sz : chunksize);
|
||||
}
|
||||
|
||||
t.postlist = [];
|
||||
@@ -778,10 +1074,11 @@ function up2k_init(have_crypto) {
|
||||
missing[a], JSON.stringify(t)));
|
||||
|
||||
t.postlist.push(idx);
|
||||
cbd[idx] = 0;
|
||||
}
|
||||
for (var a = 0; a < t.hash.length; a++)
|
||||
prog(t.n, a, (t.postlist.indexOf(a) == -1)
|
||||
? col_uploaded : col_hashed);
|
||||
|
||||
pvis.setat(t.n, cbd);
|
||||
pvis.prog(t, 0, cbd[0]);
|
||||
|
||||
var done = true;
|
||||
var msg = '🎷🐛';
|
||||
@@ -795,49 +1092,56 @@ function up2k_init(have_crypto) {
|
||||
msg = 'uploading';
|
||||
done = false;
|
||||
}
|
||||
ebi('f{0}t'.format(t.n)).innerHTML = msg;
|
||||
pvis.seth(t.n, 1, msg);
|
||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
||||
|
||||
if (done) {
|
||||
t.done = true;
|
||||
st.bytes.uploaded += t.size - t.bytes_uploaded;
|
||||
var spd1 = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
|
||||
var spd2 = (t.size / ((t.t3 - t.t2) / 1000.)) / (1024 * 1024.);
|
||||
ebi('f{0}p'.format(t.n)).innerHTML = 'hash {0}, up {1} MB/s'.format(
|
||||
spd1.toFixed(2), spd2.toFixed(2));
|
||||
var spd2 = (t.size / ((t.t4 - t.t3) / 1000.)) / (1024 * 1024.);
|
||||
pvis.seth(t.n, 2, 'hash {0}, up {1} MB/s'.format(
|
||||
spd1.toFixed(2), spd2.toFixed(2)));
|
||||
pvis.move(t.n, 'ok');
|
||||
}
|
||||
else t.t3 = undefined;
|
||||
else t.t4 = undefined;
|
||||
|
||||
tasker();
|
||||
}
|
||||
else {
|
||||
var err = "";
|
||||
var rsp = (xhr.responseText + '');
|
||||
var err = "",
|
||||
rsp = (xhr.responseText + ''),
|
||||
ofs = rsp.lastIndexOf('\nURL: ');
|
||||
|
||||
if (ofs !== -1)
|
||||
rsp = rsp.slice(0, ofs);
|
||||
|
||||
if (rsp.indexOf('<pre>') === 0)
|
||||
rsp = rsp.slice(5);
|
||||
|
||||
st.bytes.uploaded += t.size;
|
||||
if (rsp.indexOf('partial upload exists') !== -1 ||
|
||||
rsp.indexOf('file already exists') !== -1) {
|
||||
err = rsp;
|
||||
var ofs = err.lastIndexOf(' : ');
|
||||
if (ofs > 0)
|
||||
err = err.slice(0, ofs);
|
||||
|
||||
ofs = err.indexOf('\n/');
|
||||
if (ofs !== -1) {
|
||||
err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2, -1)).join(' ');
|
||||
err = err.slice(0, ofs + 1) + linksplit(err.slice(ofs + 2)).join(' ');
|
||||
}
|
||||
}
|
||||
if (err != "") {
|
||||
ebi('f{0}t'.format(t.n)).innerHTML = "ERROR";
|
||||
ebi('f{0}p'.format(t.n)).innerHTML = err;
|
||||
pvis.seth(t.n, 1, "ERROR");
|
||||
pvis.seth(t.n, 2, err);
|
||||
pvis.move(t.n, 'ng');
|
||||
|
||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
||||
tasker();
|
||||
return;
|
||||
}
|
||||
alert("server broke (error {0}):\n\"{1}\"\n".format(
|
||||
xhr.status,
|
||||
(xhr.response && xhr.response.err) ||
|
||||
(xhr.responseText && xhr.responseText) ||
|
||||
"no further information"));
|
||||
alert("server broke; hs-err {0} on file [{1}]:\n".format(
|
||||
xhr.status, t.name) + (
|
||||
(xhr.response && xhr.response.err) ||
|
||||
(xhr.responseText && xhr.responseText) ||
|
||||
"no further information"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -866,8 +1170,10 @@ function up2k_init(have_crypto) {
|
||||
|
||||
var npart = upt.npart;
|
||||
var t = st.files[upt.nfile];
|
||||
if (!t.t3)
|
||||
t.t3 = new Date().getTime();
|
||||
|
||||
prog(t.n, npart, col_uploading);
|
||||
pvis.seth(t.n, 1, "🚀 send");
|
||||
|
||||
var chunksize = get_chunksize(t.size);
|
||||
var car = npart * chunksize;
|
||||
@@ -875,68 +1181,40 @@ function up2k_init(have_crypto) {
|
||||
if (cdr >= t.size)
|
||||
cdr = t.size;
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
reader.onerror = function () {
|
||||
alert('y o u b r o k e i t\n\n(was that a folder? just files please)');
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.upload.onprogress = function (xev) {
|
||||
pvis.prog(t, npart, xev.loaded);
|
||||
};
|
||||
|
||||
reader.onload = function (e) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.upload.onprogress = function (xev) {
|
||||
var perc = xev.loaded / (cdr - car) * 100;
|
||||
prog(t.n, npart, '', perc);
|
||||
};
|
||||
xhr.onload = function (xev) {
|
||||
if (xhr.status == 200) {
|
||||
prog(t.n, npart, col_uploaded);
|
||||
st.bytes.uploaded += cdr - car;
|
||||
t.bytes_uploaded += cdr - car;
|
||||
st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
|
||||
t.postlist.splice(t.postlist.indexOf(npart), 1);
|
||||
if (t.postlist.length == 0) {
|
||||
t.t3 = new Date().getTime();
|
||||
ebi('f{0}t'.format(t.n)).innerHTML = 'verifying';
|
||||
st.todo.handshake.unshift(t);
|
||||
}
|
||||
tasker();
|
||||
xhr.onload = function (xev) {
|
||||
if (xhr.status == 200) {
|
||||
pvis.prog(t, npart, cdr - car);
|
||||
st.bytes.uploaded += cdr - car;
|
||||
t.bytes_uploaded += cdr - car;
|
||||
st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
|
||||
t.postlist.splice(t.postlist.indexOf(npart), 1);
|
||||
if (t.postlist.length == 0) {
|
||||
t.t4 = new Date().getTime();
|
||||
pvis.seth(t.n, 1, 'verifying');
|
||||
st.todo.handshake.unshift(t);
|
||||
}
|
||||
else
|
||||
alert("server broke (error {0}):\n\"{1}\"\n".format(
|
||||
xhr.status,
|
||||
tasker();
|
||||
}
|
||||
else
|
||||
alert("server broke; cu-err {0} on file [{1}]:\n".format(
|
||||
xhr.status, t.name) + (
|
||||
(xhr.response && xhr.response.err) ||
|
||||
(xhr.responseText && xhr.responseText) ||
|
||||
"no further information"));
|
||||
};
|
||||
xhr.open('POST', t.purl + 'chunkpit.php', true);
|
||||
//xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart].substr(1) + "x");
|
||||
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
||||
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
||||
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
|
||||
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
||||
xhr.responseType = 'text';
|
||||
xhr.send(e.target.result);
|
||||
};
|
||||
xhr.open('POST', t.purl + 'chunkpit.php', true);
|
||||
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
||||
xhr.setRequestHeader("X-Up2k-Wark", t.wark);
|
||||
xhr.setRequestHeader('Content-Type', 'application/octet-stream');
|
||||
if (xhr.overrideMimeType)
|
||||
xhr.overrideMimeType('Content-Type', 'application/octet-stream');
|
||||
|
||||
reader.readAsArrayBuffer(bobslice.call(t.fobj, car, cdr));
|
||||
}
|
||||
|
||||
/////
|
||||
////
|
||||
/// progress bar
|
||||
//
|
||||
|
||||
function prog(nfile, nchunk, color, percent) {
|
||||
var n1 = ebi('f{0}p{1}'.format(nfile, nchunk));
|
||||
var n2 = n1.getElementsByTagName('div')[0];
|
||||
if (percent === undefined) {
|
||||
n1.style.background = color;
|
||||
n2.style.display = 'none';
|
||||
}
|
||||
else {
|
||||
n2.style.width = percent + '%';
|
||||
n2.style.display = 'block';
|
||||
}
|
||||
xhr.responseType = 'text';
|
||||
xhr.send(bobslice.call(t.fobj, car, cdr));
|
||||
}
|
||||
|
||||
/////
|
||||
@@ -957,6 +1235,7 @@ function up2k_init(have_crypto) {
|
||||
if (btn.parentNode !== parent) {
|
||||
parent.appendChild(btn);
|
||||
ebi('u2conf').setAttribute('class', wide ? 'has_btn' : '');
|
||||
ebi('u2cards').setAttribute('class', wide ? 'w' : '');
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', onresize);
|
||||
@@ -992,14 +1271,14 @@ function up2k_init(have_crypto) {
|
||||
|
||||
var obj = ebi('nthread');
|
||||
if (dir.target) {
|
||||
obj.style.background = '#922';
|
||||
clmod(obj, 'err', 1);
|
||||
var v = Math.floor(parseInt(obj.value));
|
||||
if (v < 1 || v > 8 || v !== v)
|
||||
return;
|
||||
|
||||
parallel_uploads = v;
|
||||
swrite('nthread', v);
|
||||
obj.style.background = '#444';
|
||||
clmod(obj, 'err');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,11 @@
|
||||
margin: -1.5em 0;
|
||||
padding: .8em 0;
|
||||
width: 100%;
|
||||
max-width: 12em;
|
||||
display: inline-block;
|
||||
}
|
||||
#u2conf #u2btn_cw {
|
||||
text-align: right;
|
||||
}
|
||||
#u2notbtn {
|
||||
display: none;
|
||||
@@ -72,6 +77,7 @@
|
||||
}
|
||||
#u2tab td:nth-child(2) {
|
||||
width: 5em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#u2tab td:nth-child(3) {
|
||||
width: 40%;
|
||||
@@ -83,12 +89,41 @@
|
||||
#u2tab tr+tr:hover td {
|
||||
background: #222;
|
||||
}
|
||||
#u2cards {
|
||||
margin: 2.5em auto -2.5em auto;
|
||||
text-align: center;
|
||||
}
|
||||
#u2cards.w {
|
||||
width: 45em;
|
||||
text-align: left;
|
||||
}
|
||||
#u2cards a {
|
||||
padding: .2em 1em;
|
||||
border: 1px solid #777;
|
||||
border-width: 0 0 1px 0;
|
||||
background: linear-gradient(to bottom, #333, #222);
|
||||
}
|
||||
#u2cards a:first-child {
|
||||
border-radius: .4em 0 0 0;
|
||||
}
|
||||
#u2cards a:last-child {
|
||||
border-radius: 0 .4em 0 0;
|
||||
}
|
||||
#u2cards a.act {
|
||||
border-width: 1px 1px 0 1px;
|
||||
border-radius: .3em .3em 0 0;
|
||||
margin-left: -1px;
|
||||
background: transparent;
|
||||
}
|
||||
#u2cards span {
|
||||
color: #fff;
|
||||
}
|
||||
#u2conf {
|
||||
margin: 1em auto;
|
||||
width: 30em;
|
||||
}
|
||||
#u2conf.has_btn {
|
||||
width: 46em;
|
||||
width: 48em;
|
||||
}
|
||||
#u2conf * {
|
||||
text-align: center;
|
||||
@@ -106,6 +141,9 @@
|
||||
font-size: 1.2em;
|
||||
padding: .15em 0;
|
||||
}
|
||||
#u2conf .txtbox.err {
|
||||
background: #922;
|
||||
}
|
||||
#u2conf a {
|
||||
color: #fff;
|
||||
background: #c38;
|
||||
@@ -193,24 +231,6 @@
|
||||
.prog {
|
||||
font-family: monospace;
|
||||
}
|
||||
.prog>div {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 1.1em;
|
||||
margin-bottom: -.15em;
|
||||
box-shadow: -1px -1px 0 inset rgba(255,255,255,0.1);
|
||||
}
|
||||
.prog>div>div {
|
||||
width: 0%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: #0a0;
|
||||
}
|
||||
#u2tab a>span {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
@@ -221,3 +241,35 @@
|
||||
float: right;
|
||||
margin-bottom: -.3em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
html.light #u2btn {
|
||||
box-shadow: .4em .4em 0 #ccc;
|
||||
}
|
||||
html.light #u2cards span {
|
||||
color: #000;
|
||||
}
|
||||
html.light #u2cards a {
|
||||
background: linear-gradient(to bottom, #eee, #fff);
|
||||
}
|
||||
html.light #u2cards a.act {
|
||||
background: inherit;
|
||||
}
|
||||
html.light #u2conf .txtbox {
|
||||
background: #fff;
|
||||
color: #444;
|
||||
}
|
||||
html.light #u2conf .txtbox.err {
|
||||
background: #f96;
|
||||
color: #300;
|
||||
}
|
||||
html.light #u2cdesc {
|
||||
background: #fff;
|
||||
border: none;
|
||||
}
|
||||
html.light #op_up2k.srch #u2btn {
|
||||
border-color: #a80;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
<div id="op_bup" class="opview opbox act">
|
||||
<div id="u2err"></div>
|
||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
||||
<input type="hidden" name="act" value="bput" />
|
||||
<input type="file" name="f" multiple><br />
|
||||
<input type="submit" value="start upload">
|
||||
@@ -9,7 +9,7 @@
|
||||
</div>
|
||||
|
||||
<div id="op_mkdir" class="opview opbox act">
|
||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
||||
<input type="hidden" name="act" value="mkdir" />
|
||||
<input type="text" name="name" size="30">
|
||||
<input type="submit" value="mkdir">
|
||||
@@ -17,15 +17,15 @@
|
||||
</div>
|
||||
|
||||
<div id="op_new_md" class="opview opbox">
|
||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<form method="post" enctype="multipart/form-data" accept-charset="utf-8" action="{{ url_suf }}">
|
||||
<input type="hidden" name="act" value="new_md" />
|
||||
<input type="text" name="name" size="30">
|
||||
<input type="submit" value="create doc">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div id="op_msg" class="opview opbox">
|
||||
<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8">
|
||||
<div id="op_msg" class="opview opbox act">
|
||||
<form method="post" enctype="application/x-www-form-urlencoded" accept-charset="utf-8" action="{{ url_suf }}">
|
||||
<input type="text" name="msg" size="30">
|
||||
<input type="submit" value="send msg">
|
||||
</form>
|
||||
@@ -73,17 +73,29 @@
|
||||
<div id="u2btn_ct">
|
||||
<div id="u2btn">
|
||||
<span id="u2bm"></span><br />
|
||||
drop files here<br />
|
||||
drag/drop files<br />
|
||||
and folders here<br />
|
||||
(or click me)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="u2cards">
|
||||
<a href="#" act="ok">ok <span>0</span></a><a
|
||||
href="#" act="ng">ng <span>0</span></a><a
|
||||
href="#" act="done">done <span>0</span></a><a
|
||||
href="#" act="bz" class="act">busy <span>0</span></a><a
|
||||
href="#" act="q">que <span>0</span></a>
|
||||
</div>
|
||||
|
||||
<table id="u2tab">
|
||||
<tr>
|
||||
<td>filename</td>
|
||||
<td>status</td>
|
||||
<td>progress<a href="#" id="u2cleanup">cleanup</a></td>
|
||||
</tr>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>filename</td>
|
||||
<td>status</td>
|
||||
<td>progress<a href="#" id="u2cleanup">cleanup</a></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
|
||||
<p id="u2foot"></p>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
if (!window['console'])
|
||||
window['console'] = {
|
||||
"log": function (msg) { }
|
||||
};
|
||||
|
||||
|
||||
var clickev = window.Touch ? 'touchstart' : 'click';
|
||||
|
||||
|
||||
// error handler for mobile devices
|
||||
function hcroak(msg) {
|
||||
document.body.innerHTML = msg;
|
||||
@@ -110,7 +119,85 @@ function crc32(str) {
|
||||
crc = (crc >>> 8) ^ crctab[(crc ^ str.charCodeAt(i)) & 0xFF];
|
||||
}
|
||||
return ((crc ^ (-1)) >>> 0).toString(16);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function clmod(obj, cls, add) {
|
||||
var re = new RegExp('\\s*\\b' + cls + '\\s*\\b', 'g');
|
||||
if (add == 't')
|
||||
add = !re.test(obj.className);
|
||||
|
||||
obj.className = obj.className.replace(re, ' ') + (add ? ' ' + cls : '');
|
||||
}
|
||||
|
||||
|
||||
function sortfiles(nodes) {
|
||||
var sopts = jread('fsort', [["lead", -1, ""], ["href", 1, ""]]);
|
||||
|
||||
try {
|
||||
var is_srch = false;
|
||||
if (nodes[0]['rp']) {
|
||||
is_srch = true;
|
||||
for (var b = 0, bb = nodes.length; b < bb; b++)
|
||||
nodes[b].ext = nodes[b].rp.split('.').pop();
|
||||
for (var b = 0; b < sopts.length; b++)
|
||||
if (sopts[b][0] == 'href')
|
||||
sopts[b][0] = 'rp';
|
||||
}
|
||||
for (var a = sopts.length - 1; a >= 0; a--) {
|
||||
var name = sopts[a][0], rev = sopts[a][1], typ = sopts[a][2];
|
||||
if (!name)
|
||||
continue;
|
||||
|
||||
if (name.indexOf('tags/') === 0) {
|
||||
name = name.slice(5);
|
||||
for (var b = 0, bb = nodes.length; b < bb; b++)
|
||||
nodes[b]._sv = nodes[b].tags[name];
|
||||
}
|
||||
else {
|
||||
for (var b = 0, bb = nodes.length; b < bb; b++) {
|
||||
var v = nodes[b][name];
|
||||
|
||||
if ((v + '').indexOf('<a ') === 0)
|
||||
v = v.split('>')[1];
|
||||
else if (name == "href" && v)
|
||||
v = uricom_dec(v)[0]
|
||||
|
||||
nodes[b]._sv = v;
|
||||
}
|
||||
}
|
||||
|
||||
var onodes = nodes.map(function (x) { return x; });
|
||||
nodes.sort(function (n1, n2) {
|
||||
var v1 = n1._sv,
|
||||
v2 = n2._sv;
|
||||
|
||||
if (v1 === undefined) {
|
||||
if (v2 === undefined) {
|
||||
return onodes.indexOf(n1) - onodes.indexOf(n2);
|
||||
}
|
||||
return -1 * rev;
|
||||
}
|
||||
if (v2 === undefined) return 1 * rev;
|
||||
|
||||
var ret = rev * (typ == 'int' ? (v1 - v2) : (v1.localeCompare(v2)));
|
||||
if (ret === 0)
|
||||
ret = onodes.indexOf(n1) - onodes.indexOf(n2);
|
||||
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
for (var b = 0, bb = nodes.length; b < bb; b++) {
|
||||
delete nodes[b]._sv;
|
||||
if (is_srch)
|
||||
delete nodes[b].ext;
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
console.log("failed to apply sort config: " + ex);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
|
||||
function sortTable(table, col, cb) {
|
||||
@@ -186,7 +273,6 @@ function makeSortable(table, cb) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
(function () {
|
||||
var ops = document.querySelectorAll('#ops>a');
|
||||
for (var a = 0; a < ops.length; a++) {
|
||||
@@ -212,16 +298,16 @@ function opclick(e) {
|
||||
function goto(dest) {
|
||||
var obj = document.querySelectorAll('.opview.act');
|
||||
for (var a = obj.length - 1; a >= 0; a--)
|
||||
obj[a].classList.remove('act');
|
||||
clmod(obj[a], 'act');
|
||||
|
||||
obj = document.querySelectorAll('#ops>a');
|
||||
for (var a = obj.length - 1; a >= 0; a--)
|
||||
obj[a].classList.remove('act');
|
||||
clmod(obj[a], 'act');
|
||||
|
||||
if (dest) {
|
||||
var ui = ebi('op_' + dest);
|
||||
ui.classList.add('act');
|
||||
document.querySelector('#ops>a[data-dest=' + dest + ']').classList.add('act');
|
||||
clmod(ui, 'act', true);
|
||||
document.querySelector('#ops>a[data-dest=' + dest + ']').className += " act";
|
||||
|
||||
var fn = window['goto_' + dest];
|
||||
if (fn)
|
||||
@@ -237,7 +323,10 @@ function goto(dest) {
|
||||
goto();
|
||||
var op = sread('opmode');
|
||||
if (op !== null && op !== '.')
|
||||
goto(op);
|
||||
try {
|
||||
goto(op);
|
||||
}
|
||||
catch (ex) { }
|
||||
})();
|
||||
|
||||
|
||||
@@ -405,8 +494,7 @@ function bcfg_upd_ui(name, val) {
|
||||
if (o.getAttribute('type') == 'checkbox')
|
||||
o.checked = val;
|
||||
else if (o) {
|
||||
var fun = val ? 'add' : 'remove';
|
||||
o.classList[fun]('on');
|
||||
clmod(o, 'on', val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,13 @@ shab64() { sp=$1; f="$2"; v=0; sz=$(stat -c%s "$f"); while true; do w=$((v+sp*10
|
||||
command -v gdate && date() { gdate "$@"; }; while true; do t=$(date +%s.%N); (time wget http://127.0.0.1:3923/?ls -qO- | jq -C '.files[]|{sz:.sz,ta:.tags.artist,tb:.tags.".bpm"}|del(.[]|select(.==null))' | awk -F\" '/"/{t[$2]++} END {for (k in t){v=t[k];p=sprintf("%" (v+1) "s",v);gsub(/ /,"#",p);printf "\033[36m%s\033[33m%s ",k,p}}') 2>&1 | awk -v ts=$t 'NR==1{t1=$0} NR==2{sub(/.*0m/,"");sub(/s$/,"");t2=$0;c=2; if(t2>0.3){c=3} if(t2>0.8){c=1} } END{sub(/[0-9]{6}$/,"",ts);printf "%s \033[3%dm%s %s\033[0m\n",ts,c,t2,t1}'; sleep 0.1 || break; done
|
||||
|
||||
|
||||
##
|
||||
## js oneliners
|
||||
|
||||
# get all up2k search result URLs
|
||||
var t=[]; var b=document.location.href.split('#')[0].slice(0, -1); document.querySelectorAll('#u2tab .prog a').forEach((x) => {t.push(b+encodeURI(x.getAttribute("href")))}); console.log(t.join("\n"));
|
||||
|
||||
|
||||
##
|
||||
## sqlite3 stuff
|
||||
|
||||
@@ -83,6 +90,9 @@ sqlite3 up2k.db 'select mt1.w, mt1.k, mt1.v, mt2.v from mt mt1 inner join mt mt2
|
||||
time sqlite3 up2k.db 'select mt1.w from mt mt1 inner join mt mt2 on mt1.w = mt2.w where mt1.k = +mt2.k and mt1.rowid != mt2.rowid' > warks
|
||||
cat warks | while IFS= read -r x; do sqlite3 up2k.db "delete from mt where w = '$x'"; done
|
||||
|
||||
# dump all dbs
|
||||
find -iname up2k.db | while IFS= read -r x; do sqlite3 "$x" 'select substr(w,1,12), rd, fn from up' | sed -r 's/\|/ \| /g' | while IFS= read -r y; do printf '%s | %s\n' "$x" "$y"; done; done
|
||||
|
||||
|
||||
##
|
||||
## media
|
||||
@@ -126,6 +136,16 @@ pip install virtualenv
|
||||
# readme toc
|
||||
cat README.md | awk '!/^#/{next} {lv=length($1);sub(/[^ ]+ /,"");bab=$0;gsub(/ /,"-",bab)} {printf "%" ((lv-1)*4+1) "s [%s](#%s)\n", "*",$0,bab}'
|
||||
|
||||
# fix firefox phantom breakpoints,
|
||||
# suggestions from bugtracker, doesnt work (debugger is not attachable)
|
||||
devtools settings >> advanced >> enable browser chrome debugging + enable remote debugging
|
||||
burger > developer >> browser toolbox (ctrl-alt-shift-i)
|
||||
iframe btn topright >> chrome://devtools/content/debugger/index.html
|
||||
dbg.asyncStore.pendingBreakpoints = {}
|
||||
|
||||
# fix firefox phantom breakpoints
|
||||
about:config >> devtools.debugger.prefs-schema-version = -1
|
||||
|
||||
|
||||
##
|
||||
## http 206
|
||||
|
||||
@@ -45,11 +45,13 @@ pybin=$(command -v python3 || command -v python) || {
|
||||
exit 1
|
||||
}
|
||||
|
||||
use_gz=
|
||||
do_sh=1
|
||||
do_py=1
|
||||
while [ ! -z "$1" ]; do
|
||||
[ "$1" = clean ] && clean=1 && shift && continue
|
||||
[ "$1" = re ] && repack=1 && shift && continue
|
||||
[ "$1" = gz ] && use_gz=1 && shift && continue
|
||||
[ "$1" = no-ogv ] && no_ogv=1 && shift && continue
|
||||
[ "$1" = no-cm ] && no_cm=1 && shift && continue
|
||||
[ "$1" = no-sh ] && do_sh= && shift && continue
|
||||
@@ -161,7 +163,7 @@ find .. -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete
|
||||
find .. -type f -name ._\* | while IFS= read -r f; do cmp <(printf '\x00\x05\x16') <(head -c 3 -- "$f") && rm -f -- "$f"; done
|
||||
|
||||
echo use smol web deps
|
||||
rm -f copyparty/web/deps/*.full.*
|
||||
rm -f copyparty/web/deps/*.full.* copyparty/web/{Makefile,splash.js}
|
||||
|
||||
# it's fine dw
|
||||
grep -lE '\.full\.(js|css)' copyparty/web/* |
|
||||
@@ -204,16 +206,20 @@ args=(--owner=1000 --group=1000)
|
||||
|
||||
tar -cf tar "${args[@]}" --numeric-owner copyparty dep-j2
|
||||
|
||||
pc=bzip2
|
||||
pe=bz2
|
||||
[ $use_gz ] && pc=gzip && pe=gz
|
||||
|
||||
echo compressing tar
|
||||
# detect best level; bzip2 -7 is usually better than -9
|
||||
[ $do_py ] && { for n in {2..9}; do cp tar t.$n; bzip2 -$n t.$n & done; wait; mv -v $(ls -1S t.*.bz2 | tail -n 1) tar.bz2; }
|
||||
[ $do_sh ] && { for n in {2..9}; do cp tar t.$n; xz -ze$n t.$n & done; wait; mv -v $(ls -1S t.*.xz | tail -n 1) tar.xz; }
|
||||
[ $do_py ] && { for n in {2..9}; do cp tar t.$n; $pc -$n t.$n & done; wait; mv -v $(ls -1S t.*.$pe | tail -n 1) tar.bz2; }
|
||||
[ $do_sh ] && { for n in {2..9}; do cp tar t.$n; xz -ze$n t.$n & done; wait; mv -v $(ls -1S t.*.xz | tail -n 1) tar.xz; }
|
||||
rm t.* || true
|
||||
exts=()
|
||||
|
||||
|
||||
[ $do_sh ] && {
|
||||
exts+=(sh)
|
||||
exts+=(.sh)
|
||||
echo creating unix sfx
|
||||
(
|
||||
sed "s/PACK_TS/$ts/; s/PACK_HTS/$hts/; s/CPP_VER/$ver/" <../scripts/sfx.sh |
|
||||
@@ -224,17 +230,30 @@ echo creating unix sfx
|
||||
|
||||
|
||||
[ $do_py ] && {
|
||||
exts+=(py)
|
||||
echo creating generic sfx
|
||||
$pybin ../scripts/sfx.py --sfx-make tar.bz2 $ver $ts
|
||||
mv sfx.out $sfx_out.py
|
||||
chmod 755 $sfx_out.*
|
||||
echo creating generic sfx
|
||||
|
||||
py=../scripts/sfx.py
|
||||
suf=
|
||||
[ $use_gz ] && {
|
||||
sed -r 's/"r:bz2"/"r:gz"/' <$py >$py.t
|
||||
py=$py.t
|
||||
suf=-gz
|
||||
}
|
||||
|
||||
$pybin $py --sfx-make tar.bz2 $ver $ts
|
||||
mv sfx.out $sfx_out$suf.py
|
||||
|
||||
exts+=($suf.py)
|
||||
[ $use_gz ] &&
|
||||
rm $py
|
||||
}
|
||||
|
||||
|
||||
chmod 755 $sfx_out*
|
||||
|
||||
printf "done:\n"
|
||||
for ext in ${exts[@]}; do
|
||||
printf " %s\n" "$(realpath $sfx_out)."$ext
|
||||
printf " %s\n" "$(realpath $sfx_out)"$ext
|
||||
done
|
||||
|
||||
# apk add bash python3 tar xz bzip2
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# coding: latin-1
|
||||
from __future__ import print_function, unicode_literals
|
||||
|
||||
import os, sys, time, shutil, runpy, tarfile, hashlib, platform, tempfile, traceback
|
||||
import os, sys, time, shutil, threading, tarfile, hashlib, platform, tempfile, traceback
|
||||
|
||||
"""
|
||||
run me with any version of python, i will unpack and run copyparty
|
||||
@@ -26,6 +26,7 @@ CKSUM = None
|
||||
STAMP = None
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WINDOWS = sys.platform == "win32"
|
||||
sys.dont_write_bytecode = True
|
||||
me = os.path.abspath(os.path.realpath(__file__))
|
||||
cpp = None
|
||||
@@ -343,6 +344,21 @@ def get_payload():
|
||||
break
|
||||
|
||||
|
||||
def utime(top):
|
||||
i = 0
|
||||
files = [os.path.join(dp, p) for dp, dd, df in os.walk(top) for p in dd + df]
|
||||
while WINDOWS:
|
||||
t = int(time.time())
|
||||
if i:
|
||||
msg("utime {}, {}".format(i, t))
|
||||
|
||||
for f in files:
|
||||
os.utime(f, (t, t))
|
||||
|
||||
i += 1
|
||||
time.sleep(78123)
|
||||
|
||||
|
||||
def confirm(rv):
|
||||
msg()
|
||||
msg(traceback.format_exc())
|
||||
@@ -362,15 +378,20 @@ def run(tmp, j2ver):
|
||||
msg("sfxdir:", tmp)
|
||||
msg()
|
||||
|
||||
# "systemd-tmpfiles-clean.timer"?? HOW do you even come up with this shit
|
||||
# block systemd-tmpfiles-clean.timer
|
||||
try:
|
||||
import fcntl
|
||||
|
||||
fd = os.open(tmp, os.O_RDONLY)
|
||||
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
tmp = os.readlink(tmp) # can't flock a symlink, even with O_NOFOLLOW
|
||||
except:
|
||||
pass
|
||||
except Exception as ex:
|
||||
if not WINDOWS:
|
||||
msg("\033[31mflock:", repr(ex))
|
||||
|
||||
t = threading.Thread(target=utime, args=(tmp,))
|
||||
t.daemon = True
|
||||
t.start()
|
||||
|
||||
ld = [tmp, os.path.join(tmp, "dep-j2")]
|
||||
if j2ver:
|
||||
@@ -380,7 +401,10 @@ def run(tmp, j2ver):
|
||||
sys.path.insert(0, x)
|
||||
|
||||
try:
|
||||
runpy.run_module(str("copyparty"), run_name=str("__main__"))
|
||||
from copyparty.__main__ import main as copyparty
|
||||
|
||||
copyparty()
|
||||
|
||||
except SystemExit as ex:
|
||||
if ex.code:
|
||||
confirm(ex.code)
|
||||
|
||||
@@ -18,7 +18,9 @@ from copyparty import util
|
||||
|
||||
class Cfg(Namespace):
|
||||
def __init__(self, a=[], v=[], c=None):
|
||||
ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr mte".split()}
|
||||
ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
|
||||
ex["mtp"] = []
|
||||
ex["mte"] = "a"
|
||||
super(Cfg, self).__init__(a=a, v=v, c=c, **ex)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user