mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	Compare commits
	
		
			30 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					bd49979f4a | ||
| 
						 | 
					7e606cdd9f | ||
| 
						 | 
					8b4b7fa794 | ||
| 
						 | 
					05345ddf8b | ||
| 
						 | 
					66adb470ad | ||
| 
						 | 
					e15c8fd146 | ||
| 
						 | 
					0f09b98a39 | ||
| 
						 | 
					b4d6f4e24d | ||
| 
						 | 
					3217fa625b | ||
| 
						 | 
					e719ff8a47 | ||
| 
						 | 
					9fcf528d45 | ||
| 
						 | 
					1ddbf5a158 | ||
| 
						 | 
					64bf4574b0 | ||
| 
						 | 
					5649d26077 | ||
| 
						 | 
					92f923effe | ||
| 
						 | 
					0d46d548b9 | ||
| 
						 | 
					062df3f0c3 | ||
| 
						 | 
					789fb53b8e | ||
| 
						 | 
					351db5a18f | ||
| 
						 | 
					aabbd271c8 | ||
| 
						 | 
					aae8e0171e | ||
| 
						 | 
					45827a2458 | ||
| 
						 | 
					726030296f | ||
| 
						 | 
					6659ab3881 | ||
| 
						 | 
					c6a103609e | ||
| 
						 | 
					c6b3f035e5 | ||
| 
						 | 
					2b0a7e378e | ||
| 
						 | 
					b75ce909c8 | ||
| 
						 | 
					229c3f5dab | ||
| 
						 | 
					ec73094506 | 
							
								
								
									
										26
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								README.md
									
									
									
									
									
								
							@@ -20,6 +20,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
 | 
			
		||||
 | 
			
		||||
* top
 | 
			
		||||
    * [quickstart](#quickstart)
 | 
			
		||||
        * [on debian](#on-debian)
 | 
			
		||||
    * [notes](#notes)
 | 
			
		||||
    * [status](#status)
 | 
			
		||||
* [bugs](#bugs)
 | 
			
		||||
@@ -68,6 +69,7 @@ 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)
 | 
			
		||||
* `-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
 | 
			
		||||
  * in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
 | 
			
		||||
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
 | 
			
		||||
@@ -77,6 +79,19 @@ you may also want these, especially on servers:
 | 
			
		||||
* [contrib/nginx/copyparty.conf](contrib/nginx/copyparty.conf) to reverse-proxy behind nginx (for better https)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### on debian
 | 
			
		||||
 | 
			
		||||
recommended steps to enable audio metadata and thumbnails (from images and videos):
 | 
			
		||||
 | 
			
		||||
* as root, run the following:  
 | 
			
		||||
  `apt install python3 python3-pip python3-dev ffmpeg`
 | 
			
		||||
 | 
			
		||||
* then, as the user which will be running copyparty (so hopefully not root), run this:  
 | 
			
		||||
  `python3 -m pip install --user -U Pillow pillow-avif-plugin`
 | 
			
		||||
 | 
			
		||||
(skipped `pyheif-pillow-opener` because apparently debian is too old to build it)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## notes
 | 
			
		||||
 | 
			
		||||
general:
 | 
			
		||||
@@ -139,6 +154,8 @@ summary: all planned features work! now please enjoy the bloatening
 | 
			
		||||
 | 
			
		||||
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
 | 
			
		||||
* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
 | 
			
		||||
* dupe files will not have metadata (audio tags etc) displayed in the file listing
 | 
			
		||||
  * because they don't get `up` entries in the db (probably best fix) and `tx_browser` does not `lstat`
 | 
			
		||||
* probably more, pls let me know
 | 
			
		||||
 | 
			
		||||
## not my bugs
 | 
			
		||||
@@ -178,9 +195,11 @@ the browser has the following hotkeys
 | 
			
		||||
  * `U/O` skip 10sec back/forward
 | 
			
		||||
  * `J/L` prev/next song
 | 
			
		||||
  * `P` play/pause (also starts playing the folder)
 | 
			
		||||
* when tree-sidebar is open:
 | 
			
		||||
  * `A/D` adjust tree width
 | 
			
		||||
* in the grid view:
 | 
			
		||||
  * `S` toggle multiselect
 | 
			
		||||
  * `A/D` zoom
 | 
			
		||||
  * shift+`A/D` zoom
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## tree-mode
 | 
			
		||||
@@ -198,6 +217,8 @@ it does static images with Pillow and uses FFmpeg for video files, so you may wa
 | 
			
		||||
 | 
			
		||||
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
 | 
			
		||||
 | 
			
		||||
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## zip downloads
 | 
			
		||||
 | 
			
		||||
@@ -594,13 +615,14 @@ in the `scripts` folder:
 | 
			
		||||
roughly sorted by priority
 | 
			
		||||
 | 
			
		||||
* readme.md as epilogue
 | 
			
		||||
* single sha512 across all up2k chunks? maybe
 | 
			
		||||
* reduce up2k roundtrips
 | 
			
		||||
  * start from a chunk index and just go
 | 
			
		||||
  * terminate client on bad data
 | 
			
		||||
 | 
			
		||||
discarded ideas
 | 
			
		||||
 | 
			
		||||
* single sha512 across all up2k chunks?
 | 
			
		||||
  * crypto.subtle cannot into streaming, would have to use hashwasm, expensive
 | 
			
		||||
* separate sqlite table per tag
 | 
			
		||||
  * performance fixed by skipping some indexes (`+mt.k`)
 | 
			
		||||
* audio fingerprinting
 | 
			
		||||
 
 | 
			
		||||
@@ -305,6 +305,7 @@ def run_argparse(argv, formatter):
 | 
			
		||||
    ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
 | 
			
		||||
    ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval")
 | 
			
		||||
    ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
 | 
			
		||||
    ap2.add_argument("--th-covers", metavar="N,N", type=str, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
 | 
			
		||||
 | 
			
		||||
    ap2 = ap.add_argument_group('database options')
 | 
			
		||||
    ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
# coding: utf-8
 | 
			
		||||
 | 
			
		||||
VERSION = (0, 11, 25)
 | 
			
		||||
VERSION = (0, 11, 29)
 | 
			
		||||
CODENAME = "the grid"
 | 
			
		||||
BUILD_DT = (2021, 6, 25)
 | 
			
		||||
BUILD_DT = (2021, 6, 30)
 | 
			
		||||
 | 
			
		||||
S_VERSION = ".".join(map(str, VERSION))
 | 
			
		||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
 | 
			
		||||
 
 | 
			
		||||
@@ -693,8 +693,10 @@ class AuthSrv(object):
 | 
			
		||||
            self.user = user
 | 
			
		||||
            self.iuser = {v: k for k, v in user.items()}
 | 
			
		||||
 | 
			
		||||
            self.re_pwd = None
 | 
			
		||||
            pwds = [re.escape(x) for x in self.iuser.keys()]
 | 
			
		||||
            self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
 | 
			
		||||
            if pwds:
 | 
			
		||||
                self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
 | 
			
		||||
 | 
			
		||||
        # import pprint
 | 
			
		||||
        # pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ class HttpCli(object):
 | 
			
		||||
 | 
			
		||||
    def log(self, msg, c=0):
 | 
			
		||||
        ptn = self.asrv.re_pwd
 | 
			
		||||
        if ptn.search(msg):
 | 
			
		||||
        if ptn and ptn.search(msg):
 | 
			
		||||
            msg = ptn.sub(self.unpwd, msg)
 | 
			
		||||
 | 
			
		||||
        self.log_func(self.log_src, msg, c)
 | 
			
		||||
@@ -181,6 +181,9 @@ class HttpCli(object):
 | 
			
		||||
        self.rvol, self.wvol, self.avol = [[], [], []]
 | 
			
		||||
        self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
 | 
			
		||||
 | 
			
		||||
        if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
 | 
			
		||||
            self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
 | 
			
		||||
 | 
			
		||||
        ua = self.headers.get("user-agent", "")
 | 
			
		||||
        self.is_rclone = ua.startswith("rclone/")
 | 
			
		||||
        if self.is_rclone:
 | 
			
		||||
@@ -496,7 +499,7 @@ class HttpCli(object):
 | 
			
		||||
 | 
			
		||||
        spd1 = get_spd(nbytes, self.t0)
 | 
			
		||||
        spd2 = get_spd(self.conn.nbyte, self.conn.t0)
 | 
			
		||||
        return spd1 + " " + spd2
 | 
			
		||||
        return "{} {} n{}".format(spd1, spd2, self.conn.nreq)
 | 
			
		||||
 | 
			
		||||
    def handle_post_multipart(self):
 | 
			
		||||
        self.parser = MultipartParser(self.log, self.sr, self.headers)
 | 
			
		||||
@@ -758,6 +761,12 @@ class HttpCli(object):
 | 
			
		||||
        pwd = self.parser.require("cppwd", 64)
 | 
			
		||||
        self.parser.drop()
 | 
			
		||||
 | 
			
		||||
        ck, msg = self.get_pwd_cookie(pwd)
 | 
			
		||||
        html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
 | 
			
		||||
        self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def get_pwd_cookie(self, pwd):
 | 
			
		||||
        if pwd in self.asrv.iuser:
 | 
			
		||||
            msg = "login ok"
 | 
			
		||||
            dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
 | 
			
		||||
@@ -768,9 +777,7 @@ class HttpCli(object):
 | 
			
		||||
            exp = "Fri, 15 Aug 1997 01:00:00 GMT"
 | 
			
		||||
 | 
			
		||||
        ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
 | 
			
		||||
        html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
 | 
			
		||||
        self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
 | 
			
		||||
        return True
 | 
			
		||||
        return [ck, msg]
 | 
			
		||||
 | 
			
		||||
    def handle_mkdir(self):
 | 
			
		||||
        new_dir = self.parser.require("name", 512)
 | 
			
		||||
@@ -1555,7 +1562,7 @@ class HttpCli(object):
 | 
			
		||||
            th_fmt = self.uparam.get("th")
 | 
			
		||||
            if th_fmt is not None:
 | 
			
		||||
                if is_dir:
 | 
			
		||||
                    for fn in ["folder.png", "folder.jpg"]:
 | 
			
		||||
                    for fn in self.args.th_covers.split(","):
 | 
			
		||||
                        fp = os.path.join(abspath, fn)
 | 
			
		||||
                        if os.path.exists(fp):
 | 
			
		||||
                            vrem = "{}/{}".format(vrem.rstrip("/"), fn)
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ class HttpConn(object):
 | 
			
		||||
 | 
			
		||||
        self.t0 = time.time()
 | 
			
		||||
        self.stopping = False
 | 
			
		||||
        self.nreq = 0
 | 
			
		||||
        self.nbyte = 0
 | 
			
		||||
        self.workload = 0
 | 
			
		||||
        self.u2idx = None
 | 
			
		||||
@@ -188,6 +189,7 @@ class HttpConn(object):
 | 
			
		||||
                if self.workload >= 2 ** 31:
 | 
			
		||||
                    self.workload = 100
 | 
			
		||||
 | 
			
		||||
            self.nreq += 1
 | 
			
		||||
            cli = HttpCli(self)
 | 
			
		||||
            if not cli.run():
 | 
			
		||||
                return
 | 
			
		||||
 
 | 
			
		||||
@@ -119,7 +119,7 @@ window.baguetteBox = (function () {
 | 
			
		||||
            var gallery = [];
 | 
			
		||||
            [].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
 | 
			
		||||
                var imageElementClickHandler = function (event) {
 | 
			
		||||
                    if (event && event.ctrlKey)
 | 
			
		||||
                    if (event && (event.ctrlKey || event.metaKey))
 | 
			
		||||
                        return true;
 | 
			
		||||
 | 
			
		||||
                    event.preventDefault ? event.preventDefault() : event.returnValue = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -607,7 +607,7 @@ input.eq_gain {
 | 
			
		||||
#srch_q {
 | 
			
		||||
	white-space: pre;
 | 
			
		||||
	color: #f80;
 | 
			
		||||
	height: 1em;
 | 
			
		||||
	min-height: 1em;
 | 
			
		||||
	margin: .2em 0 -1em 1.6em;
 | 
			
		||||
}
 | 
			
		||||
#tq_raw {
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@
 | 
			
		||||
	
 | 
			
		||||
	<div id="epi" class="logue">{{ logues[1] }}</div>
 | 
			
		||||
 | 
			
		||||
	<h2><a href="?h">control-panel</a></h2>
 | 
			
		||||
	<h2><a href="/?h">control-panel</a></h2>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ ebi('op_up2k').innerHTML = (
 | 
			
		||||
	'	<tr>\n' +
 | 
			
		||||
	'		<td>\n' +
 | 
			
		||||
	'			<a href="#" id="nthread_sub">–</a><input\n' +
 | 
			
		||||
	'				class="txtbox" id="nthread" value="2"/><a\n' +
 | 
			
		||||
	'				class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' +
 | 
			
		||||
	'				href="#" id="nthread_add">+</a><br /> \n' +
 | 
			
		||||
	'		</td>\n' +
 | 
			
		||||
	'	</tr>\n' +
 | 
			
		||||
@@ -237,13 +237,17 @@ var mpl = (function () {
 | 
			
		||||
		'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' +
 | 
			
		||||
		'</div></div>' +
 | 
			
		||||
 | 
			
		||||
		'<div><h3>tint</h3><div>' +
 | 
			
		||||
		'<input type="text" id="pb_tint" size="3" value="0" tt="background level (0-100) on the seekbar$Nto make buffering less distracting" />' +
 | 
			
		||||
		'</div></div>' +
 | 
			
		||||
 | 
			
		||||
		'<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>');
 | 
			
		||||
 | 
			
		||||
	var r = {
 | 
			
		||||
		"pb_mode": sread('pb_mode') || 'loop-folder',
 | 
			
		||||
		"preload": bcfg_get('au_preload', true),
 | 
			
		||||
		"clip": bcfg_get('au_npclip', false),
 | 
			
		||||
		"os_ctl": bcfg_get('au_os_ctl', true) && have_mctl,
 | 
			
		||||
		"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
 | 
			
		||||
		"osd_cv": bcfg_get('au_osd_cv', true),
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
@@ -290,6 +294,19 @@ var mpl = (function () {
 | 
			
		||||
		draw_pb_mode();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function set_tint() {
 | 
			
		||||
		var tint = icfg_get('pb_tint', 0);
 | 
			
		||||
		if (!tint)
 | 
			
		||||
			ebi('barbuf').style.removeProperty('background');
 | 
			
		||||
		else
 | 
			
		||||
			ebi('barbuf').style.background = 'rgba(126,163,75,' + (tint / 100.0) + ')';
 | 
			
		||||
	}
 | 
			
		||||
	ebi('pb_tint').oninput = function (e) {
 | 
			
		||||
		swrite('pb_tint', this.value);
 | 
			
		||||
		set_tint();
 | 
			
		||||
	};
 | 
			
		||||
	set_tint();
 | 
			
		||||
 | 
			
		||||
	r.pp = function () {
 | 
			
		||||
		if (!r.os_ctl)
 | 
			
		||||
			return;
 | 
			
		||||
@@ -522,37 +539,38 @@ function get_np() {
 | 
			
		||||
 | 
			
		||||
// toggle player widget
 | 
			
		||||
var widget = (function () {
 | 
			
		||||
	var ret = {},
 | 
			
		||||
	var r = {},
 | 
			
		||||
		widget = ebi('widget'),
 | 
			
		||||
		wtico = ebi('wtico'),
 | 
			
		||||
		nptxt = ebi('nptxt'),
 | 
			
		||||
		npirc = ebi('npirc'),
 | 
			
		||||
		touchmode = false,
 | 
			
		||||
		side_open = false,
 | 
			
		||||
		was_paused = true;
 | 
			
		||||
 | 
			
		||||
	ret.open = function () {
 | 
			
		||||
		if (side_open)
 | 
			
		||||
	r.is_open = false;
 | 
			
		||||
 | 
			
		||||
	r.open = function () {
 | 
			
		||||
		if (r.is_open)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		widget.className = 'open';
 | 
			
		||||
		side_open = true;
 | 
			
		||||
		r.is_open = true;
 | 
			
		||||
		return true;
 | 
			
		||||
	};
 | 
			
		||||
	ret.close = function () {
 | 
			
		||||
		if (!side_open)
 | 
			
		||||
	r.close = function () {
 | 
			
		||||
		if (!r.is_open)
 | 
			
		||||
			return false;
 | 
			
		||||
 | 
			
		||||
		widget.className = '';
 | 
			
		||||
		side_open = false;
 | 
			
		||||
		r.is_open = false;
 | 
			
		||||
		return true;
 | 
			
		||||
	};
 | 
			
		||||
	ret.toggle = function (e) {
 | 
			
		||||
		ret.open() || ret.close();
 | 
			
		||||
	r.toggle = function (e) {
 | 
			
		||||
		r.open() || r.close();
 | 
			
		||||
		ev(e);
 | 
			
		||||
		return false;
 | 
			
		||||
	};
 | 
			
		||||
	ret.paused = function (paused) {
 | 
			
		||||
	r.paused = function (paused) {
 | 
			
		||||
		if (was_paused != paused) {
 | 
			
		||||
			was_paused = paused;
 | 
			
		||||
			ebi('bplay').innerHTML = paused ? '▶' : '⏸';
 | 
			
		||||
@@ -560,7 +578,7 @@ var widget = (function () {
 | 
			
		||||
	};
 | 
			
		||||
	wtico.onclick = function (e) {
 | 
			
		||||
		if (!touchmode)
 | 
			
		||||
			ret.toggle(e);
 | 
			
		||||
			r.toggle(e);
 | 
			
		||||
 | 
			
		||||
		return false;
 | 
			
		||||
	};
 | 
			
		||||
@@ -591,7 +609,7 @@ var widget = (function () {
 | 
			
		||||
			document.body.removeChild(o);
 | 
			
		||||
		}, 500);
 | 
			
		||||
	};
 | 
			
		||||
	return ret;
 | 
			
		||||
	return r;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -857,7 +875,10 @@ function playpause(e) {
 | 
			
		||||
	ebi('bplay').onclick = playpause;
 | 
			
		||||
	ebi('bprev').onclick = prev_song;
 | 
			
		||||
	ebi('bnext').onclick = next_song;
 | 
			
		||||
	ebi('barpos').onclick = function (e) {
 | 
			
		||||
 | 
			
		||||
	var bar = ebi('barpos');
 | 
			
		||||
 | 
			
		||||
	bar.onclick = function (e) {
 | 
			
		||||
		if (!mp.au) {
 | 
			
		||||
			play(0, true);
 | 
			
		||||
			return mp.fade_in();
 | 
			
		||||
@@ -868,6 +889,19 @@ function playpause(e) {
 | 
			
		||||
 | 
			
		||||
		seek_au_mul(x * 1.0 / rect.width);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	if (!is_touch)
 | 
			
		||||
		bar.onwheel = function (e) {
 | 
			
		||||
			var dist = Math.sign(e.deltaY) * 10;
 | 
			
		||||
			if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
 | 
			
		||||
				dist = e.deltaY;
 | 
			
		||||
 | 
			
		||||
			if (!dist || !mp.au)
 | 
			
		||||
				return true;
 | 
			
		||||
 | 
			
		||||
			seek_au_rel(dist);
 | 
			
		||||
			ev(e);
 | 
			
		||||
		};
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -1186,8 +1220,11 @@ function play(tid, is_ev, seek, call_depth) {
 | 
			
		||||
	mp.stopfade(true);
 | 
			
		||||
 | 
			
		||||
	var tn = tid;
 | 
			
		||||
	if ((tn + '').indexOf('f-') === 0)
 | 
			
		||||
	if ((tn + '').indexOf('f-') === 0) {
 | 
			
		||||
		tn = mp.order.indexOf(tn);
 | 
			
		||||
		if (tn < 0)
 | 
			
		||||
			return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (tn >= mp.order.length) {
 | 
			
		||||
		if (mpl.pb_mode == 'loop-folder') {
 | 
			
		||||
@@ -1385,8 +1422,7 @@ function autoplay_blocked(seek) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// autoplay linked track
 | 
			
		||||
(function () {
 | 
			
		||||
function play_linked() {
 | 
			
		||||
	var v = location.hash;
 | 
			
		||||
	if (v && v.indexOf('#af-') === 0) {
 | 
			
		||||
		var id = v.slice(2).split('&');
 | 
			
		||||
@@ -1402,7 +1438,7 @@ function autoplay_blocked(seek) {
 | 
			
		||||
 | 
			
		||||
		return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
 | 
			
		||||
	}
 | 
			
		||||
})();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
var thegrid = (function () {
 | 
			
		||||
@@ -1508,35 +1544,57 @@ var thegrid = (function () {
 | 
			
		||||
	}
 | 
			
		||||
	setsz();
 | 
			
		||||
 | 
			
		||||
	function seltgl(e) {
 | 
			
		||||
		if (e && e.ctrlKey)
 | 
			
		||||
	function gclick(e) {
 | 
			
		||||
		if (e && (e.ctrlKey || e.metaKey))
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
		ev(e);
 | 
			
		||||
		var oth = ebi(this.getAttribute('ref')),
 | 
			
		||||
			td = oth.parentNode.nextSibling,
 | 
			
		||||
			href = this.getAttribute('href'),
 | 
			
		||||
			aplay = ebi('a' + oth.getAttribute('id')),
 | 
			
		||||
			is_img = /\.(gif|jpe?g|png|webp)(\?|$)/i.test(href),
 | 
			
		||||
			in_tree = null,
 | 
			
		||||
			have_sel = QS('#files tr.sel'),
 | 
			
		||||
			td = oth.closest('td').nextSibling,
 | 
			
		||||
			tr = td.parentNode;
 | 
			
		||||
 | 
			
		||||
		td.click();
 | 
			
		||||
		this.setAttribute('class', tr.getAttribute('class'));
 | 
			
		||||
	}
 | 
			
		||||
		if (/\/(\?|$)/.test(href)) {
 | 
			
		||||
			var ta = QSA('#treeul a.hl+ul>li>a+a'),
 | 
			
		||||
				txt = oth.textContent.slice(0, -1);
 | 
			
		||||
 | 
			
		||||
	function bgopen(e) {
 | 
			
		||||
			for (var a = 0, aa = ta.length; a < aa; a++) {
 | 
			
		||||
				if (ta[a].textContent == txt) {
 | 
			
		||||
					in_tree = ta[a];
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (r.sel) {
 | 
			
		||||
			td.click();
 | 
			
		||||
			this.setAttribute('class', tr.getAttribute('class'));
 | 
			
		||||
		}
 | 
			
		||||
		else if (widget.is_open && aplay)
 | 
			
		||||
			aplay.click();
 | 
			
		||||
 | 
			
		||||
		else if (in_tree && !have_sel)
 | 
			
		||||
			in_tree.click();
 | 
			
		||||
 | 
			
		||||
		else if (!is_img && have_sel)
 | 
			
		||||
			window.open(href, '_blank');
 | 
			
		||||
 | 
			
		||||
		else return true;
 | 
			
		||||
		ev(e);
 | 
			
		||||
		var url = this.getAttribute('href');
 | 
			
		||||
		window.open(url, '_blank');
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	r.loadsel = function () {
 | 
			
		||||
		if (r.dirty)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var ths = QSA('#ggrid>a'),
 | 
			
		||||
			have_sel = !!QS('#files tr.sel');
 | 
			
		||||
		var ths = QSA('#ggrid>a');
 | 
			
		||||
 | 
			
		||||
		for (var a = 0, aa = ths.length; a < aa; a++) {
 | 
			
		||||
			ths[a].onclick = r.sel ? seltgl : have_sel ? bgopen : null;
 | 
			
		||||
			ths[a].setAttribute('class', ebi(ths[a].getAttribute('ref')).parentNode.parentNode.getAttribute('class'));
 | 
			
		||||
			var tr = ebi(ths[a].getAttribute('ref')).closest('tr');
 | 
			
		||||
			ths[a].setAttribute('class', tr.getAttribute('class'));
 | 
			
		||||
		}
 | 
			
		||||
		var uns = QS('#ggrid a[ref="unsearch"]');
 | 
			
		||||
		if (uns)
 | 
			
		||||
@@ -1567,6 +1625,8 @@ var thegrid = (function () {
 | 
			
		||||
 | 
			
		||||
			if (r.thumbs) {
 | 
			
		||||
				ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
 | 
			
		||||
				if (href == "#")
 | 
			
		||||
					ihref = '/.cpr/ico/⏏️';
 | 
			
		||||
			}
 | 
			
		||||
			else if (isdir) {
 | 
			
		||||
				ihref = '/.cpr/ico/folder';
 | 
			
		||||
@@ -1594,6 +1654,11 @@ var thegrid = (function () {
 | 
			
		||||
				ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
 | 
			
		||||
		}
 | 
			
		||||
		ebi('ggrid').innerHTML = html.join('\n');
 | 
			
		||||
 | 
			
		||||
		var ths = QSA('#ggrid>a');
 | 
			
		||||
		for (var a = 0, aa = ths.length; a < aa; a++)
 | 
			
		||||
			ths[a].onclick = gclick;
 | 
			
		||||
 | 
			
		||||
		r.dirty = false;
 | 
			
		||||
		r.bagit();
 | 
			
		||||
		r.loadsel();
 | 
			
		||||
@@ -1681,10 +1746,14 @@ document.onkeydown = function (e) {
 | 
			
		||||
	if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || e.isComposing)
 | 
			
		||||
	if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	var k = (e.code + ''), pos = -1, n;
 | 
			
		||||
 | 
			
		||||
	if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	if (k.indexOf('Digit') === 0)
 | 
			
		||||
		pos = parseInt(k.slice(-1)) * 0.1;
 | 
			
		||||
 | 
			
		||||
@@ -1720,6 +1789,14 @@ document.onkeydown = function (e) {
 | 
			
		||||
	if (k == 'KeyT')
 | 
			
		||||
		return ebi('thumbs').click();
 | 
			
		||||
 | 
			
		||||
	if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
 | 
			
		||||
		if (k == 'KeyA')
 | 
			
		||||
			return QS('#twig').click();
 | 
			
		||||
 | 
			
		||||
		if (k == 'KeyD')
 | 
			
		||||
			return QS('#twobytwo').click();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (thegrid.en) {
 | 
			
		||||
		if (k == 'KeyS')
 | 
			
		||||
			return ebi('gridsel').click();
 | 
			
		||||
@@ -1802,6 +1879,7 @@ document.onkeydown = function (e) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var search_timeout,
 | 
			
		||||
		defer_timeout,
 | 
			
		||||
		search_in_progress = 0;
 | 
			
		||||
 | 
			
		||||
	function ev_search_input() {
 | 
			
		||||
@@ -1816,9 +1894,29 @@ document.onkeydown = function (e) {
 | 
			
		||||
		if (id != "q_raw")
 | 
			
		||||
			encode_query();
 | 
			
		||||
 | 
			
		||||
		clearTimeout(search_timeout);
 | 
			
		||||
		if (Date.now() - search_in_progress > 30 * 1000)
 | 
			
		||||
		set_vq();
 | 
			
		||||
 | 
			
		||||
		clearTimeout(defer_timeout);
 | 
			
		||||
		defer_timeout = setTimeout(try_search, 2000);
 | 
			
		||||
		try_search();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function try_search() {
 | 
			
		||||
		if (Date.now() - search_in_progress > 30 * 1000) {
 | 
			
		||||
			clearTimeout(defer_timeout);
 | 
			
		||||
			clearTimeout(search_timeout);
 | 
			
		||||
			search_timeout = setTimeout(do_search, 200);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function set_vq() {
 | 
			
		||||
		if (search_in_progress)
 | 
			
		||||
			return;
 | 
			
		||||
 | 
			
		||||
		var q = ebi('q_raw').value,
 | 
			
		||||
			vq = ebi('files').getAttribute('q_raw');
 | 
			
		||||
 | 
			
		||||
		srch_msg(false, (q == vq) ? '' : 'search results below are from a previous query:\n  ' + (vq ? vq : '(*)'));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function encode_query() {
 | 
			
		||||
@@ -1888,7 +1986,8 @@ document.onkeydown = function (e) {
 | 
			
		||||
		xhr.setRequestHeader('Content-Type', 'text/plain');
 | 
			
		||||
		xhr.onreadystatechange = xhr_search_results;
 | 
			
		||||
		xhr.ts = Date.now();
 | 
			
		||||
		xhr.send(JSON.stringify({ "q": ebi('q_raw').value }));
 | 
			
		||||
		xhr.q_raw = ebi('q_raw').value;
 | 
			
		||||
		xhr.send(JSON.stringify({ "q": xhr.q_raw }));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function xhr_search_results() {
 | 
			
		||||
@@ -1959,6 +2058,8 @@ document.onkeydown = function (e) {
 | 
			
		||||
 | 
			
		||||
		ofiles.innerHTML = html.join('\n');
 | 
			
		||||
		ofiles.setAttribute("ts", this.ts);
 | 
			
		||||
		ofiles.setAttribute("q_raw", this.q_raw);
 | 
			
		||||
		set_vq();
 | 
			
		||||
		mukey.render();
 | 
			
		||||
		reload_browser();
 | 
			
		||||
		filecols.set_style(['File Name']);
 | 
			
		||||
@@ -1970,6 +2071,7 @@ document.onkeydown = function (e) {
 | 
			
		||||
		ev(e);
 | 
			
		||||
		treectl.show();
 | 
			
		||||
		ebi('files').innerHTML = orig_html;
 | 
			
		||||
		ebi('files').removeAttribute('q_raw');
 | 
			
		||||
		orig_html = null;
 | 
			
		||||
		msel.render();
 | 
			
		||||
		reload_browser();
 | 
			
		||||
@@ -2188,6 +2290,9 @@ var treectl = (function () {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function treego(e) {
 | 
			
		||||
		if (e && (e.ctrlKey || e.metaKey))
 | 
			
		||||
			return true;
 | 
			
		||||
 | 
			
		||||
		ev(e);
 | 
			
		||||
		if (this.getAttribute('class') == 'hl' &&
 | 
			
		||||
			this.previousSibling.textContent == '-') {
 | 
			
		||||
@@ -2965,3 +3070,4 @@ function reload_browser(not_mp) {
 | 
			
		||||
reload_browser(true);
 | 
			
		||||
mukey.render();
 | 
			
		||||
msel.render();
 | 
			
		||||
play_linked();
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@
 | 
			
		||||
	<div>{{ logues[1] }}</div><br />
 | 
			
		||||
	{%- endif %}
 | 
			
		||||
	
 | 
			
		||||
	<h2><a href="{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
 | 
			
		||||
	<h2><a href="/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
 | 
			
		||||
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
window.onerror = vis_exh;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function goto_up2k() {
 | 
			
		||||
    if (up2k === false)
 | 
			
		||||
@@ -1309,6 +1307,17 @@ function up2k_init(subtle) {
 | 
			
		||||
    }
 | 
			
		||||
    tt.init();
 | 
			
		||||
 | 
			
		||||
    function bumpthread2(e) {
 | 
			
		||||
        if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (e.code == 'ArrowUp')
 | 
			
		||||
            bumpthread(1);
 | 
			
		||||
 | 
			
		||||
        if (e.code == 'ArrowDown')
 | 
			
		||||
            bumpthread(-1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function bumpthread(dir) {
 | 
			
		||||
        try {
 | 
			
		||||
            dir.stopPropagation();
 | 
			
		||||
@@ -1319,7 +1328,7 @@ function up2k_init(subtle) {
 | 
			
		||||
        if (dir.target) {
 | 
			
		||||
            clmod(obj, 'err', 1);
 | 
			
		||||
            var v = Math.floor(parseInt(obj.value));
 | 
			
		||||
            if (v < 1 || v > 8 || v !== v)
 | 
			
		||||
            if (v < 0 || v > 64 || v !== v)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            parallel_uploads = v;
 | 
			
		||||
@@ -1330,11 +1339,11 @@ function up2k_init(subtle) {
 | 
			
		||||
 | 
			
		||||
        parallel_uploads += dir;
 | 
			
		||||
 | 
			
		||||
        if (parallel_uploads < 1)
 | 
			
		||||
            parallel_uploads = 1;
 | 
			
		||||
        if (parallel_uploads < 0)
 | 
			
		||||
            parallel_uploads = 0;
 | 
			
		||||
 | 
			
		||||
        if (parallel_uploads > 8)
 | 
			
		||||
            parallel_uploads = 8;
 | 
			
		||||
        if (parallel_uploads > 16)
 | 
			
		||||
            parallel_uploads = 16;
 | 
			
		||||
 | 
			
		||||
        obj.value = parallel_uploads;
 | 
			
		||||
        bumpthread({ "target": 1 })
 | 
			
		||||
@@ -1430,6 +1439,7 @@ function up2k_init(subtle) {
 | 
			
		||||
        bumpthread(-1);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ebi('nthread').onkeydown = bumpthread2;
 | 
			
		||||
    ebi('nthread').addEventListener('input', bumpthread, false);
 | 
			
		||||
    ebi('multitask').addEventListener('click', tgl_multitask, false);
 | 
			
		||||
    ebi('ask_up').addEventListener('click', tgl_ask_up, false);
 | 
			
		||||
@@ -1443,7 +1453,10 @@ function up2k_init(subtle) {
 | 
			
		||||
        nodes[a].addEventListener('touchend', nop, false);
 | 
			
		||||
 | 
			
		||||
    set_fsearch();
 | 
			
		||||
    bumpthread({ "target": 1 })
 | 
			
		||||
    bumpthread({ "target": 1 });
 | 
			
		||||
    if (parallel_uploads < 1)
 | 
			
		||||
        bumpthread(1);
 | 
			
		||||
 | 
			
		||||
    return { "init_deps": init_deps, "set_fsearch": set_fsearch }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,9 @@ function esc(txt) {
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
function vis_exh(msg, url, lineNo, columnNo, error) {
 | 
			
		||||
    if (!window.onerror)
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    window.onerror = undefined;
 | 
			
		||||
    window['vis_exh'] = null;
 | 
			
		||||
    var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
 | 
			
		||||
@@ -67,6 +70,9 @@ function ev(e) {
 | 
			
		||||
    if (e.stopPropagation)
 | 
			
		||||
        e.stopPropagation();
 | 
			
		||||
 | 
			
		||||
    if (e.stopImmediatePropagation)
 | 
			
		||||
        e.stopImmediatePropagation();
 | 
			
		||||
 | 
			
		||||
    e.returnValue = false;
 | 
			
		||||
    return e;
 | 
			
		||||
}
 | 
			
		||||
@@ -360,11 +366,11 @@ function get_vpath() {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function get_pwd() {
 | 
			
		||||
	var pwd = ('; ' + document.cookie).split('; cppwd=');
 | 
			
		||||
	if (pwd.length < 2)
 | 
			
		||||
		return null;
 | 
			
		||||
    var pwd = ('; ' + document.cookie).split('; cppwd=');
 | 
			
		||||
    if (pwd.length < 2)
 | 
			
		||||
        return null;
 | 
			
		||||
 | 
			
		||||
	return pwd[1].split(';')[0];
 | 
			
		||||
    return pwd[1].split(';')[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,10 +6,10 @@ import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform,
 | 
			
		||||
import subprocess as sp
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
run me with any version of python, i will unpack and run copyparty
 | 
			
		||||
pls don't edit this file with a text editor,
 | 
			
		||||
  it breaks the compressed stuff at the end
 | 
			
		||||
 | 
			
		||||
(but please don't edit this file with a text editor
 | 
			
		||||
  since that would probably corrupt the binary stuff at the end)
 | 
			
		||||
run me with any version of python, i will unpack and run copyparty
 | 
			
		||||
 | 
			
		||||
there's zero binaries! just plaintext python scripts all the way down
 | 
			
		||||
  so you can easily unpack the archive and inspect it for shady stuff
 | 
			
		||||
 
 | 
			
		||||
@@ -121,6 +121,7 @@ class VHttpConn(object):
 | 
			
		||||
        self.log_src = "a"
 | 
			
		||||
        self.lf_url = None
 | 
			
		||||
        self.hsrv = VHttpSrv()
 | 
			
		||||
        self.nreq = 0
 | 
			
		||||
        self.nbyte = 0
 | 
			
		||||
        self.workload = 0
 | 
			
		||||
        self.ico = None
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user