mirror of
https://github.com/9001/copyparty.git
synced 2025-08-17 09:02:15 -06:00
list incoming files + ETA in controlpanel
This commit is contained in:
parent
c79eaa089a
commit
609c5921d4
|
@ -43,6 +43,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|||
* [unpost](#unpost) - undo/delete accidental uploads
|
||||
* [self-destruct](#self-destruct) - uploads can be given a lifetime
|
||||
* [race the beam](#race-the-beam) - download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm))
|
||||
* [incoming files](#incoming-files) - the control-panel shows the ETA for all incoming files
|
||||
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
||||
* [shares](#shares) - share a file or folder by creating a temporary link
|
||||
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
||||
|
@ -731,6 +732,13 @@ download files while they're still uploading ([demo video](http://a.ocv.me/pub/g
|
|||
requires the file to be uploaded using up2k (which is the default drag-and-drop uploader), alternatively the command-line program
|
||||
|
||||
|
||||
### incoming files
|
||||
|
||||
the control-panel shows the ETA for all incoming files , but only for files being uploaded into volumes where you have read-access
|
||||
|
||||

|
||||
|
||||
|
||||
## file manager
|
||||
|
||||
cut/paste, rename, and delete files/folders (if you have permission)
|
||||
|
|
|
@ -1230,6 +1230,7 @@ def add_optouts(ap):
|
|||
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||
ap2.add_argument("--no-tarcmp", action="store_true", help="disable download as compressed tar (?tar=gz, ?tar=bz2, ?tar=xz, ?tar=gz:9, ...)")
|
||||
ap2.add_argument("--no-lifetime", action="store_true", help="do not allow clients (or server config) to schedule an upload to be deleted after a given time")
|
||||
ap2.add_argument("--no-up-list", action="store_true", help="don't show list of incoming files in controlpanel")
|
||||
ap2.add_argument("--no-pipe", action="store_true", help="disable race-the-beam (lockstep download of files which are currently being uploaded) (volflag=nopipe)")
|
||||
ap2.add_argument("--no-db-ip", action="store_true", help="do not write uploader IPs into the database")
|
||||
|
||||
|
|
|
@ -87,6 +87,7 @@ from .util import (
|
|||
relchk,
|
||||
ren_open,
|
||||
runhook,
|
||||
s2hms,
|
||||
s3enc,
|
||||
sanitize_fn,
|
||||
sanitize_vpath,
|
||||
|
@ -3939,11 +3940,30 @@ class HttpCli(object):
|
|||
for y in [self.rvol, self.wvol, self.avol]
|
||||
]
|
||||
|
||||
if self.avol and not self.args.no_rescan:
|
||||
x = self.conn.hsrv.broker.ask("up2k.get_state")
|
||||
ups = []
|
||||
now = time.time()
|
||||
get_vst = self.avol and not self.args.no_rescan
|
||||
get_ups = self.rvol and not self.args.no_up_list and self.uname or ""
|
||||
if get_vst or get_ups:
|
||||
x = self.conn.hsrv.broker.ask("up2k.get_state", get_vst, get_ups)
|
||||
vs = json.loads(x.get())
|
||||
vstate = {("/" + k).rstrip("/") + "/": v for k, v in vs["volstate"].items()}
|
||||
else:
|
||||
try:
|
||||
for rem, sz, t0, poke, vp in vs["ups"]:
|
||||
fdone = max(0.001, 1 - rem)
|
||||
td = max(0.1, now - t0)
|
||||
rd, fn = vsplit(vp.replace(os.sep, "/"))
|
||||
if not rd:
|
||||
rd = "/"
|
||||
erd = quotep(rd)
|
||||
rds = rd.replace("/", " / ")
|
||||
spd = humansize(sz * fdone / td, True) + "/s"
|
||||
eta = s2hms((td / fdone) - td, True)
|
||||
idle = s2hms(now - poke, True)
|
||||
ups.append((int(100 * fdone), spd, eta, idle, erd, rds, fn))
|
||||
except Exception as ex:
|
||||
self.log("failed to list upload progress: %r" % (ex,), 1)
|
||||
if not get_vst:
|
||||
vstate = {}
|
||||
vs = {
|
||||
"scanning": None,
|
||||
|
@ -3968,6 +3988,12 @@ class HttpCli(object):
|
|||
for k in ["scanning", "hashq", "tagq", "mtpq", "dbwt"]:
|
||||
txt += " {}({})".format(k, vs[k])
|
||||
|
||||
if ups:
|
||||
txt += "\n\nincoming files:"
|
||||
for zt in ups:
|
||||
txt += "\n%s" % (", ".join((str(x) for x in zt)),)
|
||||
txt += "\n"
|
||||
|
||||
if rvol:
|
||||
txt += "\nyou can browse:"
|
||||
for v in rvol:
|
||||
|
@ -3991,6 +4017,7 @@ class HttpCli(object):
|
|||
avol=avol,
|
||||
in_shr=self.args.shr and self.vpath.startswith(self.args.shr[1:]),
|
||||
vstate=vstate,
|
||||
ups=ups,
|
||||
scanning=vs["scanning"],
|
||||
hashq=vs["hashq"],
|
||||
tagq=vs["tagq"],
|
||||
|
|
|
@ -268,19 +268,29 @@ class Up2k(object):
|
|||
if not self.stop:
|
||||
self.log("uploads are now possible", 2)
|
||||
|
||||
def get_state(self) -> str:
|
||||
def get_state(self, get_q: bool, uname: str) -> str:
|
||||
mtpq: Union[int, str] = 0
|
||||
ups = []
|
||||
up_en = not self.args.no_up_list
|
||||
q = "select count(w) from mt where k = 't:mtp'"
|
||||
got_lock = False if PY2 else self.mutex.acquire(timeout=0.5)
|
||||
if got_lock:
|
||||
for cur in self.cur.values():
|
||||
try:
|
||||
for cur in self.cur.values() if get_q else []:
|
||||
try:
|
||||
mtpq += cur.execute(q).fetchone()[0]
|
||||
except:
|
||||
pass
|
||||
if uname and up_en:
|
||||
ups = self._active_uploads(uname)
|
||||
finally:
|
||||
self.mutex.release()
|
||||
else:
|
||||
mtpq = "(?)"
|
||||
if up_en:
|
||||
ups = [(0, 0, time.time(), "cannot show list (server too busy)")]
|
||||
|
||||
ups.sort(reverse=True)
|
||||
|
||||
ret = {
|
||||
"volstate": self.volstate,
|
||||
|
@ -288,6 +298,7 @@ class Up2k(object):
|
|||
"hashq": self.n_hashq,
|
||||
"tagq": self.n_tagq,
|
||||
"mtpq": mtpq,
|
||||
"ups": ups,
|
||||
"dbwu": "{:.2f}".format(self.db_act),
|
||||
"dbwt": "{:.2f}".format(
|
||||
min(1000 * 24 * 60 * 60 - 1, time.time() - self.db_act)
|
||||
|
@ -295,6 +306,32 @@ class Up2k(object):
|
|||
}
|
||||
return json.dumps(ret, separators=(",\n", ": "))
|
||||
|
||||
def _active_uploads(self, uname: str) -> list[tuple[float, int, int, str]]:
|
||||
ret = []
|
||||
for vtop in self.asrv.vfs.aread[uname]:
|
||||
vfs = self.asrv.vfs.all_vols.get(vtop)
|
||||
if not vfs: # dbv only
|
||||
continue
|
||||
ptop = vfs.realpath
|
||||
tab = self.registry.get(ptop)
|
||||
if not tab:
|
||||
continue
|
||||
for job in tab.values():
|
||||
ineed = len(job["need"])
|
||||
ihash = len(job["hash"])
|
||||
if ineed == ihash or not ineed:
|
||||
continue
|
||||
|
||||
zt = (
|
||||
ineed / ihash,
|
||||
job["size"],
|
||||
int(job["t0"]),
|
||||
int(job["poke"]),
|
||||
djoin(vtop, job["prel"], job["name"]),
|
||||
)
|
||||
ret.append(zt)
|
||||
return ret
|
||||
|
||||
def find_job_by_ap(self, ptop: str, ap: str) -> str:
|
||||
try:
|
||||
if ANYWIN:
|
||||
|
@ -2910,9 +2947,12 @@ class Up2k(object):
|
|||
job = deepcopy(job)
|
||||
job["wark"] = wark
|
||||
job["at"] = cj.get("at") or time.time()
|
||||
zs = "lmod ptop vtop prel name host user addr poke"
|
||||
zs = "vtop ptop prel name lmod host user addr poke"
|
||||
for k in zs.split():
|
||||
job[k] = cj.get(k) or ""
|
||||
for k in ("life", "replace"):
|
||||
if k in cj:
|
||||
job[k] = cj[k]
|
||||
|
||||
pdir = djoin(cj["ptop"], cj["prel"])
|
||||
if rand:
|
||||
|
@ -3013,18 +3053,8 @@ class Up2k(object):
|
|||
"busy": {},
|
||||
}
|
||||
# client-provided, sanitized by _get_wark: name, size, lmod
|
||||
for k in [
|
||||
"host",
|
||||
"user",
|
||||
"addr",
|
||||
"vtop",
|
||||
"ptop",
|
||||
"prel",
|
||||
"name",
|
||||
"size",
|
||||
"lmod",
|
||||
"poke",
|
||||
]:
|
||||
zs = "vtop ptop prel name size lmod host user addr poke"
|
||||
for k in zs.split():
|
||||
job[k] = cj[k]
|
||||
|
||||
for k in ["life", "replace"]:
|
||||
|
|
|
@ -60,6 +60,18 @@
|
|||
</div>
|
||||
{%- endif %}
|
||||
|
||||
{%- if ups %}
|
||||
<h1 id="aa">incoming files:</h1>
|
||||
<table class="vols">
|
||||
<thead><tr><th>%</th><th>speed</th><th>eta</th><th>idle</th><th>dir</th><th>file</th></tr></thead>
|
||||
<tbody>
|
||||
{% for u in ups %}
|
||||
<tr><td>{{ u[0] }}</td><td>{{ u[1] }}</td><td>{{ u[2] }}</td><td>{{ u[3] }}</td><td><a href="{{ u[4] }}">{{ u[5]|e }}</a></td><td>{{ u[6]|e }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{%- endif %}
|
||||
|
||||
{%- if rvol %}
|
||||
<h1 id="f">you can browse:</h1>
|
||||
<ul>
|
||||
|
|
|
@ -33,6 +33,7 @@ var Ls = {
|
|||
"ta1": "du må skrive et nytt passord først",
|
||||
"ta2": "gjenta for å bekrefte nytt passord:",
|
||||
"ta3": "fant en skrivefeil; vennligst prøv igjen",
|
||||
"aa1": "innkommende:",
|
||||
},
|
||||
"eng": {
|
||||
"d2": "shows the state of all active threads",
|
||||
|
@ -78,6 +79,7 @@ var Ls = {
|
|||
"ta1": "请先输入新密码",
|
||||
"ta2": "重复以确认新密码:",
|
||||
"ta3": "发现拼写错误;请重试",
|
||||
"aa1": "正在接收的文件:", //m
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ class Cfg(Namespace):
|
|||
ex = "chpw daw dav_auth dav_inf dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink ih ihead magic hardlink_only nid nih no_acode no_athumb no_dav no_db_ip no_del no_dupe no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tarcmp no_thumb no_vthumb no_zip nrand nw og og_no_head og_s_title q rand smb srch_dbg stats uqe vague_403 vc ver write_uplog xdev xlink xvol zs"
|
||||
ka.update(**{k: False for k in ex.split()})
|
||||
|
||||
ex = "dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_voldump re_dhash plain_ip"
|
||||
ex = "dedup dotpart dotsrch hook_v no_dhash no_fastboot no_fpool no_htp no_rescan no_sendfile no_ses no_snap no_up_list no_voldump re_dhash plain_ip"
|
||||
ka.update(**{k: True for k in ex.split()})
|
||||
|
||||
ex = "ah_cli ah_gen css_browser hist js_browser js_other mime mimes no_forget no_hash no_idx nonsus_urls og_tpl og_ua"
|
||||
|
|
Loading…
Reference in a new issue