mirror of
https://github.com/9001/copyparty.git
synced 2025-11-06 14:53:17 +00:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95ea6d5f78 | ||
|
|
7d290f6b8f | ||
|
|
9db617ed5a | ||
|
|
514456940a | ||
|
|
33feefd9cd | ||
|
|
65e14cf348 | ||
|
|
1d61bcc4f3 | ||
|
|
c38bbaca3c | ||
|
|
246d245ebc | ||
|
|
f269a710e2 | ||
|
|
051998429c | ||
|
|
432cdd640f | ||
|
|
9ed9b0964e | ||
|
|
6a97b3526d | ||
|
|
451d757996 | ||
|
|
f9e9eba3b1 | ||
|
|
2a9a6aebd9 | ||
|
|
adbb6c449e | ||
|
|
3993605324 | ||
|
|
0ae574ec2c | ||
|
|
c56ded828c | ||
|
|
02c7061945 | ||
|
|
9209e44cd3 | ||
|
|
ebed37394e | ||
|
|
4c7a2a7ec3 | ||
|
|
0a25a88a34 | ||
|
|
6aa9025347 | ||
|
|
a918cc67eb | ||
|
|
08f4695283 | ||
|
|
44e76d5eeb | ||
|
|
cfa36fd279 | ||
|
|
3d4166e006 | ||
|
|
07bac1c592 | ||
|
|
755f2ce1ba | ||
|
|
cca2844deb | ||
|
|
24a2f760b7 | ||
|
|
79bbd8fe38 |
70
README.md
70
README.md
@@ -16,11 +16,6 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [thumbnails](#thumbnails) // [md-viewer](#markdown-viewer) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [ie4](#browser-support)
|
📷 **screenshots:** [browser](#the-browser) // [upload](#uploading) // [thumbnails](#thumbnails) // [md-viewer](#markdown-viewer) // [search](#searching) // [fsearch](#file-search) // [zip-DL](#zip-downloads) // [ie4](#browser-support)
|
||||||
|
|
||||||
|
|
||||||
## breaking changes \o/
|
|
||||||
|
|
||||||
this is the readme for v0.12 which has a different expression for volume permissions (`-v`); see [the v0.11.x readme](https://github.com/9001/copyparty/tree/15b59822112dda56cee576df30f331252fc62628#readme) for stuff regarding the [current stable release](https://github.com/9001/copyparty/releases/tag/v0.11.47)
|
|
||||||
|
|
||||||
|
|
||||||
## readme toc
|
## readme toc
|
||||||
|
|
||||||
* top
|
* top
|
||||||
@@ -41,6 +36,7 @@ this is the readme for v0.12 which has a different expression for volume permiss
|
|||||||
* [uploading](#uploading)
|
* [uploading](#uploading)
|
||||||
* [file-search](#file-search)
|
* [file-search](#file-search)
|
||||||
* [file manager](#file-manager)
|
* [file manager](#file-manager)
|
||||||
|
* [batch rename](#batch-rename)
|
||||||
* [markdown viewer](#markdown-viewer)
|
* [markdown viewer](#markdown-viewer)
|
||||||
* [other tricks](#other-tricks)
|
* [other tricks](#other-tricks)
|
||||||
* [searching](#searching)
|
* [searching](#searching)
|
||||||
@@ -135,12 +131,13 @@ summary: all planned features work! now please enjoy the bloatening
|
|||||||
* ☑ FUSE client (read-only)
|
* ☑ FUSE client (read-only)
|
||||||
* browser
|
* browser
|
||||||
* ☑ navpane (directory tree sidebar)
|
* ☑ navpane (directory tree sidebar)
|
||||||
|
* ☑ file manager (cut/paste, delete, batch-rename)
|
||||||
* ☑ audio player (with OS media controls)
|
* ☑ audio player (with OS media controls)
|
||||||
|
* ☑ image gallery with webm player
|
||||||
* ☑ thumbnails
|
* ☑ thumbnails
|
||||||
* ☑ ...of images using Pillow
|
* ☑ ...of images using Pillow
|
||||||
* ☑ ...of videos using FFmpeg
|
* ☑ ...of videos using FFmpeg
|
||||||
* ☑ cache eviction (max-age; maybe max-size eventually)
|
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||||
* ☑ image gallery with webm player
|
|
||||||
* ☑ SPA (browse while uploading)
|
* ☑ SPA (browse while uploading)
|
||||||
* if you use the navpane to navigate, not folders in the file list
|
* if you use the navpane to navigate, not folders in the file list
|
||||||
* server indexing
|
* server indexing
|
||||||
@@ -181,7 +178,7 @@ small collection of user feedback
|
|||||||
* this is an msys2 bug, the regular windows edition of python is fine
|
* 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
|
* 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
|
* use `--hist` or the `hist` volflag (`-v [...]:c,hist=/tmp/foo`) to place the db inside the vm instead
|
||||||
|
|
||||||
|
|
||||||
# accounts and volumes
|
# accounts and volumes
|
||||||
@@ -373,6 +370,51 @@ if you have the required permissions, you can cut/paste, rename, and delete file
|
|||||||
you can move files across browser tabs (cut in one tab, paste in another)
|
you can move files across browser tabs (cut in one tab, paste in another)
|
||||||
|
|
||||||
|
|
||||||
|
## batch rename
|
||||||
|
|
||||||
|
select some files and press F2 to bring up the rename UI
|
||||||
|
|
||||||
|
quick explanation of the buttons,
|
||||||
|
* `[✅ apply rename]` confirms and begins renaming
|
||||||
|
* `[❌ cancel]` aborts and closes the rename window
|
||||||
|
* `[↺ reset]` reverts any filename changes back to the original name
|
||||||
|
* `[decode]` does a URL-decode on the filename, fixing stuff like `&` and `%20`
|
||||||
|
* `[advanced]` toggles advanced mode
|
||||||
|
|
||||||
|
advanced mode: rename files based on rules to decide the new names, based on the original name (regex), or based on the tags collected from the file (artist/title/...), or a mix of both
|
||||||
|
|
||||||
|
in advanced mode,
|
||||||
|
* `[case]` toggles case-sensitive regex
|
||||||
|
* `regex` is the regex pattern to apply to the original filename; any files which don't match will be skipped
|
||||||
|
* `format` is the new filename, taking values from regex capturing groups and/or from file tags
|
||||||
|
* very loosely based on foobar2000 syntax
|
||||||
|
* `presets` lets you save rename rules for later
|
||||||
|
|
||||||
|
available functions:
|
||||||
|
* `$lpad(text, length, pad_char)`
|
||||||
|
* `$rpad(text, length, pad_char)`
|
||||||
|
|
||||||
|
so,
|
||||||
|
|
||||||
|
say you have a file named [`meganeko - Eclipse - 07 Sirius A.mp3`](https://www.youtube.com/watch?v=-dtb0vDPruI) (absolutely fantastic album btw) and the tags are: `Album:Eclipse`, `Artist:meganeko`, `Title:Sirius A`, `tn:7`
|
||||||
|
|
||||||
|
you could use just regex to rename it:
|
||||||
|
* `regex` = `(.*) - (.*) - ([0-9]{2}) (.*)`
|
||||||
|
* `format` = `(3). (1) - (4)`
|
||||||
|
* `output` = `07. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
or you could use just tags:
|
||||||
|
* `format` = `$lpad((tn),2,0). (artist) - (title).(ext)`
|
||||||
|
* `output` = `7. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
or a mix of both:
|
||||||
|
* `regex` = ` - ([0-9]{2}) `
|
||||||
|
* `format` = `(1). (artist) - (title).(ext)`
|
||||||
|
* `output` = `07. meganeko - Sirius A.mp3`
|
||||||
|
|
||||||
|
the metadata keys you can use in the format field are the ones in the file-browser table header (whatever is collected with `-mte` and `-mtp`)
|
||||||
|
|
||||||
|
|
||||||
## markdown viewer
|
## markdown viewer
|
||||||
|
|
||||||

|

|
||||||
@@ -415,9 +457,9 @@ through arguments:
|
|||||||
* `-e2tsr` deletes all existing tags, does 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:
|
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
|
* `-v ~/music::r:c,e2dsa:c,e2tsr` does a full reindex of everything on startup
|
||||||
* `-v ~/music::r:cd2d` disables **all** indexing, even if any `-e2*` are on
|
* `-v ~/music::r:c,d2d` disables **all** indexing, even if any `-e2*` are on
|
||||||
* `-v ~/music::r:cd2t` disables all `-e2t*` (tags), does not affect `-e2d*`
|
* `-v ~/music::r:c,d2t` disables all `-e2t*` (tags), does not affect `-e2d*`
|
||||||
|
|
||||||
note:
|
note:
|
||||||
* `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
|
* `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
|
||||||
@@ -436,7 +478,7 @@ if you set `--no-hash`, you can enable hashing for specific volumes using flag `
|
|||||||
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
|
copyparty creates a subfolder named `.hist` inside each volume where it stores the database, thumbnails, and some other stuff
|
||||||
|
|
||||||
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
|
this can instead be kept in a single place using the `--hist` argument, or the `hist=` volume flag, or a mix of both:
|
||||||
* `--hist ~/.cache/copyparty -v ~/music::r:chist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
|
* `--hist ~/.cache/copyparty -v ~/music::r:c,hist=-` sets `~/.cache/copyparty` as the default place to put volume info, but `~/music` gets the regular `.hist` subfolder (`-` restores default behavior)
|
||||||
|
|
||||||
note:
|
note:
|
||||||
* markdown edits are always stored in a local `.hist` subdirectory
|
* markdown edits are always stored in a local `.hist` subdirectory
|
||||||
@@ -447,10 +489,12 @@ note:
|
|||||||
## metadata from audio files
|
## metadata from audio files
|
||||||
|
|
||||||
`-mte` decides which tags to index and display in the browser (and also the display order), this can be changed per-volume:
|
`-mte` decides which tags to index and display in the browser (and also the display order), this can be changed per-volume:
|
||||||
* `-v ~/music::r:cmte=title,artist` indexes and displays *title* followed by *artist*
|
* `-v ~/music::r:c,mte=title,artist` indexes and displays *title* followed by *artist*
|
||||||
|
|
||||||
if you add/remove a tag from `mte` you will need to run with `-e2tsr` once to rebuild the database, otherwise only new files will be affected
|
if you add/remove a tag from `mte` you will need to run with `-e2tsr` once to rebuild the database, otherwise only new files will be affected
|
||||||
|
|
||||||
|
but instead of using `-mte`, `-mth` is a better way to hide tags in the browser: these tags will not be displayed by default, but they still get indexed and become searchable, and users can choose to unhide them in the settings pane
|
||||||
|
|
||||||
`-mtm` can be used to add or redefine a metadata mapping, say you have media files with `foo` and `bar` tags and you want them to display as `qux` in the browser (preferring `foo` if both are present), then do `-mtm qux=foo,bar` and now you can `-mte artist,title,qux`
|
`-mtm` can be used to add or redefine a metadata mapping, say you have media files with `foo` and `bar` tags and you want them to display as `qux` in the browser (preferring `foo` if both are present), then do `-mtm qux=foo,bar` and now you can `-mte artist,title,qux`
|
||||||
|
|
||||||
tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric value
|
tags that start with a `.` such as `.bpm` and `.dur`(ation) indicate numeric value
|
||||||
@@ -471,7 +515,7 @@ copyparty can invoke external programs to collect additional metadata for files
|
|||||||
|
|
||||||
* `-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 .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,`)
|
* `-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,`)
|
||||||
* `-v ~/music::r:cmtp=.bpm=~/bin/audio-bpm.py:cmtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
|
* `-v ~/music::r:c,mtp=.bpm=~/bin/audio-bpm.py:c,mtp=key=f,t5,~/bin/audio-key.py` both as a per-volume config wow this is getting ugly
|
||||||
|
|
||||||
*but wait, there's more!* `-mtp` can be used for non-audio files as well using the `a` flag: `ay` only do audio files, `an` only do non-audio files, or `ad` do all files (d as in dontcare)
|
*but wait, there's more!* `-mtp` can be used for non-audio files as well using the `a` flag: `ay` only do audio files, `an` only do non-audio files, or `ad` do all files (d as in dontcare)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ some of these rely on libraries which are not MIT-compatible
|
|||||||
|
|
||||||
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
|
* [audio-bpm.py](./audio-bpm.py) detects the BPM of music using the BeatRoot Vamp Plugin; imports GPL2
|
||||||
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
|
* [audio-key.py](./audio-key.py) detects the melodic key of music using the Mixxx fork of keyfinder; imports GPL3
|
||||||
|
* [media-hash.py](./media-hash.py) generates checksums for audio and video streams; uses FFmpeg (LGPL or GPL)
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
@@ -18,7 +19,10 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
|
|||||||
|
|
||||||
# usage from copyparty
|
# usage from copyparty
|
||||||
|
|
||||||
`copyparty -e2dsa -e2ts -mtp key=f,audio-key.py -mtp .bpm=f,audio-bpm.py`
|
`copyparty -e2dsa -e2ts` followed by any combination of these:
|
||||||
|
* `-mtp key=f,audio-key.py`
|
||||||
|
* `-mtp .bpm=f,audio-bpm.py`
|
||||||
|
* `-mtp ahash,vhash=f,media-hash.py`
|
||||||
|
|
||||||
* `f,` makes the detected value replace any existing values
|
* `f,` makes the detected value replace any existing values
|
||||||
* the `.` in `.bpm` indicates numeric value
|
* the `.` in `.bpm` indicates numeric value
|
||||||
@@ -29,6 +33,9 @@ run [`install-deps.sh`](install-deps.sh) to build/install most dependencies requ
|
|||||||
## usage with volume-flags
|
## usage with volume-flags
|
||||||
|
|
||||||
instead of affecting all volumes, you can set the options for just one volume like so:
|
instead of affecting all volumes, you can set the options for just one volume like so:
|
||||||
```
|
|
||||||
copyparty -v /mnt/nas/music:/music:r:cmtp=key=f,audio-key.py:cmtp=.bpm=f,audio-bpm.py:ce2dsa:ce2ts
|
`copyparty -v /mnt/nas/music:/music:r:c,e2dsa:c,e2ts` immediately followed by any combination of these:
|
||||||
```
|
|
||||||
|
* `:c,mtp=key=f,audio-key.py`
|
||||||
|
* `:c,mtp=.bpm=f,audio-bpm.py`
|
||||||
|
* `:c,mtp=ahash,vhash=f,media-hash.py`
|
||||||
|
|||||||
73
bin/mtag/media-hash.py
Normal file
73
bin/mtag/media-hash.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import base64
|
||||||
|
import hashlib
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
try:
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
except:
|
||||||
|
|
||||||
|
def fsenc(p):
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
dep: ffmpeg
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def det():
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-nostdin",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-i", fsenc(sys.argv[1]),
|
||||||
|
"-f", "framemd5",
|
||||||
|
"-"
|
||||||
|
]
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
p = sp.Popen(cmd, stdout=sp.PIPE)
|
||||||
|
# ps = io.TextIOWrapper(p.stdout, encoding="utf-8")
|
||||||
|
ps = p.stdout
|
||||||
|
|
||||||
|
chans = {}
|
||||||
|
for ln in ps:
|
||||||
|
if ln.startswith(b"#stream#"):
|
||||||
|
break
|
||||||
|
|
||||||
|
m = re.match(r"^#media_type ([0-9]): ([a-zA-Z])", ln.decode("utf-8"))
|
||||||
|
if m:
|
||||||
|
chans[m.group(1)] = m.group(2)
|
||||||
|
|
||||||
|
hashers = [hashlib.sha512(), hashlib.sha512()]
|
||||||
|
for ln in ps:
|
||||||
|
n = int(ln[:1])
|
||||||
|
v = ln.rsplit(b",", 1)[-1].strip()
|
||||||
|
hashers[n].update(v)
|
||||||
|
|
||||||
|
r = {}
|
||||||
|
for k, v in chans.items():
|
||||||
|
dg = hashers[int(k)].digest()[:12]
|
||||||
|
dg = base64.urlsafe_b64encode(dg).decode("ascii")
|
||||||
|
r[v[0].lower() + "hash"] = dg
|
||||||
|
|
||||||
|
print(json.dumps(r, indent=4))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
det()
|
||||||
|
except:
|
||||||
|
pass # mute
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -13,6 +13,13 @@
|
|||||||
# But note that journalctl will get the timestamps wrong due to
|
# But note that journalctl will get the timestamps wrong due to
|
||||||
# python disabling line-buffering, so messages are out-of-order:
|
# python disabling line-buffering, so messages are out-of-order:
|
||||||
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
# https://user-images.githubusercontent.com/241032/126040249-cb535cc7-c599-4931-a796-a5d9af691bad.png
|
||||||
|
#
|
||||||
|
# enable line-buffering for realtime logging (slight performance cost):
|
||||||
|
# modify ExecStart and prefix it with `/usr/bin/stdbuf -oL` like so:
|
||||||
|
# ExecStart=/usr/bin/stdbuf -oL /usr/bin/python3 [...]
|
||||||
|
# but some systemd versions require this instead (higher performance cost):
|
||||||
|
# inside the [Service] block, add the following line:
|
||||||
|
# Environment=PYTHONUNBUFFERED=x
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=copyparty file server
|
Description=copyparty file server
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from .__init__ import E, WINDOWS, VT100, PY2, unicode
|
|||||||
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
from .util import py_desc, align_tab, IMPLICATIONS
|
from .util import py_desc, align_tab, IMPLICATIONS
|
||||||
|
from .authsrv import re_vol
|
||||||
|
|
||||||
HAVE_SSL = True
|
HAVE_SSL = True
|
||||||
try:
|
try:
|
||||||
@@ -326,7 +327,7 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||||
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
ap2.add_argument("-e2ds", action="store_true", help="enable up2k db-scanner, sets -e2d")
|
||||||
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
ap2.add_argument("-e2dsa", action="store_true", help="scan all folders (for search), sets -e2ds")
|
||||||
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume state")
|
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume data (db, thumbs)")
|
||||||
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
||||||
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
|
ap2.add_argument("--re-int", metavar="SEC", type=int, default=30, help="disk rescan check interval")
|
||||||
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval (0=off)")
|
ap2.add_argument("--re-maxage", metavar="SEC", type=int, default=0, help="disk rescan volume interval (0=off)")
|
||||||
@@ -341,7 +342,9 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--no-mtag-ff", action="store_true", help="never use FFprobe as tag reader")
|
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("-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.)",
|
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")
|
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,res,.fps,ahash,vhash")
|
||||||
|
ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)",
|
||||||
|
default=".vq,.aq,vc,ac,res,.fps")
|
||||||
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('appearance options')
|
ap2 = ap.add_argument_group('appearance options')
|
||||||
@@ -396,10 +399,16 @@ def main(argv=None):
|
|||||||
nstrs = []
|
nstrs = []
|
||||||
anymod = False
|
anymod = False
|
||||||
for ostr in al.v or []:
|
for ostr in al.v or []:
|
||||||
|
m = re_vol.match(ostr)
|
||||||
|
if not m:
|
||||||
|
# not our problem
|
||||||
|
nstrs.append(ostr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
src, dst, perms = m.groups()
|
||||||
|
na = [src, dst]
|
||||||
mod = False
|
mod = False
|
||||||
oa = ostr.split(":")
|
for opt in perms.split(":"):
|
||||||
na = oa[:2]
|
|
||||||
for opt in oa[2:]:
|
|
||||||
if re.match("c[^,]", opt):
|
if re.match("c[^,]", opt):
|
||||||
mod = True
|
mod = True
|
||||||
na.append("c," + opt[1:])
|
na.append("c," + opt[1:])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (0, 12, 4)
|
VERSION = (0, 12, 11)
|
||||||
CODENAME = "fil\033[33med"
|
CODENAME = "fil\033[33med"
|
||||||
BUILD_DT = (2021, 7, 30)
|
BUILD_DT = (2021, 8, 6)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|||||||
@@ -310,6 +310,12 @@ class VFS(object):
|
|||||||
yield f
|
yield f
|
||||||
|
|
||||||
|
|
||||||
|
if WINDOWS:
|
||||||
|
re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
|
||||||
|
else:
|
||||||
|
re_vol = re.compile(r"^([^:]*):([^:]*):(.*)$")
|
||||||
|
|
||||||
|
|
||||||
class AuthSrv(object):
|
class AuthSrv(object):
|
||||||
"""verifies users against given paths"""
|
"""verifies users against given paths"""
|
||||||
|
|
||||||
@@ -319,11 +325,6 @@ class AuthSrv(object):
|
|||||||
self.warn_anonwrite = warn_anonwrite
|
self.warn_anonwrite = warn_anonwrite
|
||||||
self.line_ctr = 0
|
self.line_ctr = 0
|
||||||
|
|
||||||
if WINDOWS:
|
|
||||||
self.re_vol = re.compile(r"^([a-zA-Z]:[\\/][^:]*|[^:]*):([^:]*):(.*)$")
|
|
||||||
else:
|
|
||||||
self.re_vol = re.compile(r"^([^:]*):([^:]*):(.*)$")
|
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.reload()
|
self.reload()
|
||||||
|
|
||||||
@@ -453,7 +454,7 @@ class AuthSrv(object):
|
|||||||
# list of src:dst:permset:permset:...
|
# list of src:dst:permset:permset:...
|
||||||
# permset is <rwmd>[,username][,username] or <c>,<flag>[=args]
|
# permset is <rwmd>[,username][,username] or <c>,<flag>[=args]
|
||||||
for v_str in self.args.v:
|
for v_str in self.args.v:
|
||||||
m = self.re_vol.match(v_str)
|
m = re_vol.match(v_str)
|
||||||
if not m:
|
if not m:
|
||||||
raise Exception("invalid -v argument: [{}]".format(v_str))
|
raise Exception("invalid -v argument: [{}]".format(v_str))
|
||||||
|
|
||||||
@@ -624,9 +625,11 @@ class AuthSrv(object):
|
|||||||
if k1 in vol.flags:
|
if k1 in vol.flags:
|
||||||
vol.flags[k2] = True
|
vol.flags[k2] = True
|
||||||
|
|
||||||
# default tag-list if unset
|
# default tag cfgs if unset
|
||||||
if "mte" not in vol.flags:
|
if "mte" not in vol.flags:
|
||||||
vol.flags["mte"] = self.args.mte
|
vol.flags["mte"] = self.args.mte
|
||||||
|
if "mth" not in vol.flags:
|
||||||
|
vol.flags["mth"] = self.args.mth
|
||||||
|
|
||||||
# append parsers from argv to volume-flags
|
# append parsers from argv to volume-flags
|
||||||
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
|
self._read_volflag(vol.flags, "mtp", self.args.mtp, True)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
from copyparty.authsrv import AuthSrv
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
@@ -9,6 +8,7 @@ import threading
|
|||||||
from .broker_util import ExceptionalQueue
|
from .broker_util import ExceptionalQueue
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
from .util import FAKE_MP
|
from .util import FAKE_MP
|
||||||
|
from copyparty.authsrv import AuthSrv
|
||||||
|
|
||||||
|
|
||||||
class MpWorker(object):
|
class MpWorker(object):
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
self.uparam = uparam
|
self.uparam = uparam
|
||||||
self.cookies = cookies
|
self.cookies = cookies
|
||||||
self.vpath = unquotep(vpath)
|
self.vpath = unquotep(vpath) # not query, so + means +
|
||||||
|
|
||||||
pwd = uparam.get("pw")
|
pwd = uparam.get("pw")
|
||||||
self.uname = self.asrv.iacct.get(pwd, "*")
|
self.uname = self.asrv.iacct.get(pwd, "*")
|
||||||
@@ -1310,11 +1310,9 @@ class HttpCli(object):
|
|||||||
else:
|
else:
|
||||||
fn = self.headers.get("host", "hey")
|
fn = self.headers.get("host", "hey")
|
||||||
|
|
||||||
afn = "".join(
|
safe = (string.ascii_letters + string.digits).replace("%", "")
|
||||||
[x if x in (string.ascii_letters + string.digits) else "_" for x in fn]
|
afn = "".join([x if x in safe.replace('"', "") else "_" for x in fn])
|
||||||
)
|
bascii = unicode(safe).encode("utf-8")
|
||||||
|
|
||||||
bascii = unicode(string.ascii_letters + string.digits).encode("utf-8")
|
|
||||||
ufn = fn.encode("utf-8", "xmlcharrefreplace")
|
ufn = fn.encode("utf-8", "xmlcharrefreplace")
|
||||||
if PY2:
|
if PY2:
|
||||||
ufn = [unicode(x) if x in bascii else "%{:02x}".format(ord(x)) for x in ufn]
|
ufn = [unicode(x) if x in bascii else "%{:02x}".format(ord(x)) for x in ufn]
|
||||||
@@ -1329,6 +1327,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
|
cdis = "attachment; filename=\"{}.{}\"; filename*=UTF-8''{}.{}"
|
||||||
cdis = cdis.format(afn, fmt, ufn, fmt)
|
cdis = cdis.format(afn, fmt, ufn, fmt)
|
||||||
|
self.log(cdis)
|
||||||
self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
|
self.send_headers(None, mime=mime, headers={"Content-Disposition": cdis})
|
||||||
|
|
||||||
fgen = vn.zipgen(rem, items, self.uname, dots, not self.args.no_scandir)
|
fgen = vn.zipgen(rem, items, self.uname, dots, not self.args.no_scandir)
|
||||||
@@ -1621,6 +1620,9 @@ class HttpCli(object):
|
|||||||
if not dst:
|
if not dst:
|
||||||
raise Pebkac(400, "need dst vpath")
|
raise Pebkac(400, "need dst vpath")
|
||||||
|
|
||||||
|
# x-www-form-urlencoded (url query part) uses
|
||||||
|
# either + or %20 for 0x20 so handle both
|
||||||
|
dst = unquotep(dst.replace("+", " "))
|
||||||
x = self.conn.hsrv.broker.put(
|
x = self.conn.hsrv.broker.put(
|
||||||
True, "up2k.handle_mv", self.uname, self.vpath, dst
|
True, "up2k.handle_mv", self.uname, self.vpath, dst
|
||||||
)
|
)
|
||||||
@@ -1753,7 +1755,7 @@ class HttpCli(object):
|
|||||||
"acct": self.uname,
|
"acct": self.uname,
|
||||||
"perms": json.dumps(perms),
|
"perms": json.dumps(perms),
|
||||||
"taglist": [],
|
"taglist": [],
|
||||||
"tag_order": [],
|
"def_hcols": [],
|
||||||
"have_up2k_idx": ("e2d" in vn.flags),
|
"have_up2k_idx": ("e2d" in vn.flags),
|
||||||
"have_tags_idx": ("e2t" in vn.flags),
|
"have_tags_idx": ("e2t" in vn.flags),
|
||||||
"have_mv": (not self.args.no_mv),
|
"have_mv": (not self.args.no_mv),
|
||||||
@@ -1950,8 +1952,8 @@ class HttpCli(object):
|
|||||||
j2a["logues"] = logues
|
j2a["logues"] = logues
|
||||||
j2a["taglist"] = taglist
|
j2a["taglist"] = taglist
|
||||||
|
|
||||||
if "mte" in vn.flags:
|
if "mth" in vn.flags:
|
||||||
j2a["tag_order"] = json.dumps(vn.flags["mte"].split(","))
|
j2a["def_hcols"] = vn.flags["mth"].split(",")
|
||||||
|
|
||||||
if self.args.css_browser:
|
if self.args.css_browser:
|
||||||
j2a["css"] = self.args.css_browser
|
j2a["css"] = self.args.css_browser
|
||||||
|
|||||||
@@ -174,25 +174,26 @@ class HttpSrv(object):
|
|||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if now - (self.tp_time or now) > 300:
|
if now - (self.tp_time or now) > 300:
|
||||||
|
m = "httpserver threadpool died: tpt {:.2f}, now {:.2f}, nthr {}, ncli {}"
|
||||||
|
self.log(self.name, m.format(self.tp_time, now, self.tp_nthr, self.ncli), 1)
|
||||||
|
self.tp_time = None
|
||||||
self.tp_q = None
|
self.tp_q = None
|
||||||
|
|
||||||
if self.tp_q:
|
|
||||||
self.tp_q.put((sck, addr))
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.ncli += 1
|
self.ncli += 1
|
||||||
|
if self.tp_q:
|
||||||
self.tp_time = self.tp_time or now
|
self.tp_time = self.tp_time or now
|
||||||
self.tp_ncli = max(self.tp_ncli, self.ncli + 1)
|
self.tp_ncli = max(self.tp_ncli, self.ncli)
|
||||||
if self.tp_nthr < self.ncli + 4:
|
if self.tp_nthr < self.ncli + 4:
|
||||||
self.start_threads(8)
|
self.start_threads(8)
|
||||||
|
|
||||||
|
self.tp_q.put((sck, addr))
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.args.no_htp:
|
if not self.args.no_htp:
|
||||||
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
||||||
self.log(self.name, m, 1)
|
self.log(self.name, m, 1)
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
self.ncli += 1
|
|
||||||
|
|
||||||
thr = threading.Thread(
|
thr = threading.Thread(
|
||||||
target=self.thr_client,
|
target=self.thr_client,
|
||||||
args=(sck, addr),
|
args=(sck, addr),
|
||||||
|
|||||||
@@ -433,6 +433,14 @@ class MTag(object):
|
|||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
v = getattr(md.info, attr)
|
v = getattr(md.info, attr)
|
||||||
|
except:
|
||||||
|
if k != "ac":
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
v = str(md.info).split(".")[1]
|
||||||
|
if v.startswith("ogg"):
|
||||||
|
v = v[3:]
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ class ThumbCli(object):
|
|||||||
if is_vid and self.args.no_vthumb:
|
if is_vid and self.args.no_vthumb:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if rem.startswith(".hist/th/") and rem.split(".")[-1] in ["webp", "jpg"]:
|
||||||
|
return os.path.join(ptop, rem)
|
||||||
|
|
||||||
if fmt == "j" and self.args.th_no_jpg:
|
if fmt == "j" and self.args.th_no_jpg:
|
||||||
fmt = "w"
|
fmt = "w"
|
||||||
|
|
||||||
|
|||||||
@@ -205,8 +205,8 @@ class ThumbSrv(object):
|
|||||||
try:
|
try:
|
||||||
fun(abspath, tpath)
|
fun(abspath, tpath)
|
||||||
except:
|
except:
|
||||||
msg = "{} failed on {}\n{}"
|
msg = "{} could not create thumbnail of {}\n{}"
|
||||||
self.log(msg.format(fun.__name__, abspath, min_ex()), 3)
|
self.log(msg.format(fun.__name__, abspath, min_ex()), "1;30")
|
||||||
with open(tpath, "wb") as _:
|
with open(tpath, "wb") as _:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -286,8 +286,9 @@ class ThumbSrv(object):
|
|||||||
cmd += seek
|
cmd += seek
|
||||||
cmd += [
|
cmd += [
|
||||||
b"-i", fsenc(abspath),
|
b"-i", fsenc(abspath),
|
||||||
|
b"-map", b"0:v:0",
|
||||||
b"-vf", scale,
|
b"-vf", scale,
|
||||||
b"-vframes", b"1",
|
b"-frames:v", b"1",
|
||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
@@ -308,8 +309,9 @@ class ThumbSrv(object):
|
|||||||
|
|
||||||
ret, sout, serr = runcmd(cmd)
|
ret, sout, serr = runcmd(cmd)
|
||||||
if ret != 0:
|
if ret != 0:
|
||||||
msg = ["ff: {}".format(x) for x in serr.split("\n")]
|
m = "FFmpeg failed (probably a corrupt video file):\n"
|
||||||
self.log("FFmpeg failed:\n" + "\n".join(msg), c="1;30")
|
m += "\n".join(["ff: {}".format(x) for x in serr.split("\n")])
|
||||||
|
self.log(m, c="1;30")
|
||||||
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
raise sp.CalledProcessError(ret, (cmd[0], b"...", cmd[-1]))
|
||||||
|
|
||||||
def poke(self, tdir):
|
def poke(self, tdir):
|
||||||
|
|||||||
@@ -1405,7 +1405,7 @@ class Up2k(object):
|
|||||||
try:
|
try:
|
||||||
ptop = dbv.realpath
|
ptop = dbv.realpath
|
||||||
cur, wark, _, _, _, _ = self._find_from_vpath(ptop, volpath)
|
cur, wark, _, _, _, _ = self._find_from_vpath(ptop, volpath)
|
||||||
self._forget_file(ptop, volpath, cur, wark)
|
self._forget_file(ptop, volpath, cur, wark, True)
|
||||||
finally:
|
finally:
|
||||||
cur.connection.commit()
|
cur.connection.commit()
|
||||||
|
|
||||||
@@ -1422,8 +1422,8 @@ class Up2k(object):
|
|||||||
if not srem:
|
if not srem:
|
||||||
raise Pebkac(400, "mv: cannot move a mountpoint")
|
raise Pebkac(400, "mv: cannot move a mountpoint")
|
||||||
|
|
||||||
st = bos.stat(sabs)
|
st = bos.lstat(sabs)
|
||||||
if stat.S_ISREG(st.st_mode):
|
if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode):
|
||||||
return self._mv_file(uname, svp, dvp)
|
return self._mv_file(uname, svp, dvp)
|
||||||
|
|
||||||
jail = svn.get_dbv(srem)[0]
|
jail = svn.get_dbv(srem)[0]
|
||||||
@@ -1491,10 +1491,10 @@ class Up2k(object):
|
|||||||
fsize = st.st_size
|
fsize = st.st_size
|
||||||
|
|
||||||
if w:
|
if w:
|
||||||
if c2:
|
if c2 and c2 != c1:
|
||||||
self._copy_tags(c1, c2, w)
|
self._copy_tags(c1, c2, w)
|
||||||
|
|
||||||
self._forget_file(svn.realpath, srem, c1, w)
|
self._forget_file(svn.realpath, srem, c1, w, c1 != c2)
|
||||||
self._relink(w, svn.realpath, srem, dabs)
|
self._relink(w, svn.realpath, srem, dabs)
|
||||||
c1.connection.commit()
|
c1.connection.commit()
|
||||||
|
|
||||||
@@ -1535,14 +1535,16 @@ class Up2k(object):
|
|||||||
return cur, wark, ftime, fsize, ip, at
|
return cur, wark, ftime, fsize, ip, at
|
||||||
return cur, None, None, None, None, None
|
return cur, None, None, None, None, None
|
||||||
|
|
||||||
def _forget_file(self, ptop, vrem, cur, wark):
|
def _forget_file(self, ptop, vrem, cur, wark, drop_tags):
|
||||||
"""forgets file in db, fixes symlinks, does not delete"""
|
"""forgets file in db, fixes symlinks, does not delete"""
|
||||||
srd, sfn = vsplit(vrem)
|
srd, sfn = vsplit(vrem)
|
||||||
self.log("forgetting {}".format(vrem))
|
self.log("forgetting {}".format(vrem))
|
||||||
if wark:
|
if wark:
|
||||||
self.log("found {} in db".format(wark))
|
self.log("found {} in db".format(wark))
|
||||||
self._relink(wark, ptop, vrem, None)
|
if self._relink(wark, ptop, vrem, None):
|
||||||
|
drop_tags = False
|
||||||
|
|
||||||
|
if drop_tags:
|
||||||
q = "delete from mt where w=?"
|
q = "delete from mt where w=?"
|
||||||
cur.execute(q, (wark[:16],))
|
cur.execute(q, (wark[:16],))
|
||||||
self.db_rm(cur, srd, sfn)
|
self.db_rm(cur, srd, sfn)
|
||||||
@@ -1581,7 +1583,7 @@ class Up2k(object):
|
|||||||
self.log("found {} dupe: [{}] {}".format(wark, ptop, dvrem))
|
self.log("found {} dupe: [{}] {}".format(wark, ptop, dvrem))
|
||||||
|
|
||||||
if not dupes:
|
if not dupes:
|
||||||
return
|
return 0
|
||||||
|
|
||||||
full = {}
|
full = {}
|
||||||
links = {}
|
links = {}
|
||||||
@@ -1618,6 +1620,8 @@ class Up2k(object):
|
|||||||
|
|
||||||
self._symlink(dabs, alink, False)
|
self._symlink(dabs, alink, False)
|
||||||
|
|
||||||
|
return len(full) + len(links)
|
||||||
|
|
||||||
def _get_wark(self, cj):
|
def _get_wark(self, cj):
|
||||||
if len(cj["name"]) > 1024 or len(cj["hash"]) > 512 * 1024: # 16TiB
|
if len(cj["name"]) > 1024 or len(cj["hash"]) > 512 * 1024: # 16TiB
|
||||||
raise Pebkac(400, "name or numchunks not according to spec")
|
raise Pebkac(400, "name or numchunks not according to spec")
|
||||||
|
|||||||
@@ -22,9 +22,6 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
pre, code, tt {
|
pre, code, tt {
|
||||||
font-family: monospace, monospace;
|
font-family: monospace, monospace;
|
||||||
}
|
}
|
||||||
@@ -49,7 +46,7 @@ pre, code, tt {
|
|||||||
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
||||||
}
|
}
|
||||||
#toast {
|
#toast {
|
||||||
top: 1.4em;
|
bottom: 5em;
|
||||||
right: -1em;
|
right: -1em;
|
||||||
line-height: 1.5em;
|
line-height: 1.5em;
|
||||||
padding: 1em 1.3em;
|
padding: 1em 1.3em;
|
||||||
@@ -810,6 +807,7 @@ input.eq_gain {
|
|||||||
#wrap {
|
#wrap {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
min-height: 90vh;
|
min-height: 90vh;
|
||||||
|
padding-bottom: 5em;
|
||||||
}
|
}
|
||||||
#tree {
|
#tree {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -1068,10 +1066,68 @@ html.light #ggrid a:hover {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
#rui {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: calc(100% - 2em);
|
||||||
|
height: auto;
|
||||||
|
overflow: auto;
|
||||||
|
max-height: calc(100% - 2em);
|
||||||
|
border-bottom: .5em solid #999;
|
||||||
|
background: #333;
|
||||||
|
padding: 1em;
|
||||||
|
z-index: 765;
|
||||||
|
}
|
||||||
|
html.light #rui {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#rui div+div {
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
#rui table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
#rui td+td {
|
||||||
|
padding: .2em 0 .2em .5em;
|
||||||
|
}
|
||||||
|
#rn_vadv input {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
}
|
||||||
|
#rui td+td,
|
||||||
|
#rui td input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#rn_f.m td:first-child {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#rn_f.m td+td {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
#rn_f .err td {
|
||||||
|
background: #c00;
|
||||||
|
}
|
||||||
|
#rn_f .err input[readonly] {
|
||||||
|
background: #600;
|
||||||
|
color: #fc0;
|
||||||
|
}
|
||||||
|
#rui input[readonly] {
|
||||||
|
color: #fff;
|
||||||
|
background: #444;
|
||||||
|
border: 1px solid #777;
|
||||||
|
padding: .2em .25em;
|
||||||
|
}
|
||||||
|
#rui h1 {
|
||||||
|
margin: 0 0 .3em 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
#pvol,
|
#pvol,
|
||||||
#barbuf,
|
#barbuf,
|
||||||
#barpos,
|
#barpos,
|
||||||
#u2conf label,
|
#u2conf label,
|
||||||
|
#rui label,
|
||||||
#ops {
|
#ops {
|
||||||
-webkit-user-select: none;
|
-webkit-user-select: none;
|
||||||
-moz-user-select: none;
|
-moz-user-select: none;
|
||||||
|
|||||||
@@ -125,7 +125,7 @@
|
|||||||
<script>
|
<script>
|
||||||
var acct = "{{ acct }}",
|
var acct = "{{ acct }}",
|
||||||
perms = {{ perms }},
|
perms = {{ perms }},
|
||||||
tag_order_cfg = {{ tag_order }},
|
def_hcols = {{ def_hcols|tojson }},
|
||||||
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
have_up2k_idx = {{ have_up2k_idx|tojson }},
|
||||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||||
have_mv = {{ have_mv|tojson }},
|
have_mv = {{ have_mv|tojson }},
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ ebi('op_cfg').innerHTML = (
|
|||||||
' <div>\n' +
|
' <div>\n' +
|
||||||
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔">ℹ️ tooltips</a>\n' +
|
' <a id="tooltips" class="tgl btn" href="#" tt="◔ ◡ ◔">ℹ️ tooltips</a>\n' +
|
||||||
' <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>\n' +
|
' <a id="lightmode" class="tgl btn" href="#">☀️ lightmode</a>\n' +
|
||||||
|
' <a id="dotfiles" class="tgl btn" href="#" tt="show hidden files (if server permits)">dotfiles</a>\n' +
|
||||||
' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' +
|
' <a id="griden" class="tgl btn" href="#" tt="toggle icons or list-view$NHotkey: G">田 the grid</a>\n' +
|
||||||
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
|
' <a id="thumbs" class="tgl btn" href="#" tt="in icon view, toggle icons or thumbnails$NHotkey: T">🖼️ thumbs</a>\n' +
|
||||||
' </div>\n' +
|
' </div>\n' +
|
||||||
@@ -521,15 +522,14 @@ var mp = new MPlayer();
|
|||||||
makeSortable(ebi('files'), mp.read_order.bind(mp));
|
makeSortable(ebi('files'), mp.read_order.bind(mp));
|
||||||
|
|
||||||
|
|
||||||
function get_np() {
|
function ft2dict(tr) {
|
||||||
var th = ebi('files').tHead.rows[0].cells,
|
var th = ebi('files').tHead.rows[0].cells,
|
||||||
tr = QS('#files tr.play').cells,
|
|
||||||
rv = [],
|
rv = [],
|
||||||
ra = [],
|
ra = [],
|
||||||
rt = {};
|
rt = {};
|
||||||
|
|
||||||
for (var a = 1, aa = th.length; a < aa; a++) {
|
for (var a = 1, aa = th.length; a < aa; a++) {
|
||||||
var tv = tr[a].textContent,
|
var tv = tr.cells[a].textContent,
|
||||||
tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').slice(-1)[0],
|
tk = a == 1 ? 'file' : th[a].getAttribute('name').split('/').slice(-1)[0],
|
||||||
vis = th[a].className.indexOf('min') === -1;
|
vis = th[a].className.indexOf('min') === -1;
|
||||||
|
|
||||||
@@ -540,6 +540,12 @@ function get_np() {
|
|||||||
rt[tk] = tv;
|
rt[tk] = tv;
|
||||||
}
|
}
|
||||||
return [rt, rv, ra];
|
return [rt, rv, ra];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function get_np() {
|
||||||
|
var tr = QS('#files tr.play');
|
||||||
|
return ft2dict(tr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -1454,6 +1460,105 @@ function play_linked() {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function fmt_ren(re, md, fmt) {
|
||||||
|
var ptr = 0;
|
||||||
|
function dive(stop_ch) {
|
||||||
|
var ret = '', ng = 0;
|
||||||
|
while (ptr < fmt.length) {
|
||||||
|
var dbg = fmt.slice(ptr),
|
||||||
|
ch = fmt[ptr++];
|
||||||
|
|
||||||
|
if (ch == '\\') {
|
||||||
|
ret += fmt[ptr++];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ch == ')' || ch == ']' || ch == stop_ch)
|
||||||
|
return [ng, ret];
|
||||||
|
|
||||||
|
if (ch == '[') {
|
||||||
|
var r2 = dive();
|
||||||
|
if (r2[0] == 0)
|
||||||
|
ret += r2[1];
|
||||||
|
}
|
||||||
|
else if (ch == '(') {
|
||||||
|
var end = fmt.indexOf(')', ptr);
|
||||||
|
if (end < 0)
|
||||||
|
throw 'the ( was never closed: ' + fmt.slice(0, ptr);
|
||||||
|
|
||||||
|
var arg = fmt.slice(ptr, end), v = null;
|
||||||
|
ptr = end + 1;
|
||||||
|
|
||||||
|
if (arg != parseInt(arg))
|
||||||
|
v = md[arg];
|
||||||
|
else {
|
||||||
|
arg = parseInt(arg);
|
||||||
|
if (arg >= re.length)
|
||||||
|
throw 'matching group ' + arg + ' exceeds ' + (re.length - 0);
|
||||||
|
|
||||||
|
v = re[arg];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v !== null && v !== undefined)
|
||||||
|
ret += v;
|
||||||
|
else
|
||||||
|
ng++;
|
||||||
|
}
|
||||||
|
else if (ch == '$') {
|
||||||
|
ch = fmt[ptr++];
|
||||||
|
var end = fmt.indexOf('(', ptr);
|
||||||
|
if (end < 0)
|
||||||
|
throw 'no function name after the $ here: ' + fmt.slice(0, ptr);
|
||||||
|
|
||||||
|
var fun = fmt.slice(ptr - 1, end);
|
||||||
|
ptr = end + 1;
|
||||||
|
|
||||||
|
if (fun == "lpad") {
|
||||||
|
var str = dive(',')[1];
|
||||||
|
var len = dive(',')[1];
|
||||||
|
var chr = dive()[1];
|
||||||
|
if (!len || !chr)
|
||||||
|
throw 'invalid arguments to ' + fun;
|
||||||
|
|
||||||
|
if (!str.length)
|
||||||
|
ng += 1;
|
||||||
|
|
||||||
|
while (str.length < len)
|
||||||
|
str = chr + str;
|
||||||
|
|
||||||
|
ret += str;
|
||||||
|
}
|
||||||
|
else if (fun == "rpad") {
|
||||||
|
var str = dive(',')[1];
|
||||||
|
var len = dive(',')[1];
|
||||||
|
var chr = dive()[1];
|
||||||
|
if (!len || !chr)
|
||||||
|
throw 'invalid arguments to ' + fun;
|
||||||
|
|
||||||
|
if (!str.length)
|
||||||
|
ng += 1;
|
||||||
|
|
||||||
|
while (str.length < len)
|
||||||
|
str += chr;
|
||||||
|
|
||||||
|
ret += str;
|
||||||
|
}
|
||||||
|
else throw 'function not implemented: "' + fun + '"';
|
||||||
|
}
|
||||||
|
else ret += ch;
|
||||||
|
}
|
||||||
|
return [ng, ret];
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return [true, dive()[1]];
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return [false, ex];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var fileman = (function () {
|
var fileman = (function () {
|
||||||
var bren = ebi('fren'),
|
var bren = ebi('fren'),
|
||||||
bdel = ebi('fdel'),
|
bdel = ebi('fdel'),
|
||||||
@@ -1468,10 +1573,10 @@ var fileman = (function () {
|
|||||||
if (r.clip === null)
|
if (r.clip === null)
|
||||||
r.clip = jread('fman_clip', []);
|
r.clip = jread('fman_clip', []);
|
||||||
|
|
||||||
var sel = msel.getsel();
|
var nsel = msel.getsel().length;
|
||||||
clmod(bren, 'en', sel.length == 1);
|
clmod(bren, 'en', nsel == 1);
|
||||||
clmod(bdel, 'en', sel.length);
|
clmod(bdel, 'en', nsel);
|
||||||
clmod(bcut, 'en', sel.length);
|
clmod(bcut, 'en', nsel);
|
||||||
clmod(bpst, 'en', r.clip && r.clip.length);
|
clmod(bpst, 'en', r.clip && r.clip.length);
|
||||||
bren.style.display = have_mv && has(perms, 'write') && has(perms, 'move') ? '' : 'none';
|
bren.style.display = have_mv && has(perms, 'write') && has(perms, 'move') ? '' : 'none';
|
||||||
bdel.style.display = have_del && has(perms, 'delete') ? '' : 'none';
|
bdel.style.display = have_del && has(perms, 'delete') ? '' : 'none';
|
||||||
@@ -1487,22 +1592,269 @@ var fileman = (function () {
|
|||||||
return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
|
return toast.err(3, 'cannot rename:\nyou do not have “move” permission in this folder');
|
||||||
|
|
||||||
var sel = msel.getsel();
|
var sel = msel.getsel();
|
||||||
if (sel.length !== 1)
|
if (!sel.length)
|
||||||
return toast.err(3, 'select exactly 1 item to rename');
|
return toast.err(3, 'select at least one item to rename');
|
||||||
|
|
||||||
var src = sel[0].vp;
|
var f = [],
|
||||||
if (src.endsWith('/'))
|
base = vsplit(sel[0].vp)[0],
|
||||||
src = src.slice(0, -1);
|
mkeys;
|
||||||
|
|
||||||
var vsp = vsplit(src),
|
for (var a = 0; a < sel.length; a++) {
|
||||||
base = vsp[0],
|
var vp = sel[a].vp;
|
||||||
ofn = vsp[1];
|
if (vp.endsWith('/'))
|
||||||
|
vp = vp.slice(0, -1);
|
||||||
|
|
||||||
var fn = prompt('new filename:', ofn);
|
var vsp = vsplit(vp);
|
||||||
if (!fn || fn == ofn)
|
if (base != vsp[0])
|
||||||
return toast.warn(1, 'rename aborted');
|
return toast.err(0, 'bug:\n' + base + '\n' + vsp[0]);
|
||||||
|
|
||||||
var dst = base + fn;
|
var vars = ft2dict(ebi(sel[a].id).closest('tr'));
|
||||||
|
mkeys = vars[1].concat(vars[2]);
|
||||||
|
|
||||||
|
var md = vars[0];
|
||||||
|
for (var k in md) {
|
||||||
|
if (!md.hasOwnProperty(k))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
md[k.toLowerCase()] = md[k];
|
||||||
|
k = k.toLowerCase();
|
||||||
|
|
||||||
|
if (k.startsWith('.'))
|
||||||
|
md[k.slice(1)] = md[k];
|
||||||
|
}
|
||||||
|
md.t = md.ext;
|
||||||
|
md.date = md.ts;
|
||||||
|
md.size = md.sz;
|
||||||
|
|
||||||
|
f.push({
|
||||||
|
"src": vp,
|
||||||
|
"ofn": uricom_dec(vsp[1])[0],
|
||||||
|
"md": vars[0],
|
||||||
|
"ok": true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var rui = ebi('rui');
|
||||||
|
if (!rui) {
|
||||||
|
rui = mknod('div');
|
||||||
|
rui.setAttribute('id', 'rui');
|
||||||
|
document.body.appendChild(rui);
|
||||||
|
}
|
||||||
|
|
||||||
|
var html = sel.length > 1 ? ['<div>'] : [
|
||||||
|
'<div>',
|
||||||
|
'<button class="rn_dec" n="0" tt="may fix some cases of broken filenames">url-decode</button>',
|
||||||
|
'//',
|
||||||
|
'<button class="rn_reset" n="0" tt="reset modified filenames back to the original ones">↺ reset</button>'
|
||||||
|
];
|
||||||
|
|
||||||
|
html = html.concat([
|
||||||
|
'<button id="rn_cancel" tt="abort and close this window">❌ cancel</button>',
|
||||||
|
'<button id="rn_apply">✅ apply rename</button>',
|
||||||
|
'<a id="rn_adv" class="tgl btn" href="#" tt="batch / metadata / pattern renaming">advanced</a>',
|
||||||
|
'<a id="rn_case" class="tgl btn" href="#" tt="case-sensitive regex">case</a>',
|
||||||
|
'</div>',
|
||||||
|
'<div id="rn_vadv"><table>',
|
||||||
|
'<tr><td>regex</td><td><input type="text" id="rn_re" tt="regex search pattern to apply to original filenames; capturing groups can be referenced in the format field below like <code>(1)</code> and <code>(2)</code> and so on" /></td></tr>',
|
||||||
|
'<tr><td>format</td><td><input type="text" id="rn_fmt" tt="inspired by foobar2000:$N<code>(title)</code> is replaced by song title,$N<code>[(artist) - ](title)</code> skips the first part if artist is blank$N<code>$lpad((tn),2,0)</code> pads tracknumber to 2 digits" /></td></tr>',
|
||||||
|
'<tr><td>preset</td><td><select id="rn_pre"></select>',
|
||||||
|
'<button id="rn_pdel">❌ delete</button>',
|
||||||
|
'<button id="rn_pnew">💾 save as</button>',
|
||||||
|
'</td></tr>',
|
||||||
|
'</table></div>'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (sel.length == 1)
|
||||||
|
html.push(
|
||||||
|
'<div><table id="rn_f">\n' +
|
||||||
|
'<tr><td>old:</td><td><input type="text" id="rn_old" n="0" readonly /></td></tr>\n' +
|
||||||
|
'<tr><td>new:</td><td><input type="text" id="rn_new" n="0" /></td></tr>');
|
||||||
|
else {
|
||||||
|
html.push(
|
||||||
|
'<div><table id="rn_f" class="m">' +
|
||||||
|
'<tr><td></td><td>new name</td><td>old name</td></tr>');
|
||||||
|
for (var a = 0; a < f.length; a++)
|
||||||
|
html.push(
|
||||||
|
'<tr><td>' +
|
||||||
|
'<button class="rn_dec" n="' + a + '">decode</button>',
|
||||||
|
'<button class="rn_reset" n="' + a + '">↺ reset</button></td>',
|
||||||
|
'<td><input type="text" id="rn_new" n="' + a + '" /></td>' +
|
||||||
|
'<td><input type="text" id="rn_old" n="' + a + '" readonly /></td></tr>');
|
||||||
|
}
|
||||||
|
html.push('</table></div>');
|
||||||
|
|
||||||
|
if (sel.length == 1) {
|
||||||
|
html.push('<div><p style="margin:.6em 0">tags for the selected file (read-only, just for reference):</p><table>');
|
||||||
|
for (var a = 0; a < mkeys.length; a++)
|
||||||
|
html.push('<tr><td>' + esc(mkeys[a]) + '</td><td><input type="text" readonly value="' + esc(f[0].md[mkeys[a]]) + '" /></td></tr>');
|
||||||
|
|
||||||
|
html.push('</table></div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
rui.innerHTML = html.join('\n');
|
||||||
|
for (var a = 0; a < f.length; a++) {
|
||||||
|
var k = '[n="' + a + '"]';
|
||||||
|
f[a].iold = QS('#rn_old' + k);
|
||||||
|
f[a].inew = QS('#rn_new' + k);
|
||||||
|
f[a].inew.value = f[a].iold.value = f[a].ofn;
|
||||||
|
|
||||||
|
(function (a) {
|
||||||
|
f[a].inew.onkeydown = function (e) {
|
||||||
|
rn_ok(a, true);
|
||||||
|
|
||||||
|
if (e.key == 'Escape')
|
||||||
|
return rn_cancel();
|
||||||
|
|
||||||
|
if (e.key == 'Enter')
|
||||||
|
return rn_apply();
|
||||||
|
};
|
||||||
|
QS('.rn_dec' + k).onclick = function () {
|
||||||
|
f[a].inew.value = uricom_dec(f[a].inew.value)[0];
|
||||||
|
};
|
||||||
|
QS('.rn_reset' + k).onclick = function () {
|
||||||
|
rn_reset(a);
|
||||||
|
};
|
||||||
|
})(a);
|
||||||
|
}
|
||||||
|
rn_reset(0);
|
||||||
|
tt.att(rui);
|
||||||
|
|
||||||
|
var adv = bcfg_get('rn_adv', false),
|
||||||
|
cs = bcfg_get('rn_case', false);
|
||||||
|
|
||||||
|
function sadv() {
|
||||||
|
ebi('rn_vadv').style.display = ebi('rn_case').style.display = adv ? '' : 'none';
|
||||||
|
}
|
||||||
|
sadv();
|
||||||
|
|
||||||
|
function rn_ok(n, ok) {
|
||||||
|
f[n].ok = ok;
|
||||||
|
clmod(f[n].inew.closest('tr'), 'err', !ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rn_reset(n) {
|
||||||
|
f[n].inew.value = f[n].iold.value = f[n].ofn;
|
||||||
|
f[n].inew.focus();
|
||||||
|
f[n].inew.setSelectionRange(0, f[n].inew.value.lastIndexOf('.'), "forward");
|
||||||
|
}
|
||||||
|
function rn_cancel() {
|
||||||
|
rui.parentNode.removeChild(rui);
|
||||||
|
}
|
||||||
|
|
||||||
|
ebi('rn_cancel').onclick = rn_cancel;
|
||||||
|
ebi('rn_apply').onclick = rn_apply;
|
||||||
|
ebi('rn_adv').onclick = function () {
|
||||||
|
adv = !adv;
|
||||||
|
bcfg_set('rn_adv', adv);
|
||||||
|
sadv();
|
||||||
|
};
|
||||||
|
ebi('rn_case').onclick = function () {
|
||||||
|
cs = !cs;
|
||||||
|
bcfg_set('rn_case', cs);
|
||||||
|
};
|
||||||
|
|
||||||
|
var ire = ebi('rn_re'),
|
||||||
|
ifmt = ebi('rn_fmt'),
|
||||||
|
ipre = ebi('rn_pre'),
|
||||||
|
idel = ebi('rn_pdel'),
|
||||||
|
inew = ebi('rn_pnew'),
|
||||||
|
defp = '$lpad((tn),2,0). [(artist) - ](title).(ext)';
|
||||||
|
|
||||||
|
var presets = {};
|
||||||
|
presets[defp] = ['', defp];
|
||||||
|
presets = jread("rn_pre", presets);
|
||||||
|
|
||||||
|
function spresets() {
|
||||||
|
var keys = Object.keys(presets), o;
|
||||||
|
keys.sort();
|
||||||
|
ipre.innerHTML = '<option value=""></option>';
|
||||||
|
for (var a = 0; a < keys.length; a++) {
|
||||||
|
o = mknod('option');
|
||||||
|
o.setAttribute('value', keys[a]);
|
||||||
|
o.textContent = keys[a];
|
||||||
|
ipre.appendChild(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inew.onclick = function () {
|
||||||
|
var name = prompt('provide a name for your new preset', ifmt.value);
|
||||||
|
if (!name)
|
||||||
|
return toast.warn(3, 'aborted');
|
||||||
|
|
||||||
|
presets[name] = [ire.value, ifmt.value];
|
||||||
|
jwrite('rn_pre', presets);
|
||||||
|
spresets();
|
||||||
|
ipre.value = name;
|
||||||
|
};
|
||||||
|
idel.onclick = function () {
|
||||||
|
delete presets[ipre.value];
|
||||||
|
jwrite('rn_pre', presets);
|
||||||
|
spresets();
|
||||||
|
};
|
||||||
|
ipre.oninput = function () {
|
||||||
|
var cfg = presets[ipre.value];
|
||||||
|
if (cfg) {
|
||||||
|
ire.value = cfg[0];
|
||||||
|
ifmt.value = cfg[1];
|
||||||
|
}
|
||||||
|
ifmt.oninput();
|
||||||
|
};
|
||||||
|
spresets();
|
||||||
|
|
||||||
|
ire.onkeydown = ifmt.onkeydown = function (e) {
|
||||||
|
if (e.key == 'Escape')
|
||||||
|
return rn_cancel();
|
||||||
|
|
||||||
|
if (e.key == 'Enter')
|
||||||
|
return rn_apply();
|
||||||
|
};
|
||||||
|
|
||||||
|
ire.oninput = ifmt.oninput = function (e) {
|
||||||
|
var ptn = ire.value,
|
||||||
|
fmt = ifmt.value,
|
||||||
|
re = null;
|
||||||
|
|
||||||
|
if (!fmt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (ptn)
|
||||||
|
re = new RegExp(ptn, cs ? 'i' : '');
|
||||||
|
}
|
||||||
|
catch (ex) {
|
||||||
|
return toast.err(5, 'invalid regex:\n' + ex);
|
||||||
|
}
|
||||||
|
toast.hide();
|
||||||
|
|
||||||
|
for (var a = 0; a < f.length; a++) {
|
||||||
|
var m = re ? re.exec(f[a].ofn) : null,
|
||||||
|
ok, txt = '';
|
||||||
|
|
||||||
|
if (re && !m) {
|
||||||
|
txt = 'regex did not match';
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var ret = fmt_ren(m, f[a].md, fmt);
|
||||||
|
ok = ret[0];
|
||||||
|
txt = ret[1];
|
||||||
|
}
|
||||||
|
rn_ok(a, ok);
|
||||||
|
f[a].inew.value = (ok ? '' : 'ERROR: ') + txt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function rn_apply() {
|
||||||
|
while (f.length && (!f[0].ok || f[0].ofn == f[0].inew.value))
|
||||||
|
f.shift();
|
||||||
|
|
||||||
|
if (!f.length) {
|
||||||
|
toast.ok(2, 'rename OK');
|
||||||
|
treectl.goto(get_evpath());
|
||||||
|
return rn_cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.inf(0, 'renaming ' + f.length + ' items\n\n' + f[0].ofn);
|
||||||
|
var dst = base + uricom_enc(f[0].inew.value, false);
|
||||||
|
|
||||||
function rename_cb() {
|
function rename_cb() {
|
||||||
if (this.readyState != XMLHttpRequest.DONE)
|
if (this.readyState != XMLHttpRequest.DONE)
|
||||||
@@ -1513,13 +1865,16 @@ var fileman = (function () {
|
|||||||
toast.err(9, 'rename failed:\n' + msg);
|
toast.err(9, 'rename failed:\n' + msg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.ok(2, 'rename OK');
|
|
||||||
treectl.goto(get_evpath());
|
f.shift().inew.value = '( OK )';
|
||||||
|
return rn_apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.open('GET', src + '?move=' + dst, true);
|
xhr.open('GET', f[0].src + '?move=' + dst, true);
|
||||||
xhr.onreadystatechange = rename_cb;
|
xhr.onreadystatechange = rename_cb;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
r.delete = function (e) {
|
r.delete = function (e) {
|
||||||
@@ -1611,7 +1966,7 @@ var fileman = (function () {
|
|||||||
links = QSA('#files tbody td:nth-child(2) a');
|
links = QSA('#files tbody td:nth-child(2) a');
|
||||||
|
|
||||||
for (var a = 0, aa = links.length; a < aa; a++)
|
for (var a = 0, aa = links.length; a < aa; a++)
|
||||||
indir.push(links[a].getAttribute('name'));
|
indir.push(vsplit(links[a].getAttribute('href'))[1]);
|
||||||
|
|
||||||
for (var a = 0; a < r.clip.length; a++) {
|
for (var a = 0; a < r.clip.length; a++) {
|
||||||
var found = false;
|
var found = false;
|
||||||
@@ -1626,12 +1981,12 @@ var fileman = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (exists.length)
|
if (exists.length)
|
||||||
alert('these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + exists.join('\n'));
|
alert('these ' + exists.length + ' items cannot be pasted here (names already exist):\n\n' + uricom_adec(exists).join('\n'));
|
||||||
|
|
||||||
if (!req.length)
|
if (!req.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!confirm('paste these ' + req.length + ' items here?\n\n' + req.join('\n')))
|
if (!confirm('paste these ' + req.length + ' items here?\n\n' + uricom_adec(req).join('\n')))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
function paster() {
|
function paster() {
|
||||||
@@ -1644,7 +1999,7 @@ var fileman = (function () {
|
|||||||
r.tx(srcdir);
|
r.tx(srcdir);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
toast.inf(0, 'pasting ' + (req.length + 1) + ' items\n\n' + vp);
|
toast.inf(0, 'pasting ' + (req.length + 1) + ' items\n\n' + uricom_dec(vp)[0]);
|
||||||
|
|
||||||
var dst = get_evpath() + vp.split('/').slice(-1)[0];
|
var dst = get_evpath() + vp.split('/').slice(-1)[0];
|
||||||
|
|
||||||
@@ -2415,6 +2770,7 @@ var treectl = (function () {
|
|||||||
prev_atop = null,
|
prev_atop = null,
|
||||||
prev_winh = null,
|
prev_winh = null,
|
||||||
dyn = bcfg_get('dyntree', true),
|
dyn = bcfg_get('dyntree', true),
|
||||||
|
dots = bcfg_get('dotfiles', false),
|
||||||
treesz = icfg_get('treesz', 16);
|
treesz = icfg_get('treesz', 16);
|
||||||
|
|
||||||
treesz = Math.min(Math.max(treesz, 4), 50);
|
treesz = Math.min(Math.max(treesz, 4), 50);
|
||||||
@@ -2533,7 +2889,7 @@ var treectl = (function () {
|
|||||||
xhr.dst = dst;
|
xhr.dst = dst;
|
||||||
xhr.rst = rst;
|
xhr.rst = rst;
|
||||||
xhr.ts = Date.now();
|
xhr.ts = Date.now();
|
||||||
xhr.open('GET', dst + '?tree=' + top, true);
|
xhr.open('GET', dst + '?tree=' + top + (dots ? '&dots' : ''), true);
|
||||||
xhr.onreadystatechange = recvtree;
|
xhr.onreadystatechange = recvtree;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
enspin('#tree');
|
enspin('#tree');
|
||||||
@@ -2637,7 +2993,7 @@ var treectl = (function () {
|
|||||||
xhr.top = url;
|
xhr.top = url;
|
||||||
xhr.hpush = hpush;
|
xhr.hpush = hpush;
|
||||||
xhr.ts = Date.now();
|
xhr.ts = Date.now();
|
||||||
xhr.open('GET', xhr.top + '?ls', true);
|
xhr.open('GET', xhr.top + '?ls' + (dots ? '&dots' : ''), true);
|
||||||
xhr.onreadystatechange = recvls;
|
xhr.onreadystatechange = recvls;
|
||||||
xhr.send();
|
xhr.send();
|
||||||
if (hpush)
|
if (hpush)
|
||||||
@@ -2774,6 +3130,13 @@ var treectl = (function () {
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tdots(e) {
|
||||||
|
ev(e);
|
||||||
|
dots = !dots;
|
||||||
|
bcfg_set('dotfiles', dots);
|
||||||
|
treectl.goto(get_evpath());
|
||||||
|
}
|
||||||
|
|
||||||
function dyntree(e) {
|
function dyntree(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
dyn = !dyn;
|
dyn = !dyn;
|
||||||
@@ -2793,6 +3156,7 @@ var treectl = (function () {
|
|||||||
|
|
||||||
ebi('entree').onclick = treectl.entree;
|
ebi('entree').onclick = treectl.entree;
|
||||||
ebi('detree').onclick = treectl.detree;
|
ebi('detree').onclick = treectl.detree;
|
||||||
|
ebi('dotfiles').onclick = tdots;
|
||||||
ebi('dyntree').onclick = dyntree;
|
ebi('dyntree').onclick = dyntree;
|
||||||
ebi('twig').onclick = scaletree;
|
ebi('twig').onclick = scaletree;
|
||||||
ebi('twobytwo').onclick = scaletree;
|
ebi('twobytwo').onclick = scaletree;
|
||||||
@@ -2839,7 +3203,7 @@ function apply_perms(newperms) {
|
|||||||
|
|
||||||
var axs = [],
|
var axs = [],
|
||||||
aclass = '>',
|
aclass = '>',
|
||||||
chk = ['read', 'write', 'rename', 'delete'];
|
chk = ['read', 'write', 'move', 'delete'];
|
||||||
|
|
||||||
for (var a = 0; a < chk.length; a++)
|
for (var a = 0; a < chk.length; a++)
|
||||||
if (has(perms, chk[a]))
|
if (has(perms, chk[a]))
|
||||||
@@ -2955,6 +3319,8 @@ var filecols = (function () {
|
|||||||
"q": "quality / bitrate",
|
"q": "quality / bitrate",
|
||||||
"Ac": "audio codec",
|
"Ac": "audio codec",
|
||||||
"Vc": "video codec",
|
"Vc": "video codec",
|
||||||
|
"Ahash": "audio checksum",
|
||||||
|
"Vhash": "video checksum",
|
||||||
"Res": "resolution",
|
"Res": "resolution",
|
||||||
"T": "filetype",
|
"T": "filetype",
|
||||||
"aq": "audio quality / bitrate",
|
"aq": "audio quality / bitrate",
|
||||||
@@ -2966,6 +3332,21 @@ var filecols = (function () {
|
|||||||
"hz": "sample rate"
|
"hz": "sample rate"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (JSON.stringify(def_hcols) != sread('hfilecols')) {
|
||||||
|
console.log("applying default hidden-cols");
|
||||||
|
jwrite('hfilecols', def_hcols);
|
||||||
|
for (var a = 0; a < def_hcols.length; a++) {
|
||||||
|
var t = def_hcols[a];
|
||||||
|
t = t.slice(0, 1).toUpperCase() + t.slice(1);
|
||||||
|
if (t.startsWith("."))
|
||||||
|
t = t.slice(1);
|
||||||
|
|
||||||
|
if (hidden.indexOf(t) == -1)
|
||||||
|
hidden.push(t);
|
||||||
|
}
|
||||||
|
jwrite("filecols", hidden);
|
||||||
|
}
|
||||||
|
|
||||||
var add_btns = function () {
|
var add_btns = function () {
|
||||||
var ths = QSA('#files th>span');
|
var ths = QSA('#files th>span');
|
||||||
for (var a = 0, aa = ths.length; a < aa; a++) {
|
for (var a = 0, aa = ths.length; a < aa; a++) {
|
||||||
@@ -3319,13 +3700,11 @@ var msel = (function () {
|
|||||||
item.id = links[a].getAttribute('id');
|
item.id = links[a].getAttribute('id');
|
||||||
item.sel = links[a].closest('tr').classList.contains('sel');
|
item.sel = links[a].closest('tr').classList.contains('sel');
|
||||||
item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
|
item.vp = href.indexOf('/') !== -1 ? href : vbase + href;
|
||||||
item.name = href.split('/').slice(-1);
|
|
||||||
|
|
||||||
r.all.push(item);
|
r.all.push(item);
|
||||||
if (item.sel)
|
if (item.sel)
|
||||||
r.sel.push(item);
|
r.sel.push(item);
|
||||||
|
|
||||||
links[a].setAttribute('name', item.name);
|
|
||||||
links[a].closest('tr').setAttribute('tabindex', '0');
|
links[a].closest('tr').setAttribute('tabindex', '0');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -3365,10 +3744,15 @@ var msel = (function () {
|
|||||||
};
|
};
|
||||||
ebi('selzip').onclick = function (e) {
|
ebi('selzip').onclick = function (e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
var names = r.getsel(),
|
var sel = r.getsel(),
|
||||||
arg = ebi('selzip').getAttribute('fmt'),
|
arg = ebi('selzip').getAttribute('fmt'),
|
||||||
txt = names.join('\n'),
|
frm = mknod('form'),
|
||||||
frm = mknod('form');
|
txt = [];
|
||||||
|
|
||||||
|
for (var a = 0; a < sel.length; a++)
|
||||||
|
txt.push(vsplit(sel[a].vp)[1]);
|
||||||
|
|
||||||
|
txt = txt.join('\n');
|
||||||
|
|
||||||
frm.setAttribute('action', '?' + arg);
|
frm.setAttribute('action', '?' + arg);
|
||||||
frm.setAttribute('method', 'post');
|
frm.setAttribute('method', 'post');
|
||||||
|
|||||||
@@ -16,9 +16,6 @@ html, body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
body {
|
|
||||||
padding-bottom: 5em;
|
|
||||||
}
|
|
||||||
#box {
|
#box {
|
||||||
padding: .5em 1em;
|
padding: .5em 1em;
|
||||||
background: #2c2c2c;
|
background: #2c2c2c;
|
||||||
|
|||||||
@@ -398,6 +398,15 @@ function uricom_dec(txt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function uricom_adec(arr) {
|
||||||
|
var ret = [];
|
||||||
|
for (var a = 0; a < arr.length; a++)
|
||||||
|
ret.push(uricom_dec(arr[a])[0]);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function get_evpath() {
|
function get_evpath() {
|
||||||
var ret = document.location.pathname;
|
var ret = document.location.pathname;
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ avg() { awk 'function pr(ncsz) {if (nsmp>0) {printf "%3s %s\n", csz, sum/nsmp} c
|
|||||||
dirs=("$HOME/vfs/ほげ" "$HOME/vfs/ほげ/ぴよ" "$HOME/vfs/$(printf \\xed\\x91)" "$HOME/vfs/$(printf \\xed\\x91/\\xed\\x92)")
|
dirs=("$HOME/vfs/ほげ" "$HOME/vfs/ほげ/ぴよ" "$HOME/vfs/$(printf \\xed\\x91)" "$HOME/vfs/$(printf \\xed\\x91/\\xed\\x92)")
|
||||||
mkdir -p "${dirs[@]}"
|
mkdir -p "${dirs[@]}"
|
||||||
for dir in "${dirs[@]}"; do for fn in ふが "$(printf \\xed\\x93)" 'qwe,rty;asd fgh+jkl%zxc&vbn <qwe>"rty'"'"'uio&asd fgh'; do echo "$dir" > "$dir/$fn.html"; done; done
|
for dir in "${dirs[@]}"; do for fn in ふが "$(printf \\xed\\x93)" 'qwe,rty;asd fgh+jkl%zxc&vbn <qwe>"rty'"'"'uio&asd fgh'; do echo "$dir" > "$dir/$fn.html"; done; done
|
||||||
|
# qw er+ty%20ui%%20op<as>df&gh&jk#zx'cv"bn`m=qw*er^ty?ui@op,as.df-gh_jk
|
||||||
|
|
||||||
##
|
##
|
||||||
## upload mojibake
|
## upload mojibake
|
||||||
@@ -79,6 +79,10 @@ command -v gdate && date() { gdate "$@"; }; while true; do t=$(date +%s.%N); (ti
|
|||||||
# get all up2k search result URLs
|
# 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"));
|
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"));
|
||||||
|
|
||||||
|
# rename all selected songs to <leading-track-number> + <Title> + <extension>
|
||||||
|
var sel=msel.getsel(), ci=find_file_col('Title')[0], re=[]; for (var a=0; a<sel.length; a++) { var url=sel[a].vp, tag=ebi(sel[a].id).closest('tr').querySelectorAll('td')[ci].textContent, name=uricom_dec(vsplit(url)[1])[0], m=/^([0-9]+[\. -]+)?.*(\.[^\.]+$)/.exec(name), name2=(m[1]||'')+tag+m[2], url2=vsplit(url)[0]+uricom_enc(name2,false); if (url!=url2) re.push([url, url2]); }
|
||||||
|
console.log(JSON.stringify(re, null, ' '));
|
||||||
|
function f() { if (!re.length) return treectl.goto(get_evpath()); var [u1,u2] = re.shift(); fetch(u1+'?move='+u2).then((rsp) => {if (rsp.ok) f(); }); }; f();
|
||||||
|
|
||||||
##
|
##
|
||||||
## bash oneliners
|
## bash oneliners
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
set -e
|
set -e
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
help() { exec cat <<'EOF'
|
||||||
|
|
||||||
# optional args:
|
# optional args:
|
||||||
#
|
#
|
||||||
@@ -26,6 +27,8 @@ echo
|
|||||||
#
|
#
|
||||||
# `no-dd` saves ~2k by removing the mouse cursor
|
# `no-dd` saves ~2k by removing the mouse cursor
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
# port install gnutar findutils gsed coreutils
|
# port install gnutar findutils gsed coreutils
|
||||||
gtar=$(command -v gtar || command -v gnutar) || true
|
gtar=$(command -v gtar || command -v gnutar) || true
|
||||||
@@ -34,6 +37,7 @@ gtar=$(command -v gtar || command -v gnutar) || true
|
|||||||
sed() { gsed "$@"; }
|
sed() { gsed "$@"; }
|
||||||
find() { gfind "$@"; }
|
find() { gfind "$@"; }
|
||||||
sort() { gsort "$@"; }
|
sort() { gsort "$@"; }
|
||||||
|
sha1sum() { shasum "$@"; }
|
||||||
unexpand() { gunexpand "$@"; }
|
unexpand() { gunexpand "$@"; }
|
||||||
command -v grealpath >/dev/null &&
|
command -v grealpath >/dev/null &&
|
||||||
realpath() { grealpath "$@"; }
|
realpath() { grealpath "$@"; }
|
||||||
@@ -72,6 +76,7 @@ while [ ! -z "$1" ]; do
|
|||||||
no-cm) no_cm=1 ; ;;
|
no-cm) no_cm=1 ; ;;
|
||||||
no-sh) do_sh= ; ;;
|
no-sh) do_sh= ; ;;
|
||||||
no-py) do_py= ; ;;
|
no-py) do_py= ; ;;
|
||||||
|
*) help ; ;;
|
||||||
esac
|
esac
|
||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
@@ -81,16 +86,23 @@ tmv() {
|
|||||||
mv t "$1"
|
mv t "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stamp=$(
|
||||||
|
for d in copyparty scripts; do
|
||||||
|
find $d -type f -printf '%TY-%Tm-%Td %TH:%TM:%TS %p\n'
|
||||||
|
done | sort | tail -n 1 | sha1sum | cut -c-16
|
||||||
|
)
|
||||||
|
|
||||||
rm -rf sfx/*
|
rm -rf sfx/*
|
||||||
mkdir -p sfx build
|
mkdir -p sfx build
|
||||||
cd sfx
|
cd sfx
|
||||||
|
|
||||||
[ $repack ] && {
|
tmpdir="$(
|
||||||
old="$(
|
|
||||||
printf '%s\n' "$TMPDIR" /tmp |
|
printf '%s\n' "$TMPDIR" /tmp |
|
||||||
awk '/./ {print; exit}'
|
awk '/./ {print; exit}'
|
||||||
)/pe-copyparty"
|
)"
|
||||||
|
|
||||||
|
[ $repack ] && {
|
||||||
|
old="$tmpdir/pe-copyparty"
|
||||||
echo "repack of files in $old"
|
echo "repack of files in $old"
|
||||||
cp -pR "$old/"*{dep-j2,copyparty} .
|
cp -pR "$old/"*{dep-j2,copyparty} .
|
||||||
}
|
}
|
||||||
@@ -172,12 +184,12 @@ mkdir -p ../dist
|
|||||||
sfx_out=../dist/copyparty-sfx
|
sfx_out=../dist/copyparty-sfx
|
||||||
|
|
||||||
echo cleanup
|
echo cleanup
|
||||||
find .. -name '*.pyc' -delete
|
find -name '*.pyc' -delete
|
||||||
find .. -name __pycache__ -delete
|
find -name __pycache__ -delete
|
||||||
|
|
||||||
# especially prevent osx from leaking your lan ip (wtf apple)
|
# especially prevent osx from leaking your lan ip (wtf apple)
|
||||||
find .. -type f \( -name .DS_Store -or -name ._.DS_Store \) -delete
|
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
|
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
|
echo use smol web deps
|
||||||
rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile
|
rm -f copyparty/web/deps/*.full.* copyparty/web/dbg-* copyparty/web/Makefile
|
||||||
@@ -196,19 +208,24 @@ done
|
|||||||
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
rm -rf copyparty/web/mde.* copyparty/web/deps/easymde*
|
||||||
echo h > copyparty/web/mde.html
|
echo h > copyparty/web/mde.html
|
||||||
f=copyparty/web/md.html
|
f=copyparty/web/md.html
|
||||||
sed -r '/edit2">edit \(fancy/d' <$f >t && tmv "$f"
|
sed -r '/edit2">edit \(fancy/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $no_fnt ] && {
|
[ $no_fnt ] && {
|
||||||
rm -f copyparty/web/deps/scp.woff2
|
rm -f copyparty/web/deps/scp.woff2
|
||||||
f=copyparty/web/md.css
|
f=copyparty/web/md.css
|
||||||
sed -r '/scp\.woff2/d' <$f >t && tmv "$f"
|
gzip -d "$f"
|
||||||
|
sed -r '/scp\.woff2/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $no_dd ] && {
|
[ $no_dd ] && {
|
||||||
rm -rf copyparty/web/dd
|
rm -rf copyparty/web/dd
|
||||||
f=copyparty/web/browser.css
|
f=copyparty/web/browser.css
|
||||||
sed -r 's/(cursor: )url\([^)]+\), (pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: cursor/d' <$f >t && tmv "$f"
|
gzip -d "$f"
|
||||||
|
sed -r 's/(cursor: )url\([^)]+\), (pointer)/\1\2/; /[0-9]+% \{cursor:/d; /animation: cursor/d' <$f >t
|
||||||
|
tmv "$f"
|
||||||
}
|
}
|
||||||
|
|
||||||
[ $repack ] ||
|
[ $repack ] ||
|
||||||
@@ -241,10 +258,9 @@ find | grep -E '\.(js|html)$' | while IFS= read -r f; do
|
|||||||
tmv "$f"
|
tmv "$f"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
||||||
gzres() {
|
gzres() {
|
||||||
command -v pigz &&
|
command -v pigz &&
|
||||||
pk='pigz -11 -J 34 -I 100' ||
|
pk='pigz -11 -I 256' ||
|
||||||
pk='gzip'
|
pk='gzip'
|
||||||
|
|
||||||
echo "$pk"
|
echo "$pk"
|
||||||
@@ -254,7 +270,30 @@ find | grep -E '\.(js|css)$' | grep -vF /deps/ | while IFS= read -r f; do
|
|||||||
done
|
done
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
zdir="$tmpdir/cpp-mksfx"
|
||||||
|
[ -e "$zdir/$stamp" ] || rm -rf "$zdir"
|
||||||
|
mkdir -p "$zdir"
|
||||||
|
echo a > "$zdir/$stamp"
|
||||||
|
nf=$(ls -1 "$zdir"/arc.* | wc -l)
|
||||||
|
[ $nf -ge 2 ] && [ ! $repack ] && use_zdir=1 || use_zdir=
|
||||||
|
|
||||||
|
[ $use_zdir ] || {
|
||||||
|
echo "$nf alts += 1"
|
||||||
gzres
|
gzres
|
||||||
|
[ $repack ] ||
|
||||||
|
tar -cf "$zdir/arc.$(date +%s)" copyparty/web/*.gz
|
||||||
|
}
|
||||||
|
[ $use_zdir ] && {
|
||||||
|
arcs=("$zdir"/arc.*)
|
||||||
|
arc="${arcs[$RANDOM % ${#arcs[@]} ] }"
|
||||||
|
echo "using $arc"
|
||||||
|
tar -xf "$arc"
|
||||||
|
for f in copyparty/web/*.gz; do
|
||||||
|
rm "${f%.*}"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
echo gen tarlist
|
echo gen tarlist
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ def tc1():
|
|||||||
|
|
||||||
arg = "{}:{}:{}".format(pd, ud, p, hp)
|
arg = "{}:{}:{}".format(pd, ud, p, hp)
|
||||||
if hp:
|
if hp:
|
||||||
arg += ":chist=" + hp
|
arg += ":c,hist=" + hp
|
||||||
|
|
||||||
args += ["-v", arg]
|
args += ["-v", arg]
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,9 @@ def uncomment(fpath):
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("uncommenting", end="")
|
print("uncommenting", end="", flush=True)
|
||||||
for f in sys.argv[1:]:
|
for f in sys.argv[1:]:
|
||||||
print(".", end="")
|
print(".", end="", flush=True)
|
||||||
uncomment(f)
|
uncomment(f)
|
||||||
|
|
||||||
print("k")
|
print("k")
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class Cfg(Namespace):
|
|||||||
nih=True,
|
nih=True,
|
||||||
mtp=[],
|
mtp=[],
|
||||||
mte="a",
|
mte="a",
|
||||||
|
mth="",
|
||||||
hist=None,
|
hist=None,
|
||||||
no_hash=False,
|
no_hash=False,
|
||||||
css_browser=None,
|
css_browser=None,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ class Cfg(Namespace):
|
|||||||
ex2 = {
|
ex2 = {
|
||||||
"mtp": [],
|
"mtp": [],
|
||||||
"mte": "a",
|
"mte": "a",
|
||||||
|
"mth": "",
|
||||||
"hist": None,
|
"hist": None,
|
||||||
"no_hash": False,
|
"no_hash": False,
|
||||||
"css_browser": None,
|
"css_browser": None,
|
||||||
|
|||||||
Reference in New Issue
Block a user