Skip to content

Commit

Permalink
list incoming files + ETA in controlpanel
Browse files Browse the repository at this point in the history
  • Loading branch information
9001 committed Sep 10, 2024
1 parent c79eaa0 commit 609c592
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 24 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
![copyparty-cpanel-upload-eta-or8](https://github.com/user-attachments/assets/fd275ffa-698c-4fca-a307-4d2181269a6a)
## file manager
cut/paste, rename, and delete files/folders (if you have permission)
Expand Down
1 change: 1 addition & 0 deletions copyparty/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
33 changes: 30 additions & 3 deletions copyparty/httpcli.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
relchk,
ren_open,
runhook,
s2hms,
s3enc,
sanitize_fn,
sanitize_vpath,
Expand Down Expand Up @@ -3939,11 +3940,30 @@ def tx_mounts(self) -> bool:
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,
Expand All @@ -3968,6 +3988,12 @@ def tx_mounts(self) -> bool:
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:
Expand All @@ -3991,6 +4017,7 @@ def tx_mounts(self) -> bool:
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"],
Expand Down
70 changes: 50 additions & 20 deletions copyparty/up2k.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,33 +268,70 @@ def _unblock(self) -> None:
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:
mtpq += cur.execute(q).fetchone()[0]
except:
pass
self.mutex.release()
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,
"scanning": bool(self.pp),
"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)
),
}
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:
Expand Down Expand Up @@ -2910,9 +2947,12 @@ def _handle_json(self, cj: dict[str, Any], depth: int = 1) -> dict[str, Any]:
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:
Expand Down Expand Up @@ -3013,18 +3053,8 @@ def _handle_json(self, cj: dict[str, Any], depth: int = 1) -> dict[str, Any]:
"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"]:
Expand Down
12 changes: 12 additions & 0 deletions copyparty/web/splash.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ <h1>admin panel:</h1>
</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>
Expand Down
2 changes: 2 additions & 0 deletions copyparty/web/splash.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -78,6 +79,7 @@ var Ls = {
"ta1": "请先输入新密码",
"ta2": "重复以确认新密码:",
"ta3": "发现拼写错误;请重试",
"aa1": "正在接收的文件:", //m
}
};

Expand Down
2 changes: 1 addition & 1 deletion tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ def __init__(self, a=None, v=None, c=None, **ka0):
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"
Expand Down

0 comments on commit 609c592

Please sign in to comment.