Compare commits

...

44 Commits

Author SHA1 Message Date
ed
4eb20f10ad v0.11.46 2021-07-22 08:42:27 +02:00
ed
daa11df558 avoid chrome bug 809574 2021-07-22 08:40:46 +02:00
ed
1bb0db30a0 fix logout link going 404 2021-07-21 01:30:27 +02:00
ed
02910b0020 v0.11.45 2021-07-20 23:23:08 +02:00
ed
23b8901c9c include localstore on the crashpage 2021-07-20 23:22:35 +02:00
ed
99f6ed0cd7 up2k-cli: avoid loading sha.js multiple times 2021-07-20 23:14:30 +02:00
ed
890c310880 another attempt at fixing tooltips on iphone 2021-07-20 23:07:15 +02:00
ed
0194eeb31f add login/permissions indicator 2021-07-20 22:42:03 +02:00
ed
f9be4c62b1 v0.11.44 2021-07-20 01:03:08 +02:00
ed
027e8c18f1 sfx: option to remove mouse cursor 2021-07-20 01:00:28 +02:00
ed
4a3bb35a95 sfx: option to remove scp.woff2 2021-07-20 00:45:54 +02:00
ed
4bfb0d4494 notes 2021-07-19 23:46:44 +02:00
ed
7e0ef03a1e fix audio player edgecase (continue into next folder with sidebar closed) 2021-07-19 23:10:48 +02:00
ed
f7dbd95a54 v0.11.43 2021-07-19 01:56:19 +02:00
ed
515ee2290b v0.11.42 2021-07-18 23:22:09 +02:00
ed
b0c78910bb fix tabchange triggering tooltips 2021-07-18 23:21:36 +02:00
ed
f4ca62b664 reattach tooltips on column show/hide 2021-07-18 23:14:57 +02:00
ed
8eb8043a3d fix 3rdparty namecase 2021-07-18 22:50:29 +02:00
ed
3e8541362a keep active dir scrolled into view on keybd nav 2021-07-18 22:32:34 +02:00
ed
789724e348 use preferred key notation in search results 2021-07-18 21:50:57 +02:00
ed
5125b9532f fix multiple whitespace in query translator 2021-07-18 21:39:28 +02:00
ed
ebc9de02b0 case-insensitive tag search 2021-07-18 21:34:36 +02:00
ed
ec788fa491 mutagen fixes:
* extract codec and format info
* add FFprobe as fallback when mutagen fails
* add option to blacklist FFprobe for tags
2021-07-18 19:57:31 +02:00
ed
9b5e264574 systemd: fix name in journalctl 2021-07-17 19:14:15 +02:00
ed
57c297274b v0.11.41 2021-07-17 17:53:34 +02:00
ed
e9bf092317 tweak audio drawer tab 2021-07-17 17:24:48 +02:00
ed
d173887324 explain confusing behavior in journalctl 2021-07-17 16:45:49 +02:00
ed
99820d854c oh that wasnt enough ok then 2021-07-17 16:45:25 +02:00
ed
62df0a0eb2 thx osx 2021-07-17 16:43:22 +02:00
ed
600e9ac947 try to workaround iphones not hiding tooltips 2021-07-17 16:03:21 +02:00
ed
3ca41be2b4 do up2k snapshot on shutdown 2021-07-17 14:48:35 +02:00
ed
5c7debd900 improve signal handling + emit sd-notify on start 2021-07-17 04:15:07 +02:00
ed
7fa5b23ce3 sfx: fix color bleed on flock errors 2021-07-17 04:12:14 +02:00
ed
ff82738aaf vscode: support whitespace in python binary path 2021-07-17 04:11:14 +02:00
ed
bf5ee9d643 colum header tooltips 2021-07-17 02:52:55 +02:00
ed
72a8593ecd gridmode shortcut in the audio drawer 2021-07-17 01:45:05 +02:00
ed
bc3bbe07d4 combine tabs on narrow screens 2021-07-17 01:21:49 +02:00
ed
c7cb64bfef gallery: add hotkey list button 2021-07-17 01:14:14 +02:00
ed
629f537d06 add more hotkey tooltips 2021-07-17 01:05:26 +02:00
ed
9e988041b8 cosmetics 2021-07-16 02:56:21 +02:00
ed
f9a8b5c9d7 update readme 2021-07-16 02:44:06 +02:00
ed
b9c3538253 nope, not doing this 2021-07-15 23:49:30 +02:00
ed
2bc0cdf017 fix md-editor hotkeys on dvorak 2021-07-15 23:24:10 +02:00
ed
02a91f60d4 playing some golf 2021-07-15 23:19:37 +02:00
27 changed files with 692 additions and 241 deletions

5
.vscode/tasks.json vendored
View File

@@ -9,7 +9,10 @@
{
"label": "no_dbg",
"type": "shell",
"command": "${config:python.pythonPath} .vscode/launch.py"
"command": "${config:python.pythonPath}",
"args": [
".vscode/launch.py"
]
}
]
}

108
README.md
View File

@@ -59,6 +59,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
* [just the sfx](#just-the-sfx)
* [complete release](#complete-release)
* [todo](#todo)
* [discarded ideas](#discarded-ideas)
## quickstart
@@ -69,7 +70,7 @@ running the sfx without arguments (for example doubleclicking it on Windows) wil
some recommended options:
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
* `-e2ts` enables audio metadata indexing (needs either FFprobe or Mutagen), see [optional dependencies](#optional-dependencies)
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
* replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
@@ -114,7 +115,7 @@ summary: all planned features work! now please enjoy the bloatening
* backend stuff
* ☑ sanic multipart parser
*load balancer (multiprocessing)
*multiprocessing (actual multithreading)
* ☑ volumes (mountpoints)
* ☑ accounts
* upload
@@ -130,10 +131,10 @@ summary: all planned features work! now please enjoy the bloatening
* ☑ tree-view
* ☑ audio player (with OS media controls)
* ☑ thumbnails
* ☑ images using Pillow
* ☑ videos using FFmpeg
* ...of images using Pillow
* ...of videos using FFmpeg
* ☑ cache eviction (max-age; maybe max-size eventually)
* ☑ image gallery
* ☑ image gallery with webm player
* ☑ SPA (browse while uploading)
* if you use the file-tree on the left only, not folders in the file list
* server indexing
@@ -154,10 +155,10 @@ small collection of user feedback
# bugs
* Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade
* 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
* MacOS: `--th-ff-jpg` may fix thumbnails using macports-FFmpeg
* `--th-ff-jpg` may fix video thumbnails on some FFmpeg versions
## general bugs
@@ -172,9 +173,12 @@ small collection of user feedback
* Windows: folders cannot be accessed if the name ends with `.`
* python or windows bug
* Windows: msys2-python 3.8.6 occasionally throws "RuntimeError: release unlocked lock" when leaving a scoped mutex in up2k
* Windows: msys2-python 3.8.6 occasionally throws `RuntimeError: release unlocked lock` when leaving a scoped mutex in up2k
* this is an msys2 bug, the regular windows edition of python is fine
* VirtualBox: sqlite throws `Disk I/O Error` when running in a VM and the up2k database is in a vboxsf
* use `--hist` or the `hist` volflag (`-v [...]:chist=/tmp/foo`) to place the db inside the vm instead
# the browser
@@ -188,15 +192,16 @@ small collection of user feedback
* `[📂]` mkdir, create directories
* `[📝]` new-md, create a new markdown document
* `[📟]` send-msg, either to server-log or into textfiles if `--urlform save`
* `[⚙️]` client configuration options
* `[🎺]` audio-player config options
* `[⚙️]` general client config options
## hotkeys
the browser has the following hotkeys
the browser has the following hotkeys (assumes qwerty, ignores actual layout)
* `B` toggle breadcrumbs / directory tree
* `I/K` prev/next folder
* `M` parent folder
* `M` parent folder (or unexpand current)
* `G` toggle list / grid view
* `T` toggle thumbnails / icons
* when playing audio:
@@ -220,7 +225,13 @@ the browser has the following hotkeys
* in the grid view:
* `S` toggle multiselect
* shift+`A/D` zoom
* in the markdown editor:
* `^s` save
* `^h` header
* `^k` autoformat table
* `^u` jump to next unicode character
* `^e` toggle editor / preview
* `^up, ^down` jump paragraphs
## tree-mode
@@ -233,7 +244,7 @@ click `[-]` and `[+]` (or hotkeys `A`/`D`) to adjust the size, and the `[a]` tog
![copyparty-thumbs-fs8](https://user-images.githubusercontent.com/241032/120070302-10836b00-c08a-11eb-8eb4-82004a34c342.png)
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how destructive your users are
it does static images with Pillow and uses FFmpeg for video files, so you may want to `--no-thumb` or maybe just `--no-vthumb` depending on how dangerous your users are
images with the following names (see `--th-covers`) become the thumbnail of the folder they're in: `folder.png`, `folder.jpg`, `cover.png`, `cover.jpg`
@@ -252,9 +263,10 @@ the `zip` link next to folders can produce various types of zip/tar files using
| `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
* `up2k.db` and `dir.txt` 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
* this is only to support MS-DOS PKZIP v2.04g (october 1993) and older
* how are you accessing copyparty actually
you can also zip a selection of files or folders by clicking them in the browser, that brings up a selection editor and zip button in the bottom right
@@ -269,9 +281,11 @@ two upload methods are available in the html client:
up2k has several advantages:
* you can drop folders into the browser (files are added recursively)
* files are processed in chunks, and each chunk is checksummed
* uploads resume if they are interrupted (for example by a reboot)
* uploads autoresume if they are interrupted by network issues
* uploads resume if you reboot your browser or pc, just upload the same files again
* server detects any corruption; the client reuploads affected chunks
* the client doesn't upload anything that already exists on the server
* much higher speeds than ftp/scp/tarpipe on some internet connections (mainly american ones) thanks to parallel connections
* the last-modified timestamp of the file is preserved
see [up2k](#up2k) for details on how it works
@@ -304,11 +318,11 @@ in the `[🚀 up2k]` tab, after toggling the `[🔎]` switch green, any files/fo
files go into `[ok]` if they exist (and you get a link to where it is), otherwise they land in `[ng]`
* the main reason filesearch is combined with the uploader is cause the code was too spaghetti to separate it out somewhere else, this is no longer the case but now i've warmed up to the idea too much
adding the same file multiple times is blocked, so if you first search for a file and then decide to upload it, you have to click the `[cleanup]` button to discard `[done]` files
adding the same file multiple times is blocked, so if you first search for a file and then decide to upload it, you have to click the `[cleanup]` button to discard `[done]` files (or just refresh the page)
note that since up2k has to read the file twice, `[🎈 bup]` can be up to 2x faster in extreme cases (if your internet connection is faster than the read-speed of your HDD)
up2k has saved a few uploads from becoming corrupted in-transfer already; caught an android phone on wifi redhanded in wireshark with a bitflip, however bup with https would *probably* have noticed as well thanks to tls also functioning as an integrity check
up2k has saved a few uploads from becoming corrupted in-transfer already; caught an android phone on wifi redhanded in wireshark with a bitflip, however bup with https would *probably* have noticed as well (thanks to tls also functioning as an integrity check)
## markdown viewer
@@ -346,11 +360,11 @@ searching relies on two databases, the up2k filetree (`-e2d`) and the metadata t
through arguments:
* `-e2d` enables file indexing on upload
* `-e2ds` scans writable folders on startup
* `-e2ds` scans writable folders for new files on startup
* `-e2dsa` scans all mounted volumes (including readonly ones)
* `-e2t` enables metadata indexing on upload
* `-e2ts` scans for tags in all files that don't have tags yet
* `-e2tsr` deletes all existing tags, so a full reindex
* `-e2tsr` deletes all existing tags, does a full reindex
the same arguments can be set as volume flags, in addition to `d2d` and `d2t` for disabling:
* `-v ~/music::r:ce2dsa:ce2tsr` does a full reindex of everything on startup
@@ -358,11 +372,11 @@ the same arguments can be set as volume flags, in addition to `d2d` and `d2t` fo
* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
note:
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those
* `e2tsr` is probably always overkill, since `e2ds`/`e2dsa` would pick up any file modifications and `e2ts` would then reindex those, unless there is a new copyparty version with new parsers and the release note says otherwise
* the rescan button in the admin panel has no effect unless the volume has `-e2ds` or higher
you can choose to only index filename/path/size/last-modified (and not the hash of the file contents) by setting `--no-hash` or the volume-flag `cdhash`, this has the following consequences:
* initial indexing is way faster, especially when the volume is on a networked disk
* initial indexing is way faster, especially when the volume is on a network disk
* makes it impossible to [file-search](#file-search)
* if someone uploads the same file contents, the upload will not be detected as a dupe, so it will not get symlinked or rejected
@@ -395,17 +409,17 @@ tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric val
see the beautiful mess of a dictionary in [mtag.py](https://github.com/9001/copyparty/blob/master/copyparty/mtag.py) for the default mappings (should cover mp3,opus,flac,m4a,wav,aif,)
`--no-mutagen` disables mutagen and uses ffprobe instead, which...
* is about 20x slower than mutagen
* catches a few tags that mutagen doesn't
`--no-mutagen` disables Mutagen and uses FFprobe instead, which...
* is about 20x slower than Mutagen
* catches a few tags that Mutagen doesn't
* melodic key, video resolution, framerate, pixfmt
* avoids pulling any GPL code into copyparty
* more importantly runs ffprobe on incoming files which is bad if your ffmpeg has a cve
* more importantly runs FFprobe on incoming files which is bad if your FFmpeg has a cve
## file parser plugins
copyparty can invoke external programs to collect additional metadata for files using `mtp` (as argument or volume flag), there is a default timeout of 30sec
copyparty can invoke external programs to collect additional metadata for files using `mtp` (either as argument or volume flag), there is a default timeout of 30sec
* `-mtp .bpm=~/bin/audio-bpm.py` will execute `~/bin/audio-bpm.py` with the audio file as argument 1 to provide the `.bpm` tag, if that does not exist in the audio metadata
* `-mtp key=f,t5,~/bin/audio-key.py` uses `~/bin/audio-key.py` to get the `key` tag, replacing any existing metadata tag (`f,`), aborting if it takes longer than 5sec (`t5,`)
@@ -439,11 +453,13 @@ copyparty can invoke external programs to collect additional metadata for files
| 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 |
| thumbnail view | - | - | - | - | yep | yep | yep | yep |
| image viewer | - | - | - | - | yep | yep | yep | yep |
| **= feature =** | ie6 | ie9 | ie10 | ie11 | ff 52 | c 49 | iOS | Andr |
* internet explorer 6 to 8 behave the same
* firefox 52 and chrome 49 are the last winxp versions
@@ -461,7 +477,7 @@ quick summary of more eccentric web-browsers trying to view a directory index:
| **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) |
| **ie4** and **netscape** 4.0 | 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 |
| **SerenityOS** (7e98457) | hits a page fault, works with `?b=u`, file upload not-impl |
# client examples
@@ -515,7 +531,7 @@ below are some tweaks roughly ordered by usefulness:
* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
* `--http-only` or `--https-only` (unless you want to support both protocols) will reduce the delay before a new connection is established
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
* `--no-hash` when indexing a networked disk if you don't care about the actual filehashes and only want the names/tags searchable
* `--no-hash` when indexing a network-disk if you don't care about the actual filehashes and only want the names/tags searchable
* `-j` enables multiprocessing (actual multithreading) and can make copyparty perform better in cpu-intensive workloads, for example:
* huge amount of short-lived connections
* really heavy traffic (downloads/uploads)
@@ -532,18 +548,18 @@ below are some tweaks roughly ordered by usefulness:
enable music tags:
* either `mutagen` (fast, pure-python, skips a few tags, makes copyparty GPL? idk)
* or `FFprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
* or `ffprobe` (20x slower, more accurate, possibly dangerous depending on your distro and users)
enable image thumbnails:
enable thumbnails of images:
* `Pillow` (requires py2.7 or py3.5+)
enable video thumbnails:
enable thumbnails of videos:
* `ffmpeg` and `ffprobe` somewhere in `$PATH`
enable reading HEIF pictures:
enable thumbnails of HEIF pictures:
* `pyheif-pillow-opener` (requires Linux or a C compiler)
enable reading AVIF pictures:
enable thumbnails of AVIF pictures:
* `pillow-avif-plugin`
@@ -557,7 +573,7 @@ python -m pip install --user -U jinja2 mutagen Pillow
some bundled tools have copyleft dependencies, see [./bin/#mtag](bin/#mtag)
these are standalone programs and will never be imported / evaluated by copyparty
these are standalone programs and will never be imported / evaluated by copyparty, and must be enabled through `-mtp` configs
# sfx
@@ -573,10 +589,10 @@ pls note that `copyparty-sfx.sh` will fail if you rename `copyparty-sfx.py` to `
## sfx repack
if you don't need all the features you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except for either msys2 or WSL if you're on windows)
* `724K` original size as of v0.4.0
* `256K` after `./scripts/make-sfx.sh re no-ogv`
* `164K` after `./scripts/make-sfx.sh re no-ogv no-cm`
if you don't need all the features, you can repack the sfx and save a bunch of space; all you need is an sfx and a copy of this repo (nothing else to download or build, except if you're on windows then you need msys2 or WSL)
* `525k` size of original sfx.py as of v0.11.30
* `315k` after `./scripts/make-sfx.sh re no-ogv`
* `223k` after `./scripts/make-sfx.sh re no-ogv no-cm`
the features you can opt to drop are
* `ogv`.js, the opus/vorbis decoder which is needed by apple devices to play foss audio files
@@ -623,7 +639,7 @@ rm -rf copyparty/web/deps
curl -L https://github.com/9001/copyparty/releases/latest/download/copyparty-sfx.py >x.py
python3 x.py -h
rm x.py
mv /tmp/pe-copyparty/copyparty/web/deps/ copyparty/web/
mv /tmp/pe-copyparty/copyparty/web/deps/ copyparty/web/deps/
```
then build the sfx using any of the following examples:
@@ -651,14 +667,16 @@ in the `scripts` folder:
roughly sorted by priority
* hls framework for Someone Else to drop code into :^)
* readme.md as epilogue
## discarded ideas
* reduce up2k roundtrips
* start from a chunk index and just go
* terminate client on bad data
* logging to file
discarded ideas
* not worth the effort, just throw enough conncetions at it
* single sha512 across all up2k chunks?
* crypto.subtle cannot into streaming, would have to use hashwasm, expensive
* separate sqlite table per tag

View File

@@ -7,11 +7,19 @@
# you may want to:
# change '/usr/bin/python' to another interpreter
# change '/mnt::a' to another location or permission-set
#
# with `Type=notify`, copyparty will signal systemd when it is ready to
# accept connections; correctly delaying units depending on copyparty.
# But note that journalctl will get the timestamps wrong due to
# python disabling line-buffering, so messages are out-of-order:
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
[Unit]
Description=copyparty file server
[Service]
Type=notify
SyslogIdentifier=copyparty
ExecStart=/usr/bin/python3 /usr/local/bin/copyparty-sfx.py -q -v /mnt::a
ExecStartPre=/bin/bash -c 'mkdir -p /run/tmpfiles.d/ && echo "x /tmp/pe-copyparty*" > /run/tmpfiles.d/copyparty.conf'

View File

@@ -319,8 +319,9 @@ def run_argparse(argv, formatter):
ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume state")
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
ap2.add_argument("--no-mutagen", action="store_true", help="use ffprobe for tags instead")
ap2.add_argument("--no-mutagen", action="store_true", help="use FFprobe for tags instead")
ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism")
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")

View File

@@ -1,8 +1,8 @@
# coding: utf-8
VERSION = (0, 11, 40)
VERSION = (0, 11, 46)
CODENAME = "the grid"
BUILD_DT = (2021, 7, 15)
BUILD_DT = (2021, 7, 22)
S_VERSION = ".".join(map(str, VERSION))
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)

View File

@@ -29,7 +29,8 @@ class MpWorker(object):
# we inherited signal_handler from parent,
# replace it with something harmless
if not FAKE_MP:
signal.signal(signal.SIGINT, self.signal_handler)
for sig in [signal.SIGINT, signal.SIGTERM]:
signal.signal(sig, self.signal_handler)
# starting to look like a good idea
self.asrv = AuthSrv(args, None, False)
@@ -44,7 +45,7 @@ class MpWorker(object):
thr.start()
thr.join()
def signal_handler(self, signal, frame):
def signal_handler(self, sig, frame):
# print('k')
pass

View File

@@ -1376,7 +1376,7 @@ class HttpCli(object):
for c, v in [[b"&", 4], [b"<", 3], [b">", 3]]:
sz_md += (len(buf) - len(buf.replace(c, b""))) * v
file_ts = max(ts_md, ts_html)
file_ts = max(ts_md, ts_html, E.t0)
file_lastmod, do_send = self._chk_lastmod(file_ts)
self.out_headers["Last-Modified"] = file_lastmod
self.out_headers.update(NO_CACHE)
@@ -1653,6 +1653,7 @@ class HttpCli(object):
"files": [],
"taglist": [],
"srvinf": srv_info,
"acct": self.uname,
"perms": perms,
"logues": logues,
}
@@ -1660,6 +1661,7 @@ class HttpCli(object):
"vdir": quotep(self.vpath),
"vpnodes": vpnodes,
"files": [],
"acct": self.uname,
"perms": json.dumps(perms),
"taglist": [],
"tag_order": [],

View File

@@ -227,37 +227,47 @@ def parse_ffprobe(txt):
class MTag(object):
def __init__(self, log_func, args):
self.log_func = log_func
self.args = args
self.usable = True
self.prefer_mt = False
mappings = args.mtm
self.prefer_mt = not args.no_mtag_ff
self.backend = "ffprobe" if args.no_mutagen else "mutagen"
or_ffprobe = " or ffprobe"
self.can_ffprobe = (
HAVE_FFPROBE
and not args.no_mtag_ff
and (not WINDOWS or sys.version_info >= (3, 8))
)
mappings = args.mtm
or_ffprobe = " or FFprobe"
if self.backend == "mutagen":
self.get = self.get_mutagen
try:
import mutagen
except:
self.log("could not load mutagen, trying ffprobe instead", c=3)
self.log("could not load Mutagen, trying FFprobe instead", c=3)
self.backend = "ffprobe"
if self.backend == "ffprobe":
self.usable = self.can_ffprobe
self.get = self.get_ffprobe
self.prefer_mt = True
# about 20x slower
self.usable = HAVE_FFPROBE
if self.usable and WINDOWS and sys.version_info < (3, 8):
self.usable = False
if not HAVE_FFPROBE:
pass
elif args.no_mtag_ff:
msg = "found FFprobe but it was disabled by --no-mtag-ff"
self.log(msg, c=3)
elif WINDOWS and sys.version_info < (3, 8):
or_ffprobe = " or python >= 3.8"
msg = "found ffprobe but your python is too old; need 3.8 or newer"
msg = "found FFprobe but your python is too old; need 3.8 or newer"
self.log(msg, c=1)
if not self.usable:
msg = "need mutagen{} to read media tags so please run this:\n{}{} -m pip install --user mutagen\n"
self.log(
msg.format(or_ffprobe, " " * 37, os.path.basename(sys.executable)), c=1
)
msg = "need Mutagen{} to read media tags so please run this:\n{}{} -m pip install --user mutagen\n"
pybin = os.path.basename(sys.executable)
self.log(msg.format(or_ffprobe, " " * 37, pybin), c=1)
return
# https://picard-docs.musicbrainz.org/downloads/MusicBrainz_Picard_Tag_Map.html
@@ -387,7 +397,7 @@ class MTag(object):
v2 = r2.get(k)
if v1 == v2:
print(" ", k, v1)
elif v1 != "0000": # ffprobe date=0
elif v1 != "0000": # FFprobe date=0
diffs.append(k)
print(" 1", k, v1)
print(" 2", k, v2)
@@ -408,20 +418,33 @@ class MTag(object):
md = mutagen.File(fsenc(abspath), easy=True)
x = md.info.length
except Exception as ex:
return {}
return self.get_ffprobe(abspath) if self.can_ffprobe else {}
ret = {}
try:
dur = int(md.info.length)
sz = os.path.getsize(fsenc(abspath))
ret = {".q": [0, int((sz / md.info.length) / 128)]}
for attr, k, norm in [
["codec", "ac", unicode],
["channels", "chs", int],
["sample_rate", ".hz", int],
["bitrate", ".aq", int],
["length", ".dur", int],
]:
try:
q = int(md.info.bitrate / 1024)
v = getattr(md.info, attr)
except:
q = int((os.path.getsize(fsenc(abspath)) / dur) / 128)
continue
ret[".dur"] = [0, dur]
ret[".q"] = [0, q]
except:
pass
if not v:
continue
if k == ".aq":
v /= 1000
if k == "ac" and v.startswith("mp4a.40."):
v = "aac"
ret[k] = [0, norm(v)]
return self.normalize_tags(ret, md)

View File

@@ -6,12 +6,15 @@ import os
import sys
import time
import shlex
import string
import signal
import socket
import threading
from datetime import datetime, timedelta
import calendar
from .__init__ import E, PY2, WINDOWS, MACOS, VT100
from .util import mp, start_log_thrs, start_stackmon
from .__init__ import E, PY2, WINDOWS, MACOS, VT100, unicode
from .util import mp, start_log_thrs, start_stackmon, min_ex
from .authsrv import AuthSrv
from .tcpsrv import TcpSrv
from .up2k import Up2k
@@ -33,6 +36,9 @@ class SvcHub(object):
self.args = args
self.argv = argv
self.logf = None
self.stop_req = False
self.stopping = False
self.stop_cond = threading.Condition()
self.ansi_re = re.compile("\033\\[[^m]*m")
self.log_mutex = threading.Lock()
@@ -127,21 +133,55 @@ class SvcHub(object):
print(msg, end="")
def run(self):
thr = threading.Thread(target=self.tcpsrv.run, name="svchub-main")
self.tcpsrv.run()
thr = threading.Thread(target=self.sd_notify, name="sd-notify")
thr.daemon = True
thr.start()
# winxp/py2.7 support: thr.join() kills signals
try:
while True:
time.sleep(9001)
thr = threading.Thread(target=self.stop_thr, name="svchub-sig")
thr.daemon = True
thr.start()
except KeyboardInterrupt:
for sig in [signal.SIGINT, signal.SIGTERM]:
signal.signal(sig, self.signal_handler)
try:
while not self.stop_req:
time.sleep(9001)
except:
pass
self.shutdown()
def stop_thr(self):
while not self.stop_req:
with self.stop_cond:
self.stop_cond.wait(9001)
self.shutdown()
def signal_handler(self):
if self.stopping:
return
self.stop_req = True
with self.stop_cond:
self.stop_cond.notify_all()
def shutdown(self):
if self.stopping:
return
self.stopping = True
self.stop_req = True
try:
with self.log_mutex:
print("OPYTHAT")
self.tcpsrv.shutdown()
self.broker.shutdown()
self.up2k.shutdown()
if self.thumbsrv:
self.thumbsrv.shutdown()
@@ -268,3 +308,22 @@ class SvcHub(object):
else:
self.log("svchub", err)
return False
def sd_notify(self):
try:
addr = os.getenv("NOTIFY_SOCKET")
if not addr:
return
addr = unicode(addr)
if addr.startswith("@"):
addr = "\0" + addr[1:]
m = "".join(x for x in addr if x in string.printable)
self.log("sd_notify", m)
sck = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sck.connect(addr)
sck.sendall(b"READY=1")
except:
self.log("sd_notify", min_ex())

View File

@@ -121,10 +121,10 @@ class ThumbSrv(object):
if not self.args.no_vthumb and (not HAVE_FFMPEG or not HAVE_FFPROBE):
missing = []
if not HAVE_FFMPEG:
missing.append("ffmpeg")
missing.append("FFmpeg")
if not HAVE_FFPROBE:
missing.append("ffprobe")
missing.append("FFprobe")
msg = "cannot create video thumbnails because some of the required programs are not available: "
msg += ", ".join(missing)

View File

@@ -7,6 +7,7 @@ import time
import threading
from datetime import datetime
from .__init__ import unicode
from .util import s3dec, Pebkac, min_ex
from .up2k import up2k_wark_from_hashlist
@@ -90,6 +91,8 @@ class U2idx(object):
mt_ctr = 0
mt_keycmp = "substr(up.w,1,16)"
mt_keycmp2 = None
ptn_lc = re.compile(r" (mt[0-9]+\.v) ([=<!>]+) \? $")
ptn_lcv = re.compile(r"[a-zA-Z]")
while True:
uq = uq.strip()
@@ -182,6 +185,21 @@ class U2idx(object):
va.append(v)
is_key = True
# lowercase tag searches
m = ptn_lc.search(q)
if not m or not ptn_lcv.search(unicode(v)):
continue
va.pop()
va.append(v.lower())
q = q[: m.start()]
field, oper = m.groups()
if oper in ["=", "=="]:
q += " {} like ? ".format(field)
else:
q += " lower({}) {} ? ".format(field, oper)
try:
return self.run_query(vols, joins + "where " + q, va)
except Exception as ex:

View File

@@ -304,7 +304,7 @@ class Up2k(object):
self.log(msg.format(len(vols), time.time() - t0))
if needed_mutagen:
msg = "could not read tags because no backends are available (mutagen or ffprobe)"
msg = "could not read tags because no backends are available (Mutagen or FFprobe)"
self.log(msg, c=1)
thr = None
@@ -596,7 +596,7 @@ class Up2k(object):
c2 = conn.cursor()
c3 = conn.cursor()
n_left = cur.execute("select count(w) from up").fetchone()[0]
for w, rd, fn in cur.execute("select w, rd, fn from up"):
for w, rd, fn in cur.execute("select w, rd, fn from up order by rd, fn"):
n_left -= 1
q = "select w from mt where w = ?"
if c2.execute(q, (w[:16],)).fetchone():
@@ -1363,19 +1363,22 @@ class Up2k(object):
self.log("could not unsparse [{}]".format(path), 3)
def _snapshot(self):
persist_interval = 30 # persist unfinished uploads index every 30 sec
discard_interval = 21600 # drop unfinished uploads after 6 hours inactivity
prev = {}
self.snap_persist_interval = 300 # persist unfinished index every 5 min
self.snap_discard_interval = 21600 # drop unfinished after 6 hours inactivity
self.snap_prev = {}
while True:
time.sleep(persist_interval)
with self.mutex:
for k, reg in self.registry.items():
self._snap_reg(prev, k, reg, discard_interval)
time.sleep(self.snap_persist_interval)
self.do_snapshot()
def _snap_reg(self, prev, ptop, reg, discard_interval):
def do_snapshot(self):
with self.mutex:
for k, reg in self.registry.items():
self._snap_reg(k, reg)
def _snap_reg(self, ptop, reg):
now = time.time()
histpath = self.asrv.vfs.histtab[ptop]
rm = [x for x in reg.values() if now - x["poke"] > discard_interval]
rm = [x for x in reg.values() if now - x["poke"] > self.snap_discard_interval]
if rm:
m = "dropping {} abandoned uploads in {}".format(len(rm), ptop)
vis = [self._vis_job_progress(x) for x in rm]
@@ -1397,15 +1400,15 @@ class Up2k(object):
path = os.path.join(histpath, "up2k.snap")
if not reg:
if ptop not in prev or prev[ptop] is not None:
prev[ptop] = None
if ptop not in self.snap_prev or self.snap_prev[ptop] is not None:
self.snap_prev[ptop] = None
if os.path.exists(fsenc(path)):
os.unlink(fsenc(path))
return
newest = max(x["poke"] for _, x in reg.items()) if reg else 0
etag = [len(reg), newest]
if etag == prev.get(ptop):
if etag == self.snap_prev.get(ptop):
return
try:
@@ -1421,7 +1424,7 @@ class Up2k(object):
atomic_move(path2, path)
self.log("snap: {} |{}|".format(path, len(reg.keys())))
prev[ptop] = etag
self.snap_prev[ptop] = etag
def _tagger(self):
with self.mutex:
@@ -1489,6 +1492,11 @@ class Up2k(object):
self.n_hashq += 1
# self.log("hashq {} push {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
def shutdown(self):
if hasattr(self, "snap_prev"):
self.log("writing snapshot")
self.do_snapshot()
def up2k_chunksize(filesize):
chunksize = 1024 * 1024
@@ -1504,7 +1512,7 @@ def up2k_chunksize(filesize):
def up2k_wark_from_hashlist(salt, filesize, hashes):
""" server-reproducible file identifier, independent of name or location """
"""server-reproducible file identifier, independent of name or location"""
ident = [salt, str(filesize)]
ident.extend(hashes)
ident = "\n".join(ident)

View File

@@ -22,7 +22,7 @@ window.baguetteBox = (function () {
afterHide: null,
onChange: null,
},
overlay, slider, btnPrev, btnNext, btnVmode, btnClose,
overlay, slider, btnPrev, btnNext, btnHelp, btnVmode, btnClose,
currentGallery = [],
currentIndex = 0,
isOverlayVisible = false,
@@ -97,7 +97,7 @@ window.baguetteBox = (function () {
}
function bindImageClickListeners(selector, userOptions) {
var galleryNodeList = document.querySelectorAll(selector);
var galleryNodeList = QSA(selector);
var selectorData = {
galleries: [],
nodeList: galleryNodeList
@@ -174,22 +174,57 @@ window.baguetteBox = (function () {
'<button id="bbox-prev" class="bbox-btn" type="button" aria-label="Previous">&lt;</button>' +
'<button id="bbox-next" class="bbox-btn" type="button" aria-label="Next">&gt;</button>' +
'<div id="bbox-btns">' +
'<button id="bbox-help" type="button">?</button>' +
'<button id="bbox-vmode" type="button" tt="a"></button>' +
'<button id="bbox-close" type="button" aria-label="Close">&times;</button>' +
'<button id="bbox-close" type="button" aria-label="Close">X</button>' +
'</div></div>'
);
overlay = ctr.firstChild;
QS('body').appendChild(overlay);
tt.init();
tt.att(overlay);
}
slider = ebi('bbox-slider');
btnPrev = ebi('bbox-prev');
btnNext = ebi('bbox-next');
btnHelp = ebi('bbox-help');
btnVmode = ebi('bbox-vmode');
btnClose = ebi('bbox-close');
bindEvents();
}
function halp() {
if (ebi('bbox-halp'))
return;
var list = [
['<b># hotkey</b>', '<b># operation</b>'],
['escape', 'close'],
['left, J', 'previous file'],
['right, L', 'next file'],
['home', 'first file'],
['end', 'last file'],
['space, P, K', 'video: play / pause'],
['U', 'video: seek 10sec back'],
['P', 'video: seek 10sec ahead'],
['M', 'video: toggle mute'],
['R', 'video: toggle loop'],
['C', 'video: toggle auto-next'],
['F', 'video: toggle fullscreen'],
],
d = mknod('table'),
html = ['<tbody>'];
for (var a = 0; a < list.length; a++)
html.push('<tr><td>' + list[a][0] + '</td><td>' + list[a][1] + '</td></tr>');
d.innerHTML = html.join('\n') + '</tbody>';
d.setAttribute('id', 'bbox-halp');
d.onclick = function () {
overlay.removeChild(d);
};
overlay.appendChild(d);
}
function keyDownHandler(e) {
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
return;
@@ -240,21 +275,23 @@ window.baguetteBox = (function () {
if (!v)
return;
var msg = 'When video ends, ', lbl;
var msg = 'When video ends, ', tts = '', lbl;
if (vloop) {
lbl = 'Loop';
msg += 'repeat it';
tts = '$NHotkey: R';
}
else if (vnext) {
lbl = 'Cont';
msg += 'continue to next';
tts = '$NHotkey: C';
}
else {
lbl = 'Stop';
msg += 'just stop'
}
btnVmode.setAttribute('aria-label', msg);
btnVmode.setAttribute('tt', msg);
btnVmode.setAttribute('tt', msg + tts);
btnVmode.textContent = lbl;
v.loop = vloop
@@ -310,6 +347,7 @@ window.baguetteBox = (function () {
bind(btnNext, 'click', showNextImage);
bind(btnClose, 'click', hideOverlay);
bind(btnVmode, 'click', tglVmode);
bind(btnHelp, 'click', halp);
bind(slider, 'contextmenu', contextmenuHandler);
bind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
bind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
@@ -323,6 +361,7 @@ window.baguetteBox = (function () {
unbind(btnNext, 'click', showNextImage);
unbind(btnClose, 'click', hideOverlay);
unbind(btnVmode, 'click', tglVmode);
unbind(btnHelp, 'click', halp);
unbind(slider, 'contextmenu', contextmenuHandler);
unbind(overlay, 'touchstart', touchstartHandler, nonPassiveEvent);
unbind(overlay, 'touchmove', touchmoveHandler, passiveEvent);
@@ -435,6 +474,10 @@ window.baguetteBox = (function () {
if (options.bodyClass && document.body.classList)
document.body.classList.remove(options.bodyClass);
var h = ebi('bbox-halp');
if (h)
h.parentNode.removeChild(h);
if (options.afterHide)
options.afterHide();
@@ -491,7 +534,7 @@ window.baguetteBox = (function () {
image.addEventListener(is_vid ? 'loadedmetadata' : 'load', function () {
// Remove loader element
var spinner = document.querySelector('#baguette-img-' + index + ' .bbox-spinner');
var spinner = QS('#baguette-img-' + index + ' .bbox-spinner');
figure.removeChild(spinner);
if (!options.async && callback)
callback();

View File

@@ -211,15 +211,39 @@ a, #files tbody div a:last-child {
margin: .8em 0;
}
#srv_info {
opacity: .5;
font-size: .8em;
color: #fc5;
color: #a73;
background: #333;
position: absolute;
top: .5em;
font-size: .8em;
top: .5em;
left: 2em;
padding-right: .5em;
}
#srv_info span {
color: #fff;
color: #aaa;
}
#acc_info {
position: absolute;
font-size: .81em;
top: .5em;
right: 2em;
color: #999;
}
#acc_info span:before {
color: #f4c;
border-bottom: 1px solid rgba(255,68,204,0.6);
margin-right: .6em;
}
html.read #acc_info span:before {
content: 'Read-Only access';
}
html.write #acc_info span:before {
content: 'Write-Only access';
}
html.read.write #acc_info span:before {
content: 'Read-Write access';
color: #999;
border: none;
}
#files tbody a.play {
color: #e70;
@@ -337,10 +361,18 @@ html.light #ggrid a.sel {
height: 100%;
background: #3c3c3c;
}
#wtgrid,
#wtico {
cursor: url(/.cpr/dd/4.png), pointer;
animation: cursor 500ms;
position: relative;
top: -.06em;
}
#wtgrid {
font-size: .8em;
top: -.12em;
}
#wtgrid:hover,
#wtico:hover {
animation: cursor 500ms infinite;
}
@@ -356,9 +388,10 @@ html.light #ggrid a.sel {
}
#wtoggle {
position: absolute;
white-space: nowrap;
top: -1.2em;
right: 0;
width: 1.2em;
width: 2.5em;
height: 1em;
font-size: 2em;
line-height: 1em;
@@ -390,10 +423,10 @@ html.light #ggrid a.sel {
line-height: 1em;
}
#wtoggle.np {
width: 5.5em;
width: 6.63em;
}
#wtoggle.sel {
width: 6.4em;
width: 7.57em;
}
#wtoggle.sel #wzip,
#wtoggle.np #wnp {
@@ -476,6 +509,17 @@ html.light #ggrid a.sel {
max-width: 9em;
}
}
@media (max-width: 35em) {
#ops>a[data-dest="new_md"],
#ops>a[data-dest="msg"] {
display: none;
}
#op_mkdir.act+div,
#op_mkdir.act+div+div {
display: block;
margin-top: 1em;
}
}
@@ -773,9 +817,14 @@ input.eq_gain {
display: block;
width: 1em;
border-radius: .2em;
margin: -1.3em auto 0 auto;
margin: -1.2em auto 0 auto;
top: 2em;
position: relative;
background: #444;
}
#files th span {
position: relative;
}
#files>thead>tr>th.min,
#files td.min {
display: none;
@@ -976,10 +1025,14 @@ html.light .tgl.btn.on {
}
html.light #srv_info {
color: #c83;
background: #eee;
}
html.light #srv_info,
html.light #acc_info {
text-shadow: 1px 1px 0 #fff;
}
html.light #srv_info span {
color: #000;
color: #777;
}
html.light #treeul a+a {
background: inherit;
@@ -1249,8 +1302,7 @@ html.light #bbox-overlay figcaption a {
.bbox-btn {
position: fixed;
}
.bbox-btn,
#bbox-btns>button {
#bbox-overlay button {
cursor: pointer;
outline: none;
padding: 0 .3em;
@@ -1258,20 +1310,18 @@ html.light #bbox-overlay figcaption a {
border: 0;
border-radius: 15%;
background: rgba(50, 50, 50, 0.5);
color: #ddd;
font: 1.6em sans-serif;
color: rgba(255,255,255,0.7);
transition: background-color .3s ease;
line-height: 1em;
transition: color .3s ease;
font-size: 1.4em;
line-height: 1.4em;
vertical-align: top;
}
.bbox-btn:focus,
.bbox-btn:hover {
#bbox-overlay button:focus,
#bbox-overlay button:hover {
color: rgba(255,255,255,0.9);
background: rgba(50, 50, 50, 0.9);
}
button#bbox-vmode {
font-size: 1em;
line-height: 1.6em;
}
#bbox-next {
right: 1%;
}
@@ -1283,6 +1333,21 @@ button#bbox-vmode {
right: 2%;
position: fixed;
}
#bbox-halp {
color: #fff;
background: #333;
position: absolute;
top: 0;
left: 0;
z-index: 20;
padding: .4em;
}
#bbox-halp td {
padding: .2em .5em;
}
#bbox-halp td:first-child {
text-align: right;
}
.bbox-spinner {
width: 40px;
height: 40px;

View File

@@ -39,23 +39,23 @@
<div id="op_mkdir" class="opview opbox act">
<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">
📂<input type="text" name="name" size="30">
<input type="submit" value="make directory">
</form>
</div>
<div id="op_new_md" class="opview opbox">
<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">
📝<input type="text" name="name" size="30">
<input type="submit" value="new markdown doc">
</form>
</div>
<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">
📟<input type="text" name="msg" size="30">
<input type="submit" value="send msg to server log">
</form>
</div>
@@ -121,7 +121,8 @@
<div id="widget"></div>
<script>
var perms = {{ perms }},
var acct = "{{ acct }}",
perms = {{ perms }},
tag_order_cfg = {{ tag_order }},
have_up2k_idx = {{ have_up2k_idx|tojson }},
have_tags_idx = {{ have_tags_idx|tojson }},

View File

@@ -37,6 +37,7 @@ ebi('widget').innerHTML = (
' href="#" id="npirc" tt="copy irc-formatted track info">📋irc</a><a' +
' href="#" id="nptxt" tt="copy plaintext track info">📋txt</a>' +
'</span><a' +
' href="#" id="wtgrid">田</a><a' +
' href="#" id="wtico">♫</a>' +
'</div>' +
'<div id="widgeti">' +
@@ -149,8 +150,8 @@ ebi('op_cfg').innerHTML = (
ebi('tree').innerHTML = (
'<div id="treeh">\n' +
' <a href="#" id="detree" tt="show breadcrumbs$NHotkey: B">🍞...</a>\n' +
' <a href="#" class="btn" step="2" id="twobytwo">+</a>\n' +
' <a href="#" class="btn" step="-2" id="twig">&ndash;</a>\n' +
' <a href="#" class="btn" step="2" id="twobytwo" tt="Hotkey: A">+</a>\n' +
' <a href="#" class="btn" step="-2" id="twig" tt="Hotkey: D">&ndash;</a>\n' +
' <a href="#" class="tgl btn" id="dyntree" tt="autogrow as tree expands">a</a>\n' +
'</div>\n' +
'<ul id="treeul"></ul>\n' +
@@ -176,8 +177,10 @@ function opclick(e) {
goto(dest);
var input = QS('.opview.act input:not([type="hidden"])')
if (input && !is_touch)
if (input && !is_touch) {
tt.skip = true;
input.focus();
}
}
@@ -605,7 +608,7 @@ var widget = (function () {
m += '[' + cv + s2ms(mp.au.currentTime) + ck + '/' + cv + s2ms(mp.au.duration) + ck + ']';
var o = document.createElement('input');
var o = mknod('input');
o.style.cssText = 'position:fixed;top:45%;left:48%;padding:1em;z-index:9';
o.value = m;
document.body.appendChild(o);
@@ -1143,7 +1146,7 @@ var audio_eq = (function () {
v = parseFloat(vs);
if (isNaN(v) || v + '' != vs)
throw 42;
throw new Error('inval band');
if (isNaN(band))
r.amp = Math.round((v + step * 0.2) * 100) / 100;
@@ -1449,17 +1452,24 @@ function play_linked() {
};
(function () {
var d = mknod('div');
d.setAttribute('id', 'acc_info');
document.body.insertBefore(d, ebi('ops'));
})();
var thegrid = (function () {
var lfiles = ebi('files'),
gfiles = document.createElement('div');
gfiles = mknod('div');
gfiles.setAttribute('id', 'gfiles');
gfiles.style.display = 'none';
gfiles.innerHTML = (
'<div id="ghead">' +
'<a href="#" class="tgl btn" id="gridsel" tt="enable file selection; ctrl-click a file to override$NHotkey: S">multiselect</a> &nbsp; zoom ' +
'<a href="#" class="btn" z="-1.2" tt="Hotkey: A">&ndash;</a> ' +
'<a href="#" class="btn" z="1.2" tt="Hotkey: D">+</a> &nbsp; sort by: ' +
'<a href="#" class="btn" z="-1.2" tt="Hotkey: shift-A">&ndash;</a> ' +
'<a href="#" class="btn" z="1.2" tt="Hotkey: shift-D">+</a> &nbsp; sort by: ' +
'<a href="#" s="href">name</a>, ' +
'<a href="#" s="sz">size</a>, ' +
'<a href="#" s="ts">date</a>, ' +
@@ -1485,8 +1495,11 @@ var thegrid = (function () {
r.setdirty();
};
ebi('griden').onclick = function (e) {
ebi('griden').onclick = ebi('wtgrid').onclick = function (e) {
ev(e);
if (!this.closest)
return;
r.en = !r.en;
bcfg_set('griden', r.en);
if (r.en) {
@@ -1553,7 +1566,7 @@ var thegrid = (function () {
setsz();
function gclick(e) {
if (e && (e.ctrlKey || e.metaKey))
if (ctrl(e))
return true;
var oth = ebi(this.getAttribute('ref')),
@@ -1707,6 +1720,26 @@ var thegrid = (function () {
})();
function tree_scrollto() {
var act = QS('#treeul a.hl'),
ul = act ? act.offsetParent : null;
if (!ul)
return;
var ctr = ebi('tree'),
em = parseFloat(getComputedStyle(act).fontSize),
top = act.offsetTop + ul.offsetTop,
min = top - 11 * em,
max = top - (ctr.offsetHeight - 10 * em);
if (ctr.scrollTop > min)
ctr.scrollTop = Math.floor(min);
else if (ctr.scrollTop < max)
ctr.scrollTop = Math.floor(max);
}
function tree_neigh(n) {
var links = QSA('#treeul li>a+a');
if (!links.length) {
@@ -1732,6 +1765,7 @@ function tree_neigh(n) {
if (act >= links.length)
act = 0;
treectl.dir_cb = tree_scrollto;
links[act].click();
}
@@ -1936,7 +1970,7 @@ document.onkeydown = function (e) {
for (var b = 1; b < sconf[a].length; b++) {
var k = sconf[a][b][0],
chk = 'srch_' + k + 'c',
tvs = ebi('srch_' + k + 'v').value.split(/ /g);
tvs = ebi('srch_' + k + 'v').value.split(/ +/g);
if (!ebi(chk).checked)
continue;
@@ -1949,7 +1983,7 @@ document.onkeydown = function (e) {
q += ' and ';
if (k == 'adv') {
q += tv.replace(/ /g, " and ").replace(/([=!><]=?)/, " $1 ");
q += tv.replace(/ +/g, " and ").replace(/([=!><]=?)/, " $1 ");
continue;
}
@@ -2280,7 +2314,12 @@ var treectl = (function () {
var fun = treectl.dir_cb;
if (fun) {
treectl.dir_cb = null;
fun();
try {
fun();
}
catch (ex) {
console.log("dir_cb failed", ex);
}
}
}
@@ -2301,7 +2340,7 @@ var treectl = (function () {
}
function treego(e) {
if (e && (e.ctrlKey || e.metaKey))
if (ctrl(e))
return true;
ev(e);
@@ -2402,6 +2441,7 @@ var treectl = (function () {
if (this.hpush)
hist_push(this.top);
acct = res.acct;
apply_perms(res.perms);
despin('#files');
despin('#gfiles');
@@ -2518,9 +2558,13 @@ function despin(sel) {
function apply_perms(newperms) {
perms = newperms || [];
ebi('acc_info').innerHTML = '<span>' + (acct != '*' ?
'<a href="/?pw=x">Logout ' + acct + '</a>' :
'<a href="/?h">Login</a>') + '</span>';
var o = QSA('#ops>a[data-perm], #u2footfoot');
for (var a = 0; a < o.length; a++) {
var display = 'inline';
var display = '';
var needed = o[a].getAttribute('data-perm').split(' ');
for (var b = 0; b < needed.length; b++) {
if (!has(perms, needed[b])) {
@@ -2541,12 +2585,10 @@ function apply_perms(newperms) {
de = document.documentElement,
tds = QSA('#u2conf td');
/* good idea maybe
clmod(de, "read", have_read);
clmod(de, "write", have_write);
clmod(de, "nread", !have_read);
clmod(de, "nwrite", !have_write);
*/
for (var a = 0; a < tds.length; a++) {
tds[a].style.display =
@@ -2572,7 +2614,7 @@ function find_file_col(txt) {
for (var a = 0; a < tds.length; a++) {
var spans = tds[a].getElementsByTagName('span');
if (spans.length && spans[0].textContent == txt) {
min = tds[a].getAttribute('class').indexOf('min') !== -1;
min = (tds[a].getAttribute('class') || '').indexOf('min') !== -1;
i = a;
break;
}
@@ -2614,14 +2656,36 @@ function mk_files_header(taglist) {
var filecols = (function () {
var hidden = jread('filecols', []);
var hidden = jread('filecols', []),
tts = {
"c": "action buttons",
"dur": "duration",
"q": "quality / bitrate",
"Ac": "audio codec",
"Vc": "video codec",
"Res": "resolution",
"T": "filetype",
"aq": "audio quality / bitrate",
"vq": "video quality / bitrate",
"pixfmt": "subsampling / pixel structure",
"resw": "horizontal resolution",
"resh": "veritcal resolution",
"acs": "audio channels",
"hz": "sample rate"
};
var add_btns = function () {
var ths = QSA('#files th>span');
for (var a = 0, aa = ths.length; a < aa; a++) {
var th = ths[a].parentElement;
var th = ths[a].parentElement,
ttv = tts[ths[a].textContent];
th.innerHTML = '<div class="cfg"><a href="#">-</a></div>' + ths[a].outerHTML;
th.getElementsByTagName('a')[0].onclick = ev_row_tgl;
if (ttv) {
th.setAttribute("tt", ttv);
th.setAttribute("ttd", "u");
}
}
};
@@ -2644,7 +2708,10 @@ var filecols = (function () {
hcols = ebi('hcols');
for (var a = 0; a < hidden.length; a++) {
html.push('<a href="#" class="btn">' + esc(hidden[a]) + '</a>');
var ttv = tts[hidden[a]],
tta = ttv ? ' tt="' + ttv + '">' : '>';
html.push('<a href="#" class="btn"' + tta + esc(hidden[a]) + '</a>');
}
hcols.previousSibling.style.display = html.length ? 'block' : 'none';
hcols.innerHTML = html.join('\n');
@@ -2677,6 +2744,10 @@ var filecols = (function () {
for (var b = 0, bb = tds.length; b < bb; b++)
tds[b].setAttribute('class', cls);
}
if (window['tt']) {
tt.att(ebi('hcols'));
tt.att(QS('#files thead'));
}
};
set_style();
@@ -2867,7 +2938,7 @@ var arcfmt = (function () {
["tar", "tar", "plain gnutar file"],
["zip", "zip=utf8", "zip with utf8 filenames (maybe wonky on windows 7 and older)"],
["zip_dos", "zip", "zip with traditional cp437 filenames, for really old software"],
["zip_crc", "zip=crc", "cp437 with crc32 computed early for truly ancient software$N(takes longer to process before download can start)"]
["zip_crc", "zip=crc", "cp437 with crc32 computed early,$Nfor MS-DOS PKZIP v2.04g (october 1993)$N(takes longer to process before download can start)"]
];
for (var a = 0; a < fmts.length; a++) {
@@ -2899,7 +2970,7 @@ var arcfmt = (function () {
var ofs = href.lastIndexOf('?');
if (ofs < 0)
throw 'missing arg in url';
throw new Error('missing arg in url');
o.setAttribute("href", href.slice(0, ofs + 1) + arg);
o.textContent = fmt.split('_')[0];
@@ -3061,7 +3132,7 @@ function reload_browser(not_mp) {
var oo = QSA('#files>tbody>tr>td:nth-child(3)');
for (var a = 0, aa = oo.length; a < aa; a++) {
var sz = oo[a].textContent.replace(/ /g, ""),
var sz = oo[a].textContent.replace(/ +/g, ""),
hsz = sz.replace(/\B(?=(\d{3})+(?!\d))/g, " ");
oo[a].textContent = hsz;

View File

@@ -8,6 +8,47 @@ html, body {
font-family: sans-serif;
line-height: 1.5em;
}
#tt {
position: fixed;
max-width: 34em;
background: #222;
border: 0 solid #777;
overflow: hidden;
margin-top: 1em;
padding: 0 1.3em;
height: 0;
opacity: .1;
transition: opacity 0.14s, height 0.14s, padding 0.14s;
box-shadow: 0 .2em .5em #222;
border-radius: .4em;
z-index: 9001;
}
#tt.b {
padding: 0 2em;
border-radius: .5em;
box-shadow: 0 .2em 1em #000;
}
#tt.show {
padding: 1em 1.3em;
border-width: .4em 0;
height: auto;
opacity: 1;
}
#tt.show.b {
padding: 1.5em 2em;
border-width: .5em 0;
}
#tt code {
background: #3c3c3c;
padding: .1em .3em;
border-top: 1px solid #777;
border-radius: .3em;
font-family: monospace, monospace;
line-height: 1.7em;
}
#tt em {
color: #f6a;
}
#mtw {
display: none;
}

View File

@@ -14,9 +14,9 @@
<a id="lightswitch" href="#">go dark</a>
<a id="navtoggle" href="#">hide nav</a>
{%- if edit %}
<a id="save" href="?edit">save</a>
<a id="sbs" href="#">sbs</a>
<a id="nsbs" href="#">editor</a>
<a id="save" href="?edit" tt="Hotkey: ctrl-s">save</a>
<a id="sbs" href="#" tt="editor and preview side by side">sbs</a>
<a id="nsbs" href="#" tt="switch between editor and preview$NHotkey: ctrl-e">editor</a>
<div id="toolsbox">
<a id="tools" href="#">tools</a>
<a id="fmt_table" href="#">prettify table (ctrl-k)</a>
@@ -26,8 +26,8 @@
<a id="help" href="#">help</a>
</div>
{%- else %}
<a href="?edit">edit (basic)</a>
<a href="?edit2">edit (fancy)</a>
<a href="?edit" tt="good: higher performance$Ngood: same document width as viewer$Nbad: assumes you know markdown">edit (basic)</a>
<a href="?edit2" tt="not in-house so probably less buggy">edit (fancy)</a>
<a href="?raw">view raw</a>
{%- endif %}
</div>

View File

@@ -176,7 +176,7 @@ function md_plug_err(ex, js) {
var lns = js.split('\n');
if (ln < lns.length) {
o = mknod('span');
o.style.cssText = 'color:#ac2;font-size:.9em;font-family:scp;display:block';
o.style.cssText = "color:#ac2;font-size:.9em;font-family:'scp',monospace,monospace;display:block";
o.textContent = lns[ln - 1];
}
}
@@ -530,3 +530,6 @@ dom_navtgl.onclick = function () {
if (sread('hidenav') == 1)
dom_navtgl.onclick();
if (window['tt'])
tt.init();

View File

@@ -924,10 +924,9 @@ function cfg_uni(e) {
(function () {
function keydown(ev) {
ev = ev || window.event;
var kc = ev.keyCode || ev.which;
var ctrl = ev.ctrlKey || ev.metaKey;
//console.log(ev.code, kc);
if (ctrl && (ev.code == "KeyS" || kc == 83)) {
var kc = ev.code || ev.keyCode || ev.which;
//console.log(ev.key, ev.code, ev.keyCode, ev.which);
if (ctrl(ev) && (ev.code == "KeyS" || kc == 83)) {
save();
return false;
}
@@ -936,23 +935,15 @@ function cfg_uni(e) {
if (d)
d.click();
}
if (document.activeElement == dom_src) {
if (ev.code == "Tab" || kc == 9) {
md_indent(ev.shiftKey);
return false;
}
if (ctrl && (ev.code == "KeyH" || kc == 72)) {
if (document.activeElement != dom_src)
return true;
if (ctrl(ev)) {
if (ev.code == "KeyH" || kc == 72) {
md_header(ev.shiftKey);
return false;
}
if (!ctrl && (ev.code == "Home" || kc == 36)) {
md_home(ev.shiftKey);
return false;
}
if (!ctrl && !ev.shiftKey && (ev.code == "Enter" || kc == 13)) {
return md_newline();
}
if (ctrl && (ev.code == "KeyZ" || kc == 90)) {
if (ev.code == "KeyZ" || kc == 90) {
if (ev.shiftKey)
action_stack.redo();
else
@@ -960,33 +951,45 @@ function cfg_uni(e) {
return false;
}
if (ctrl && (ev.code == "KeyY" || kc == 89)) {
if (ev.code == "KeyY" || kc == 89) {
action_stack.redo();
return false;
}
if (!ctrl && !ev.shiftKey && kc == 8) {
return md_backspace();
}
if (ctrl && (ev.code == "KeyK")) {
if (ev.code == "KeyK") {
fmt_table();
return false;
}
if (ctrl && (ev.code == "KeyU")) {
if (ev.code == "KeyU") {
iter_uni();
return false;
}
if (ctrl && (ev.code == "KeyE")) {
if (ev.code == "KeyE") {
dom_nsbs.click();
//fmt_table();
return false;
}
var up = ev.code == "ArrowUp" || kc == 38;
var dn = ev.code == "ArrowDown" || kc == 40;
if (ctrl && (up || dn)) {
if (up || dn) {
md_p_jump(dn);
return false;
}
}
else {
if (ev.code == "Tab" || kc == 9) {
md_indent(ev.shiftKey);
return false;
}
if (ev.code == "Home" || kc == 36) {
md_home(ev.shiftKey);
return false;
}
if (!ev.shiftKey && (ev.code == "Enter" || kc == 13)) {
return md_newline();
}
if (!ev.shiftKey && kc == 8) {
return md_backspace();
}
}
}
document.onkeydown = keydown;
ebi('save').onclick = save;

View File

@@ -290,8 +290,7 @@ function U2pvis(act, btns) {
if (this.is_act(this.tab[a].in))
console.log("tab %d/%d = sz %s", a, aa, this.tab[a].bt);
console.log("a");
throw 42;
throw new Error('see console');
}
obj.innerHTML = fo.hp;
@@ -304,15 +303,8 @@ function U2pvis(act, btns) {
oldcat = fo.in,
bz_act = this.act == "bz";
if (oldcat == newcat) {
throw 42;
}
//console.log("oldcat %s %d, newcat %s %d, head=%d, tail=%d, file=%d, act.old=%s, act.new=%s, bz_act=%s",
// oldcat, this.ctr[oldcat],
// newcat, this.ctr[newcat],
// this.head, this.tail, nfile,
// this.is_act(oldcat), this.is_act(newcat), bz_act);
if (oldcat == newcat)
return;
fo.in = newcat;
this.ctr[oldcat]--;
@@ -468,6 +460,15 @@ function U2pvis(act, btns) {
}
function fsearch_explain(e) {
ev(e);
if (!has(perms, 'write'))
return alert('your access to this folder is Read-Only\n\n' + (acct == '*' ? 'you are currently not logged in' : 'you are currently logged in as ' + acct));
alert('you are currently in file-search mode\n\nswitch to upload-mode by clicking the green magnifying glass (next to the big yellow search button), and then refresh\n\nsorry');
}
function up2k_init(subtle) {
// show modal message
function showmodal(msg) {
@@ -495,8 +496,9 @@ function up2k_init(subtle) {
shame = 'your browser is impressively ancient';
// upload ui hidden by default, clicking the header shows it
var got_deps = false;
function init_deps() {
if (!subtle && !window.asmCrypto) {
if (!got_deps && !subtle && !window.asmCrypto) {
var fn = 'sha512.' + sha_js + '.js';
showmodal('<h1>loading ' + fn + '</h1><h2>since ' + shame + '</h2><h4>thanks chrome</h4>');
import_js('/.cpr/deps/' + fn, unmodal);
@@ -507,6 +509,7 @@ function up2k_init(subtle) {
ebi('u2foot').innerHTML = 'seems like ' + shame + ' so do that if you want more performance <span style="color:#' +
(sha_js == 'ac' ? 'c84">(expecting 20' : '8a5">(but dont worry too much, expect 100') + ' MiB/s)</span>';
}
got_deps = true;
}
// show uploader if the user only has write-access
@@ -1298,8 +1301,8 @@ function up2k_init(subtle) {
smsg = '';
if (!response || !response.hits || !response.hits.length) {
msg = 'not found on server';
smsg = '404';
msg = 'not found on server <a href="#" onclick="fsearch_explain()" class="fsearch_explain">(explain)</a>';
}
else {
smsg = 'found';
@@ -1570,7 +1573,7 @@ function up2k_init(subtle) {
for (var a = o.length - 1; a >= 0; a--) {
o[a].parentNode.getElementsByTagName('input')[0].setAttribute('tt', o[a].getAttribute('tt'));
}
tt.init();
tt.att(QS('#u2conf'));
function bumpthread2(e) {
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)

View File

@@ -257,6 +257,11 @@ html.light #u2foot .warn span {
float: right;
margin-bottom: -.3em;
}
.fsearch_explain {
padding-left: .7em;
font-size: 1.1em;
line-height: 0;
}

View File

@@ -7,7 +7,8 @@ if (!window['console'])
var is_touch = 'ontouchstart' in window,
ANDROID = /(android)/i.test(navigator.userAgent);
IPHONE = /iPhone|iPad|iPod/i.test(navigator.userAgent),
ANDROID = /android/i.test(navigator.userAgent);
// error handler for mobile devices
@@ -22,6 +23,9 @@ function esc(txt) {
});
}
function vis_exh(msg, url, lineNo, columnNo, error) {
if ((msg + '').indexOf('ResizeObserver') !== -1)
return; // chrome issue 809574 (benign, from <video>)
if (!window.onerror)
return;
@@ -30,17 +34,21 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
var html = ['<h1>you hit a bug!</h1><p style="font-size:1.3em;margin:0">try to <a href="#" onclick="localStorage.clear();location.reload();" style="text-decoration:underline;color:#fc0">reset copyparty settings</a> if you are stuck here</p><p>please send me a screenshot arigathanks gozaimuch: <code>ed/irc.rizon.net</code> or <code>ed#2644</code><br />&nbsp; (and if you can, press F12 and include the "Console" tab in the screenshot too)</p><p>',
esc(url + ' @' + lineNo + ':' + columnNo), '<br />' + esc(String(msg)) + '</p>'];
if (error) {
var find = ['desc', 'stack', 'trace'];
for (var a = 0; a < find.length; a++)
if (String(error[find[a]]) !== 'undefined')
html.push('<h3>' + find[a] + '</h3>' +
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
try {
if (error) {
var find = ['desc', 'stack', 'trace'];
for (var a = 0; a < find.length; a++)
if (String(error[find[a]]) !== 'undefined')
html.push('<h3>' + find[a] + '</h3>' +
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
}
html.push('<h3>localStore</h3>' + esc(JSON.stringify(localStorage)));
}
catch (e) { }
document.body.innerHTML = html.join('\n');
var s = mknod('style');
s.innerHTML = 'body{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em} h1{margin:.5em 1em 0 0;padding:0} h3{border-top:1px solid #999;margin:0} code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} *{line-height:1.5em}';
s.innerHTML = 'body{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em} h1{margin:.5em 1em 0 0;padding:0} h3{border-top:1px solid #999;margin:1em 0 0 0} code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} *{line-height:1.5em}';
document.head.appendChild(s);
throw 'fatal_err';
@@ -502,13 +510,20 @@ function hist_replace(url) {
var tt = (function () {
var r = {
"tt": mknod("div"),
"en": true
"en": true,
"el": null,
"skip": false
};
r.tt.setAttribute('id', 'tt');
document.body.appendChild(r.tt);
r.show = function () {
if (r.skip) {
r.skip = false;
return;
}
var cfg = sread('tooltips');
if (cfg !== null && cfg != '1')
return;
@@ -517,11 +532,18 @@ var tt = (function () {
if (!msg)
return;
r.el = this;
var pos = this.getBoundingClientRect(),
dir = this.getAttribute('ttd') || '',
left = pos.left < window.innerWidth / 2,
top = pos.top < window.innerHeight / 2,
big = this.className.indexOf(' ttb') !== -1;
if (dir.indexOf('u') + 1) top = false;
if (dir.indexOf('d') + 1) top = true;
if (dir.indexOf('l') + 1) left = false;
if (dir.indexOf('r') + 1) left = true;
clmod(r.tt, 'b', big);
r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
@@ -529,13 +551,45 @@ var tt = (function () {
r.tt.style.right = left ? 'auto' : (window.innerWidth - pos.right) + 'px';
r.tt.innerHTML = msg.replace(/\$N/g, "<br />");
r.el.addEventListener('mouseleave', r.hide);
clmod(r.tt, 'show', 1);
};
r.hide = function () {
r.hide = function (e) {
ev(e);
clmod(r.tt, 'show');
if (r.el)
r.el.removeEventListener('mouseleave', r.hide);
};
if (is_touch && IPHONE) {
var f1 = r.show,
f2 = r.hide;
r.show = function () {
setTimeout(f1.bind(this), 301);
};
r.hide = function () {
setTimeout(f2.bind(this), 301);
};
}
r.tt.onclick = r.hide;
r.att = function (ctr) {
var _show = r.en ? r.show : null,
_hide = r.en ? r.hide : null,
o = ctr.querySelectorAll('*[tt]');
for (var a = o.length - 1; a >= 0; a--) {
o[a].onfocus = _show;
o[a].onblur = _hide;
o[a].onmouseenter = _show;
o[a].onmouseleave = _hide;
}
r.hide();
}
r.init = function () {
var ttb = ebi('tooltips');
if (ttb) {
@@ -547,18 +601,7 @@ var tt = (function () {
};
r.en = bcfg_get('tooltips', true)
}
var _show = r.en ? r.show : null,
_hide = r.en ? r.hide : null;
var o = QSA('*[tt]');
for (var a = o.length - 1; a >= 0; a--) {
o[a].onfocus = _show;
o[a].onblur = _hide;
o[a].onmouseenter = _show;
o[a].onmouseleave = _hide;
}
r.hide();
r.att(document);
};
return r;

View File

@@ -166,7 +166,10 @@ dbg.asyncStore.pendingBreakpoints = {}
about:config >> devtools.debugger.prefs-schema-version = -1
# determine server version
git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > ../revs && cat ../{util,browser}.js >../vr && cat ../revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser}.js >../vg 2>/dev/null && diff -wNarU0 ../{vg,vr} | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done
git pull; git reset --hard origin/HEAD && git log --format=format:"%H %ai %d" --decorate=full > ../revs && cat ../{util,browser,up2k}.js >../vr && cat ../revs | while read -r rev extra; do (git reset --hard $rev >/dev/null 2>/dev/null && dsz=$(cat copyparty/web/{util,browser,up2k}.js >../vg 2>/dev/null && diff -wNarU0 ../{vg,vr} | wc -c) && printf '%s %6s %s\n' "$rev" $dsz "$extra") </dev/null; done
# download all sfx versions
curl https://api.github.com/repos/9001/copyparty/releases?per_page=100 | jq -r '.[] | .tag_name + " " + .name' | while read v t; do fn="copyparty $v $t.py"; [ -e $fn ] || curl https://github.com/9001/copyparty/releases/download/$v/copyparty-sfx.py -Lo "$fn"; done
##

View File

@@ -20,6 +20,11 @@ echo
#
# `no-cm` saves ~90k by removing easymde/codemirror
# (the fancy markdown editor)
#
# `no-fnt` saves ~9k by removing the source-code-pro font
# (mainly used my the markdown viewer/editor)
#
# `no-dd` saves ~2k by removing the mouse cursor
# port install gnutar findutils gsed coreutils
@@ -57,14 +62,18 @@ 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
[ "$1" = no-py ] && do_py= && shift && continue
break
case $1 in
clean) clean=1 ; ;;
re) repack=1 ; ;;
gz) use_gz=1 ; ;;
no-ogv) no_ogv=1 ; ;;
no-fnt) no_fnt=1 ; ;;
no-dd) no_dd=1 ; ;;
no-cm) no_cm=1 ; ;;
no-sh) do_sh= ; ;;
no-py) do_py= ; ;;
esac
shift
done
tmv() {
@@ -190,6 +199,18 @@ done
sed -r '/edit2">edit \(fancy/d' <$f >t && tmv "$f"
}
[ $no_fnt ] && {
rm -f copyparty/web/deps/scp.woff2
f=copyparty/web/md.css
sed -r '/scp\.woff2/d' <$f >t && tmv "$f"
}
[ $no_dd ] && {
rm -rf copyparty/web/dd
f=copyparty/web/browser.css
sed -r 's/(cursor: )url\([^)]+\), (pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: cursor/d' <$f >t && tmv "$f"
}
[ $repack ] ||
find | grep -E '\.py$' |
grep -vE '__version__' |

View File

@@ -380,7 +380,7 @@ def run(tmp, j2):
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except Exception as ex:
if not WINDOWS:
msg("\033[31mflock:", repr(ex))
msg("\033[31mflock:{!r}\033[0m".format(ex))
t = threading.Thread(target=utime, args=(tmp,))
t.daemon = True

View File

@@ -66,6 +66,14 @@ def get_ramdisk():
for _ in range(10):
try:
_, _ = chkcmd("diskutil", "eraseVolume", "HFS+", "cptd", devname)
with open("/Volumes/cptd/.metadata_never_index", "w") as f:
f.write("orz")
try:
shutil.rmtree("/Volumes/cptd/.fseventsd")
except:
pass
return subdir("/Volumes/cptd")
except Exception as ex:
print(repr(ex))
@@ -119,7 +127,7 @@ class VHttpConn(object):
self.addr = ("127.0.0.1", "42069")
self.args = args
self.asrv = asrv
self.is_mp = False
self.nid = None
self.log_func = log
self.log_src = "a"
self.lf_url = None