Compare commits

...

17 Commits

Author SHA1 Message Date
ed
5649d26077 v0.11.28 2021-06-28 15:36:13 +02:00
ed
92f923effe hotkey for adjusting tree width 2021-06-28 15:34:10 +02:00
ed
0d46d548b9 fix panic when zero accounts 2021-06-28 15:20:40 +02:00
ed
062df3f0c3 point control-panel link to / 2021-06-27 00:52:15 +02:00
ed
789fb53b8e tweaks 2021-06-27 00:49:28 +02:00
ed
351db5a18f ah yes trailing whitespace as markup my good old friend we meet again 2021-06-27 00:20:42 +02:00
ed
aabbd271c8 add debian howto 2021-06-27 00:19:37 +02:00
ed
aae8e0171e v0.11.27 2021-06-25 22:23:21 +02:00
ed
45827a2458 fix exit-search button in gridview 2021-06-25 22:18:16 +02:00
ed
726030296f apparently the html dom-property is not normalized 2021-06-25 22:07:37 +02:00
ed
6659ab3881 ajax subfolders from gridview 2021-06-25 21:49:09 +02:00
ed
c6a103609e fix gridview selection/baguettebox order 2021-06-25 21:35:45 +02:00
ed
c6b3f035e5 gridview audio playback in search results too 2021-06-25 21:12:49 +02:00
ed
2b0a7e378e persist url-password as cookie 2021-06-25 20:39:55 +02:00
ed
b75ce909c8 audio seek with scrollbar on progressbar 2021-06-25 20:24:30 +02:00
ed
229c3f5dab play audio from grid when widget open 2021-06-25 20:04:19 +02:00
ed
ec73094506 v0.11.26 2021-06-25 03:10:43 +02:00
8 changed files with 139 additions and 47 deletions

View File

@@ -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:
@@ -178,9 +193,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 +215,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

View File

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

View File

@@ -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})

View File

@@ -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:
@@ -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)

View File

@@ -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>

View File

@@ -243,7 +243,7 @@ var mpl = (function () {
"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),
};
@@ -522,37 +522,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 +561,7 @@ var widget = (function () {
};
wtico.onclick = function (e) {
if (!touchmode)
ret.toggle(e);
r.toggle(e);
return false;
};
@@ -591,7 +592,7 @@ var widget = (function () {
document.body.removeChild(o);
}, 500);
};
return ret;
return r;
})();
@@ -857,7 +858,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 +872,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 +1203,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 +1405,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 +1421,7 @@ function autoplay_blocked(seek) {
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
}
})();
};
var thegrid = (function () {
@@ -1508,35 +1527,57 @@ var thegrid = (function () {
}
setsz();
function seltgl(e) {
function gclick(e) {
if (e && e.ctrlKey)
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 +1608,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 +1637,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 +1729,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 +1772,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();
@@ -2965,3 +3025,4 @@ function reload_browser(not_mp) {
reload_browser(true);
mukey.render();
msel.render();
play_linked();

View File

@@ -54,7 +54,7 @@
<div>{{ logues[1] }}</div><br />
{%- endif %}
<h2><a href="{{ url_suf }}{{ url_suf and '&amp;' or '?' }}h">control-panel</a></h2>
<h2><a href="/{{ url_suf }}{{ url_suf and '&amp;' or '?' }}h">control-panel</a></h2>
</body>
</html>

View File

@@ -67,6 +67,9 @@ function ev(e) {
if (e.stopPropagation)
e.stopPropagation();
if (e.stopImmediatePropagation)
e.stopImmediatePropagation();
e.returnValue = false;
return e;
}
@@ -360,11 +363,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];
}