From fc2754cba589774a4ff665546653e753c72ba7ce Mon Sep 17 00:00:00 2001 From: ed Date: Mon, 29 Sep 2025 22:16:57 +0000 Subject: [PATCH] option to delete .PARTIAL on expiration --- copyparty/__main__.py | 1 + copyparty/cfg.py | 2 ++ copyparty/up2k.py | 8 ++++++-- tests/util.py | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 1fc56b2e..16c48aa4 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1231,6 +1231,7 @@ def add_upload(ap): ap2.add_argument("--no-snap", action="store_true", help="disable snapshots -- forget unfinished uploads on shutdown; don't create .hist/up2k.snap files -- abandoned/interrupted uploads must be cleaned up manually") ap2.add_argument("--snap-wri", metavar="SEC", type=int, default=300, help="write upload state to ./hist/up2k.snap every \033[33mSEC\033[0m seconds; allows resuming incomplete uploads after a server crash") ap2.add_argument("--snap-drop", metavar="MIN", type=float, default=1440.0, help="forget unfinished uploads after \033[33mMIN\033[0m minutes; impossible to resume them after that (360=6h, 1440=24h)") + ap2.add_argument("--rm-partial", action="store_true", help="delete the .PARTIAL file when an unfinished upload expires after \033[33m--snap-drop\033[0m (volflag=rm_partial)") ap2.add_argument("--u2ts", metavar="TXT", type=u, default="c", help="how to timestamp uploaded files; [\033[32mc\033[0m]=client-last-modified, [\033[32mu\033[0m]=upload-time, [\033[32mfc\033[0m]=force-c, [\033[32mfu\033[0m]=force-u (volflag=u2ts)") ap2.add_argument("--rotf-tz", metavar="TXT", type=u, default="UTC", help="default timezone for the rotf upload rule; examples: [\033[32mEurope/Oslo\033[0m], [\033[32mAmerica/Toronto\033[0m], [\033[32mAntarctica/South_Pole\033[0m] (volflag=rotf_tz)") ap2.add_argument("--rand", action="store_true", help="force randomized filenames, \033[33m--nrand\033[0m chars long (volflag=rand)") diff --git a/copyparty/cfg.py b/copyparty/cfg.py index 7acf75c7..93c146af 100644 --- a/copyparty/cfg.py +++ b/copyparty/cfg.py @@ -55,6 +55,7 @@ def vf_bmap() -> dict[str, str]: "opds", "rand", "reflink", + "rm_partial", "rmagic", "rss", "wo_up_readme", @@ -195,6 +196,7 @@ flagcats = { "wram": "allow uploading into ramdisks", "sparse": "force use of sparse files, mainly for s3-backed storage", "nosparse": "deny use of sparse files, mainly for slow storage", + "rm_partial": "delete unfinished uploads from HDD when they timeout", "daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files", "nosub": "forces all uploads into the top folder of the vfs", "magic": "enables filetype detection for nameless uploads", diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 95754569..db6931b3 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -5289,17 +5289,21 @@ class Up2k(object): self.log("\n".join([t] + vis)) for job in rm: del reg[job["wark"]] + rsv_cleared = False try: # remove the filename reservation path = djoin(job["ptop"], job["prel"], job["name"]) if bos.path.getsize(path) == 0: bos.unlink(path) + rsv_cleared = True except: pass try: - if len(job["hash"]) == len(job["need"]): - # PARTIAL is empty, delete that too + if len(job["hash"]) == len(job["need"]) or ( + rsv_cleared and "rm_partial" in self.flags[job["ptop"]] + ): + # PARTIAL is empty (hash==need) or --rm-partial, so delete that too path = djoin(job["ptop"], job["prel"], job["tnam"]) bos.unlink(path) except: diff --git a/tests/util.py b/tests/util.py index e3663c8c..527f9e61 100644 --- a/tests/util.py +++ b/tests/util.py @@ -143,7 +143,7 @@ class Cfg(Namespace): def __init__(self, a=None, v=None, c=None, **ka0): ka = {} - ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rmagic rss smb srch_dbg srch_excl srch_icase stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" + ex = "allow_flac allow_wav chpw cookie_lax daw dav_auth dav_mac dav_rt e2d e2ds e2dsa e2t e2ts e2tsr e2v e2vu e2vp early_ban ed emp exp force_js getmod grid gsel hardlink hardlink_only http_no_tcp ih ihead localtime log_badxml magic md_no_br nid nih no_acode no_athumb no_bauth no_clone no_cp no_dav no_db_ip no_del no_dirsz no_dupe no_fnugg no_lifetime no_logues no_mv no_pipe no_poll no_readme no_robots no_sb_md no_sb_lg no_scandir no_tail no_tarcmp no_thumb no_vthumb no_u2abrt no_zip nrand nsort nw og og_no_head og_s_title ohead opds q rand re_dirsz reflink rm_partial rmagic rss smb srch_dbg srch_excl srch_icase stats uqe usernames vague_403 vc ver wo_up_readme write_uplog xdev xlink xvol zipmaxu zs" ka.update(**{k: False for k in ex.split()}) ex = "dav_inf 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 wram re_dhash see_dots plain_ip"