copyparty 1.15.7__py3-none-any.whl → 1.15.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
copyparty/__main__.py CHANGED
@@ -1009,7 +1009,7 @@ def add_upload(ap):
1009
1009
  ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="windows-only: minimum size of incoming uploads through up2k before they are made into sparse files")
1010
1010
  ap2.add_argument("--turbo", metavar="LVL", type=int, default=0, help="configure turbo-mode in up2k client; [\033[32m-1\033[0m] = forbidden/always-off, [\033[32m0\033[0m] = default-off and warn if enabled, [\033[32m1\033[0m] = default-off, [\033[32m2\033[0m] = on, [\033[32m3\033[0m] = on and disable datecheck")
1011
1011
  ap2.add_argument("--u2j", metavar="JOBS", type=int, default=2, help="web-client: number of file chunks to upload in parallel; 1 or 2 is good for low-latency (same-country) connections, 4-8 for android clients, 16 for cross-atlantic (max=64)")
1012
- ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for this size. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
1012
+ ap2.add_argument("--u2sz", metavar="N,N,N", type=u, default="1,64,96", help="web-client: default upload chunksize (MiB); sets \033[33mmin,default,max\033[0m in the settings gui. Each HTTP POST will aim for \033[33mdefault\033[0m, and never exceed \033[33mmax\033[0m. Cloudflare max is 96. Big values are good for cross-atlantic but may increase HDD fragmentation on some FS. Disable this optimization with [\033[32m1,1,1\033[0m]")
1013
1013
  ap2.add_argument("--u2sort", metavar="TXT", type=u, default="s", help="upload order; [\033[32ms\033[0m]=smallest-first, [\033[32mn\033[0m]=alphabetical, [\033[32mfs\033[0m]=force-s, [\033[32mfn\033[0m]=force-n -- alphabetical is a bit slower on fiber/LAN but makes it easier to eyeball if everything went fine")
1014
1014
  ap2.add_argument("--write-uplog", action="store_true", help="write POST reports to textfiles in working-directory")
1015
1015
 
copyparty/__version__.py CHANGED
@@ -1,8 +1,8 @@
1
1
  # coding: utf-8
2
2
 
3
- VERSION = (1, 15, 7)
3
+ VERSION = (1, 15, 8)
4
4
  CODENAME = "fill the drives"
5
- BUILD_DT = (2024, 10, 14)
5
+ BUILD_DT = (2024, 10, 16)
6
6
 
7
7
  S_VERSION = ".".join(map(str, VERSION))
8
8
  S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
copyparty/authsrv.py CHANGED
@@ -59,6 +59,7 @@ if PY2:
59
59
  LEELOO_DALLAS = "leeloo_dallas"
60
60
 
61
61
  SEE_LOG = "see log for details"
62
+ SEESLOG = " (see serverlog for details)"
62
63
  SSEELOG = " ({})".format(SEE_LOG)
63
64
  BAD_CFG = "invalid config; {}".format(SEE_LOG)
64
65
  SBADCFG = " ({})".format(BAD_CFG)
copyparty/httpcli.py CHANGED
@@ -1874,7 +1874,7 @@ class HttpCli(object):
1874
1874
  f, fn = ren_open(fn, *open_a, **params)
1875
1875
  try:
1876
1876
  path = os.path.join(fdir, fn)
1877
- post_sz, sha_hex, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
1877
+ post_sz, sha_hex, sha_b64 = hashcopy(reader, f, None, 0, self.args.s_wr_slp)
1878
1878
  finally:
1879
1879
  f.close()
1880
1880
 
@@ -2337,7 +2337,7 @@ class HttpCli(object):
2337
2337
  broker = self.conn.hsrv.broker
2338
2338
  x = broker.ask("up2k.handle_chunks", ptop, wark, chashes)
2339
2339
  response = x.get()
2340
- chashes, chunksize, cstarts, path, lastmod, sprs = response
2340
+ chashes, chunksize, cstarts, path, lastmod, fsize, sprs = response
2341
2341
  maxsize = chunksize * len(chashes)
2342
2342
  cstart0 = cstarts[0]
2343
2343
  locked = chashes # remaining chunks to be received in this request
@@ -2345,6 +2345,50 @@ class HttpCli(object):
2345
2345
  num_left = -1 # num chunks left according to most recent up2k release
2346
2346
  treport = time.time() # ratelimit up2k reporting to reduce overhead
2347
2347
 
2348
+ if "x-up2k-subc" in self.headers:
2349
+ sc_ofs = int(self.headers["x-up2k-subc"])
2350
+ chash = chashes[0]
2351
+
2352
+ u2sc = self.conn.hsrv.u2sc
2353
+ try:
2354
+ sc_pofs, hasher = u2sc[chash]
2355
+ if not sc_ofs:
2356
+ t = "client restarted the chunk; forgetting subchunk offset %d"
2357
+ self.log(t % (sc_pofs,))
2358
+ raise Exception()
2359
+ except:
2360
+ sc_pofs = 0
2361
+ hasher = hashlib.sha512()
2362
+
2363
+ et = "subchunk protocol error; resetting chunk "
2364
+ if sc_pofs != sc_ofs:
2365
+ u2sc.pop(chash, None)
2366
+ t = "%s[%s]: the expected resume-point was %d, not %d"
2367
+ raise Pebkac(400, t % (et, chash, sc_pofs, sc_ofs))
2368
+ if len(cstarts) > 1:
2369
+ u2sc.pop(chash, None)
2370
+ t = "%s[%s]: only a single subchunk can be uploaded in one request; you are sending %d chunks"
2371
+ raise Pebkac(400, t % (et, chash, len(cstarts)))
2372
+ csize = min(chunksize, fsize - cstart0[0])
2373
+ cstart0[0] += sc_ofs # also sets cstarts[0][0]
2374
+ sc_next_ofs = sc_ofs + postsize
2375
+ if sc_next_ofs > csize:
2376
+ u2sc.pop(chash, None)
2377
+ t = "%s[%s]: subchunk offset (%d) plus postsize (%d) exceeds chunksize (%d)"
2378
+ raise Pebkac(400, t % (et, chash, sc_ofs, postsize, csize))
2379
+ else:
2380
+ final_subchunk = sc_next_ofs == csize
2381
+ t = "subchunk %s %d:%d/%d %s"
2382
+ zs = "END" if final_subchunk else ""
2383
+ self.log(t % (chash[:15], sc_ofs, sc_next_ofs, csize, zs), 6)
2384
+ if final_subchunk:
2385
+ u2sc.pop(chash, None)
2386
+ else:
2387
+ u2sc[chash] = (sc_next_ofs, hasher)
2388
+ else:
2389
+ hasher = None
2390
+ final_subchunk = True
2391
+
2348
2392
  try:
2349
2393
  if self.args.nw:
2350
2394
  path = os.devnull
@@ -2375,9 +2419,11 @@ class HttpCli(object):
2375
2419
  reader = read_socket(
2376
2420
  self.sr, self.args.s_rd_sz, min(remains, chunksize)
2377
2421
  )
2378
- post_sz, _, sha_b64 = hashcopy(reader, f, self.args.s_wr_slp)
2422
+ post_sz, _, sha_b64 = hashcopy(
2423
+ reader, f, hasher, 0, self.args.s_wr_slp
2424
+ )
2379
2425
 
2380
- if sha_b64 != chash:
2426
+ if sha_b64 != chash and final_subchunk:
2381
2427
  try:
2382
2428
  self.bakflip(
2383
2429
  f, path, cstart[0], post_sz, chash, sha_b64, vfs.flags
@@ -2409,7 +2455,8 @@ class HttpCli(object):
2409
2455
 
2410
2456
  # be quick to keep the tcp winsize scale;
2411
2457
  # if we can't confirm rn then that's fine
2412
- written.append(chash)
2458
+ if final_subchunk:
2459
+ written.append(chash)
2413
2460
  now = time.time()
2414
2461
  if now - treport < 1:
2415
2462
  continue
@@ -2797,7 +2844,7 @@ class HttpCli(object):
2797
2844
  tabspath = os.path.join(fdir, tnam)
2798
2845
  self.log("writing to {}".format(tabspath))
2799
2846
  sz, sha_hex, sha_b64 = hashcopy(
2800
- p_data, f, self.args.s_wr_slp, max_sz
2847
+ p_data, f, None, max_sz, self.args.s_wr_slp
2801
2848
  )
2802
2849
  if sz == 0:
2803
2850
  raise Pebkac(400, "empty files in post")
@@ -3127,7 +3174,7 @@ class HttpCli(object):
3127
3174
  wunlink(self.log, fp, vfs.flags)
3128
3175
 
3129
3176
  with open(fsenc(fp), "wb", self.args.iobuf) as f:
3130
- sz, sha512, _ = hashcopy(p_data, f, self.args.s_wr_slp)
3177
+ sz, sha512, _ = hashcopy(p_data, f, None, 0, self.args.s_wr_slp)
3131
3178
 
3132
3179
  if lim:
3133
3180
  lim.nup(self.ip)
copyparty/httpsrv.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
+ import hashlib
4
5
  import math
5
6
  import os
6
7
  import re
@@ -141,6 +142,7 @@ class HttpSrv(object):
141
142
  self.t_periodic = None
142
143
 
143
144
  self.u2fh = FHC()
145
+ self.u2sc = {}
144
146
  self.pipes = CachedDict(0.2)
145
147
  self.metrics = Metrics(self)
146
148
  self.nreq = 0
copyparty/up2k.py CHANGED
@@ -20,7 +20,7 @@ from copy import deepcopy
20
20
  from queue import Queue
21
21
 
22
22
  from .__init__ import ANYWIN, PY2, TYPE_CHECKING, WINDOWS, E
23
- from .authsrv import LEELOO_DALLAS, SSEELOG, VFS, AuthSrv
23
+ from .authsrv import LEELOO_DALLAS, SEESLOG, VFS, AuthSrv
24
24
  from .bos import bos
25
25
  from .cfg import vf_bmap, vf_cmap, vf_vmap
26
26
  from .fsutil import Fstab
@@ -2876,9 +2876,6 @@ class Up2k(object):
2876
2876
  "user": cj["user"],
2877
2877
  "addr": ip,
2878
2878
  "at": at,
2879
- "hash": [],
2880
- "need": [],
2881
- "busy": {},
2882
2879
  }
2883
2880
  for k in ["life"]:
2884
2881
  if k in cj:
@@ -2912,17 +2909,20 @@ class Up2k(object):
2912
2909
  hashes2, st = self._hashlist_from_file(orig_ap)
2913
2910
  wark2 = up2k_wark_from_hashlist(self.salt, st.st_size, hashes2)
2914
2911
  if dwark != wark2:
2915
- t = "will not dedup (fs index desync): fs=%s, db=%s, file: %s"
2916
- self.log(t % (wark2, dwark, orig_ap))
2912
+ t = "will not dedup (fs index desync): fs=%s, db=%s, file: %s\n%s"
2913
+ self.log(t % (wark2, dwark, orig_ap, rj))
2917
2914
  lost.append(dupe[3:])
2918
2915
  continue
2919
2916
  data_ok = True
2920
2917
  job = rj
2921
2918
  break
2922
2919
 
2923
- if job and wark in reg:
2924
- # self.log("pop " + wark + " " + job["name"] + " handle_json db", 4)
2925
- del reg[wark]
2920
+ if job:
2921
+ if wark in reg:
2922
+ del reg[wark]
2923
+ job["hash"] = job["need"] = []
2924
+ job["done"] = True
2925
+ job["busy"] = {}
2926
2926
 
2927
2927
  if lost:
2928
2928
  c2 = None
@@ -2950,7 +2950,7 @@ class Up2k(object):
2950
2950
  path = djoin(rj["ptop"], rj["prel"], fn)
2951
2951
  try:
2952
2952
  st = bos.stat(path)
2953
- if st.st_size > 0 or not rj["need"]:
2953
+ if st.st_size > 0 or "done" in rj:
2954
2954
  # upload completed or both present
2955
2955
  break
2956
2956
  except:
@@ -2964,13 +2964,13 @@ class Up2k(object):
2964
2964
  inc_ap = djoin(cj["ptop"], cj["prel"], cj["name"])
2965
2965
  orig_ap = djoin(rj["ptop"], rj["prel"], rj["name"])
2966
2966
 
2967
- if self.args.nw or n4g or not st:
2967
+ if self.args.nw or n4g or not st or "done" not in rj:
2968
2968
  pass
2969
2969
 
2970
2970
  elif st.st_size != rj["size"]:
2971
- t = "will not dedup (fs index desync): {}, size fs={} db={}, mtime fs={} db={}, file: {}"
2971
+ t = "will not dedup (fs index desync): {}, size fs={} db={}, mtime fs={} db={}, file: {}\n{}"
2972
2972
  t = t.format(
2973
- wark, st.st_size, rj["size"], st.st_mtime, rj["lmod"], path
2973
+ wark, st.st_size, rj["size"], st.st_mtime, rj["lmod"], path, rj
2974
2974
  )
2975
2975
  self.log(t)
2976
2976
  del reg[wark]
@@ -2980,8 +2980,8 @@ class Up2k(object):
2980
2980
  hashes2, _ = self._hashlist_from_file(orig_ap)
2981
2981
  wark2 = up2k_wark_from_hashlist(self.salt, st.st_size, hashes2)
2982
2982
  if wark != wark2:
2983
- t = "will not dedup (fs index desync): fs=%s, idx=%s, file: %s"
2984
- self.log(t % (wark2, wark, orig_ap))
2983
+ t = "will not dedup (fs index desync): fs=%s, idx=%s, file: %s\n%s"
2984
+ self.log(t % (wark2, wark, orig_ap, rj))
2985
2985
  del reg[wark]
2986
2986
 
2987
2987
  if job or wark in reg:
@@ -2996,7 +2996,7 @@ class Up2k(object):
2996
2996
  dst = djoin(cj["ptop"], cj["prel"], cj["name"])
2997
2997
  vsrc = djoin(job["vtop"], job["prel"], job["name"])
2998
2998
  vsrc = vsrc.replace("\\", "/") # just for prints anyways
2999
- if job["need"]:
2999
+ if "done" not in job:
3000
3000
  self.log("unfinished:\n {0}\n {1}".format(src, dst))
3001
3001
  err = "partial upload exists at a different location; please resume uploading here instead:\n"
3002
3002
  err += "/" + quotep(vsrc) + " "
@@ -3357,14 +3357,14 @@ class Up2k(object):
3357
3357
 
3358
3358
  def handle_chunks(
3359
3359
  self, ptop , wark , chashes
3360
- ) :
3360
+ ) :
3361
3361
  with self.mutex, self.reg_mutex:
3362
3362
  self.db_act = self.vol_act[ptop] = time.time()
3363
3363
  job = self.registry[ptop].get(wark)
3364
3364
  if not job:
3365
3365
  known = " ".join([x for x in self.registry[ptop].keys()])
3366
3366
  self.log("unknown wark [{}], known: {}".format(wark, known))
3367
- raise Pebkac(400, "unknown wark" + SSEELOG)
3367
+ raise Pebkac(400, "unknown wark" + SEESLOG)
3368
3368
 
3369
3369
  if "t0c" not in job:
3370
3370
  job["t0c"] = time.time()
@@ -3380,7 +3380,7 @@ class Up2k(object):
3380
3380
  try:
3381
3381
  nchunk = uniq.index(chashes[0])
3382
3382
  except:
3383
- raise Pebkac(400, "unknown chunk0 [%s]" % (chashes[0]))
3383
+ raise Pebkac(400, "unknown chunk0 [%s]" % (chashes[0],))
3384
3384
  expanded = [chashes[0]]
3385
3385
  for prefix in chashes[1:]:
3386
3386
  nchunk += 1
@@ -3414,7 +3414,7 @@ class Up2k(object):
3414
3414
  for chash in chashes:
3415
3415
  nchunk = [n for n, v in enumerate(job["hash"]) if v == chash]
3416
3416
  if not nchunk:
3417
- raise Pebkac(400, "unknown chunk %s" % (chash))
3417
+ raise Pebkac(400, "unknown chunk %s" % (chash,))
3418
3418
 
3419
3419
  ofs = [chunksize * x for x in nchunk]
3420
3420
  coffsets.append(ofs)
@@ -3439,7 +3439,7 @@ class Up2k(object):
3439
3439
 
3440
3440
  job["poke"] = time.time()
3441
3441
 
3442
- return chashes, chunksize, coffsets, path, job["lmod"], job["sprs"]
3442
+ return chashes, chunksize, coffsets, path, job["lmod"], job["size"], job["sprs"]
3443
3443
 
3444
3444
  def fast_confirm_chunks(
3445
3445
  self, ptop , wark , chashes
@@ -3508,11 +3508,13 @@ class Up2k(object):
3508
3508
  src = djoin(pdir, job["tnam"])
3509
3509
  dst = djoin(pdir, job["name"])
3510
3510
  except Exception as ex:
3511
- raise Pebkac(500, "finish_upload, wark, " + repr(ex))
3511
+ self.log(min_ex(), 1)
3512
+ raise Pebkac(500, "finish_upload, wark, %r%s" % (ex, SEESLOG))
3512
3513
 
3513
3514
  if job["need"]:
3514
- t = "finish_upload {} with remaining chunks {}"
3515
- raise Pebkac(500, t.format(wark, job["need"]))
3515
+ self.log(min_ex(), 1)
3516
+ t = "finish_upload %s with remaining chunks %s%s"
3517
+ raise Pebkac(500, t % (wark, job["need"], SEESLOG))
3516
3518
 
3517
3519
  upt = job.get("at") or time.time()
3518
3520
  vflags = self.flags[ptop]
@@ -4033,7 +4035,9 @@ class Up2k(object):
4033
4035
  self.db_act = self.vol_act[dbv.realpath] = time.time()
4034
4036
  svpf = "/".join(x for x in [dbv.vpath, vrem, fn[0]] if x)
4035
4037
  if not svpf.startswith(svp + "/"): # assert
4036
- raise Pebkac(500, "mv: bug at {}, top {}".format(svpf, svp))
4038
+ self.log(min_ex(), 1)
4039
+ t = "mv: bug at %s, top %s%s"
4040
+ raise Pebkac(500, t % (svpf, svp, SEESLOG))
4037
4041
 
4038
4042
  dvpf = dvp + svpf[len(svp) :]
4039
4043
  self._mv_file(uname, ip, svpf, dvpf, curs)
@@ -4048,7 +4052,9 @@ class Up2k(object):
4048
4052
  for zsl in (rm_ok, rm_ng):
4049
4053
  for ap in reversed(zsl):
4050
4054
  if not ap.startswith(sabs):
4051
- raise Pebkac(500, "mv_d: bug at {}, top {}".format(ap, sabs))
4055
+ self.log(min_ex(), 1)
4056
+ t = "mv_d: bug at %s, top %s%s"
4057
+ raise Pebkac(500, t % (ap, sabs, SEESLOG))
4052
4058
 
4053
4059
  rem = ap[len(sabs) :].replace(os.sep, "/").lstrip("/")
4054
4060
  vp = vjoin(dvp, rem)
copyparty/util.py CHANGED
@@ -2637,10 +2637,12 @@ def yieldfile(fn , bufsz ) :
2637
2637
  def hashcopy(
2638
2638
  fin ,
2639
2639
  fout ,
2640
- slp = 0,
2641
- max_sz = 0,
2640
+ hashobj ,
2641
+ max_sz ,
2642
+ slp ,
2642
2643
  ) :
2643
- hashobj = hashlib.sha512()
2644
+ if not hashobj:
2645
+ hashobj = hashlib.sha512()
2644
2646
  tlen = 0
2645
2647
  for buf in fin:
2646
2648
  tlen += len(buf)
copyparty/web/a/u2c.py CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env python3
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
- S_VERSION = "2.2"
5
- S_BUILD_DT = "2024-10-13"
4
+ S_VERSION = "2.4"
5
+ S_BUILD_DT = "2024-10-16"
6
6
 
7
7
  """
8
8
  u2c.py: upload to copyparty
@@ -62,6 +62,9 @@ else:
62
62
 
63
63
  unicode = str
64
64
 
65
+
66
+ WTF8 = "replace" if PY2 else "surrogateescape"
67
+
65
68
  VT100 = platform.system() != "Windows"
66
69
 
67
70
 
@@ -228,7 +231,7 @@ class File(object):
228
231
  self.lmod = lmod # type: float
229
232
 
230
233
  self.abs = os.path.join(top, rel) # type: bytes
231
- self.name = self.rel.split(b"/")[-1].decode("utf-8", "replace") # type: str
234
+ self.name = self.rel.split(b"/")[-1].decode("utf-8", WTF8) # type: str
232
235
 
233
236
  # set by get_hashlist
234
237
  self.cids = [] # type: list[tuple[str, int, int]] # [ hash, ofs, sz ]
@@ -267,10 +270,41 @@ class FileSlice(object):
267
270
  raise Exception(9)
268
271
  tlen += clen
269
272
 
270
- self.len = tlen
273
+ self.len = self.tlen = tlen
271
274
  self.cdr = self.car + self.len
272
275
  self.ofs = 0 # type: int
273
- self.f = open(file.abs, "rb", 512 * 1024)
276
+
277
+ self.f = None
278
+ self.seek = self._seek0
279
+ self.read = self._read0
280
+
281
+ def subchunk(self, maxsz, nth):
282
+ if self.tlen <= maxsz:
283
+ return -1
284
+
285
+ if not nth:
286
+ self.car0 = self.car
287
+ self.cdr0 = self.cdr
288
+
289
+ self.car = self.car0 + maxsz * nth
290
+ if self.car >= self.cdr0:
291
+ return -2
292
+
293
+ self.cdr = self.car + min(self.cdr0 - self.car, maxsz)
294
+ self.len = self.cdr - self.car
295
+ self.seek(0)
296
+ return nth
297
+
298
+ def unsub(self):
299
+ self.car = self.car0
300
+ self.cdr = self.cdr0
301
+ self.len = self.tlen
302
+
303
+ def _open(self):
304
+ self.seek = self._seek
305
+ self.read = self._read
306
+
307
+ self.f = open(self.file.abs, "rb", 512 * 1024)
274
308
  self.f.seek(self.car)
275
309
 
276
310
  # https://stackoverflow.com/questions/4359495/what-is-exactly-a-file-like-object-in-python
@@ -282,10 +316,14 @@ class FileSlice(object):
282
316
  except:
283
317
  pass # py27 probably
284
318
 
319
+ def close(self, *a, **ka):
320
+ return # until _open
321
+
285
322
  def tell(self):
286
323
  return self.ofs
287
324
 
288
- def seek(self, ofs, wh=0):
325
+ def _seek(self, ofs, wh=0):
326
+
289
327
  if wh == 1:
290
328
  ofs = self.ofs + ofs
291
329
  elif wh == 2:
@@ -299,12 +337,21 @@ class FileSlice(object):
299
337
  self.ofs = ofs
300
338
  self.f.seek(self.car + ofs)
301
339
 
302
- def read(self, sz):
340
+ def _read(self, sz):
341
+
303
342
  sz = min(sz, self.len - self.ofs)
304
343
  ret = self.f.read(sz)
305
344
  self.ofs += len(ret)
306
345
  return ret
307
346
 
347
+ def _seek0(self, ofs, wh=0):
348
+ self._open()
349
+ return self.seek(ofs, wh)
350
+
351
+ def _read0(self, sz):
352
+ self._open()
353
+ return self.read(sz)
354
+
308
355
 
309
356
  class MTHash(object):
310
357
  def __init__(self, cores):
@@ -557,13 +604,17 @@ def walkdir(err, top, excl, seen):
557
604
  for ap, inf in sorted(statdir(err, top)):
558
605
  if excl.match(ap):
559
606
  continue
560
- yield ap, inf
561
607
  if stat.S_ISDIR(inf.st_mode):
608
+ yield ap, inf
562
609
  try:
563
610
  for x in walkdir(err, ap, excl, seen):
564
611
  yield x
565
612
  except Exception as ex:
566
613
  err.append((ap, str(ex)))
614
+ elif stat.S_ISREG(inf.st_mode):
615
+ yield ap, inf
616
+ else:
617
+ err.append((ap, "irregular filetype 0%o" % (inf.st_mode,)))
567
618
 
568
619
 
569
620
  def walkdirs(err, tops, excl):
@@ -609,11 +660,12 @@ def walkdirs(err, tops, excl):
609
660
 
610
661
  # mostly from copyparty/util.py
611
662
  def quotep(btxt):
663
+ # type: (bytes) -> bytes
612
664
  quot1 = quote(btxt, safe=b"/")
613
665
  if not PY2:
614
666
  quot1 = quot1.encode("ascii")
615
667
 
616
- return quot1.replace(b" ", b"+") # type: ignore
668
+ return quot1.replace(b" ", b"%20") # type: ignore
617
669
 
618
670
 
619
671
  # from copyparty/util.py
@@ -641,7 +693,7 @@ def up2k_chunksize(filesize):
641
693
  while True:
642
694
  for mul in [1, 2]:
643
695
  nchunks = math.ceil(filesize * 1.0 / chunksize)
644
- if nchunks <= 256 or (chunksize >= 32 * 1024 * 1024 and nchunks < 4096):
696
+ if nchunks <= 256 or (chunksize >= 32 * 1024 * 1024 and nchunks <= 4096):
645
697
  return chunksize
646
698
 
647
699
  chunksize += stepsize
@@ -720,7 +772,7 @@ def handshake(ar, file, search):
720
772
  url = file.url
721
773
  else:
722
774
  if b"/" in file.rel:
723
- url = quotep(file.rel.rsplit(b"/", 1)[0]).decode("utf-8", "replace")
775
+ url = quotep(file.rel.rsplit(b"/", 1)[0]).decode("utf-8")
724
776
  else:
725
777
  url = ""
726
778
  url = ar.vtop + url
@@ -766,15 +818,15 @@ def handshake(ar, file, search):
766
818
  if search:
767
819
  return r["hits"], False
768
820
 
769
- file.url = r["purl"]
821
+ file.url = quotep(r["purl"].encode("utf-8", WTF8)).decode("utf-8")
770
822
  file.name = r["name"]
771
823
  file.wark = r["wark"]
772
824
 
773
825
  return r["hash"], r["sprs"]
774
826
 
775
827
 
776
- def upload(fsl, stats):
777
- # type: (FileSlice, str) -> None
828
+ def upload(fsl, stats, maxsz):
829
+ # type: (FileSlice, str, int) -> None
778
830
  """upload a range of file data, defined by one or more `cid` (chunk-hash)"""
779
831
 
780
832
  ctxt = fsl.cids[0]
@@ -792,21 +844,33 @@ def upload(fsl, stats):
792
844
  if stats:
793
845
  headers["X-Up2k-Stat"] = stats
794
846
 
847
+ nsub = 0
795
848
  try:
796
- sc, txt = web.req("POST", fsl.file.url, headers, fsl, MO)
797
-
798
- if sc == 400:
799
- if (
800
- "already being written" in txt
801
- or "already got that" in txt
802
- or "only sibling chunks" in txt
803
- ):
804
- fsl.file.nojoin = 1
805
-
806
- if sc >= 400:
807
- raise Exception("http %s: %s" % (sc, txt))
849
+ while nsub != -1:
850
+ nsub = fsl.subchunk(maxsz, nsub)
851
+ if nsub == -2:
852
+ return
853
+ if nsub >= 0:
854
+ headers["X-Up2k-Subc"] = str(maxsz * nsub)
855
+ headers.pop(CLEN, None)
856
+ nsub += 1
857
+
858
+ sc, txt = web.req("POST", fsl.file.url, headers, fsl, MO)
859
+
860
+ if sc == 400:
861
+ if (
862
+ "already being written" in txt
863
+ or "already got that" in txt
864
+ or "only sibling chunks" in txt
865
+ ):
866
+ fsl.file.nojoin = 1
867
+
868
+ if sc >= 400:
869
+ raise Exception("http %s: %s" % (sc, txt))
808
870
  finally:
809
871
  fsl.f.close()
872
+ if nsub != -1:
873
+ fsl.unsub()
810
874
 
811
875
 
812
876
  class Ctl(object):
@@ -938,7 +1002,7 @@ class Ctl(object):
938
1002
  print(" %d up %s" % (ncs - nc, cid))
939
1003
  stats = "%d/0/0/%d" % (nf, self.nfiles - nf)
940
1004
  fslice = FileSlice(file, [cid])
941
- upload(fslice, stats)
1005
+ upload(fslice, stats, self.ar.szm)
942
1006
 
943
1007
  print(" ok!")
944
1008
  if file.recheck:
@@ -1057,7 +1121,7 @@ class Ctl(object):
1057
1121
  print(" ls ~{0}".format(srd))
1058
1122
  zt = (
1059
1123
  self.ar.vtop,
1060
- quotep(rd.replace(b"\\", b"/")).decode("utf-8", "replace"),
1124
+ quotep(rd.replace(b"\\", b"/")).decode("utf-8"),
1061
1125
  )
1062
1126
  sc, txt = web.req("GET", "%s%s?ls&lt&dots" % zt, {})
1063
1127
  if sc >= 400:
@@ -1066,7 +1130,7 @@ class Ctl(object):
1066
1130
  j = json.loads(txt)
1067
1131
  for f in j["dirs"] + j["files"]:
1068
1132
  rfn = f["href"].split("?")[0].rstrip("/")
1069
- ls[unquote(rfn.encode("utf-8", "replace"))] = f
1133
+ ls[unquote(rfn.encode("utf-8", WTF8))] = f
1070
1134
  except Exception as ex:
1071
1135
  print(" mkdir ~{0} ({1})".format(srd, ex))
1072
1136
 
@@ -1080,7 +1144,7 @@ class Ctl(object):
1080
1144
  lnodes = [x.split(b"/")[-1] for x in zls]
1081
1145
  bnames = [x for x in ls if x not in lnodes and x != b".hist"]
1082
1146
  vpath = self.ar.url.split("://")[-1].split("/", 1)[-1]
1083
- names = [x.decode("utf-8", "replace") for x in bnames]
1147
+ names = [x.decode("utf-8", WTF8) for x in bnames]
1084
1148
  locs = [vpath + srd + "/" + x for x in names]
1085
1149
  while locs:
1086
1150
  req = locs
@@ -1286,7 +1350,7 @@ class Ctl(object):
1286
1350
  self._check_if_done()
1287
1351
  continue
1288
1352
 
1289
- njoin = (self.ar.sz * 1024 * 1024) // chunksz
1353
+ njoin = self.ar.sz // chunksz
1290
1354
  cs = hs[:]
1291
1355
  while cs:
1292
1356
  fsl = FileSlice(file, cs[:1])
@@ -1338,7 +1402,7 @@ class Ctl(object):
1338
1402
  )
1339
1403
 
1340
1404
  try:
1341
- upload(fsl, stats)
1405
+ upload(fsl, stats, self.ar.szm)
1342
1406
  except Exception as ex:
1343
1407
  t = "upload failed, retrying: %s #%s+%d (%s)\n"
1344
1408
  eprint(t % (file.name, cids[0][:8], len(cids) - 1, ex))
@@ -1427,6 +1491,7 @@ source file/folder selection uses rsync syntax, meaning that:
1427
1491
  ap.add_argument("-j", type=int, metavar="CONNS", default=2, help="parallel connections")
1428
1492
  ap.add_argument("-J", type=int, metavar="CORES", default=hcores, help="num cpu-cores to use for hashing; set 0 or 1 for single-core hashing")
1429
1493
  ap.add_argument("--sz", type=int, metavar="MiB", default=64, help="try to make each POST this big")
1494
+ ap.add_argument("--szm", type=int, metavar="MiB", default=96, help="max size of each POST (default is cloudflare max)")
1430
1495
  ap.add_argument("-nh", action="store_true", help="disable hashing while uploading")
1431
1496
  ap.add_argument("-ns", action="store_true", help="no status panel (for slow consoles and macos)")
1432
1497
  ap.add_argument("--cd", type=float, metavar="SEC", default=5, help="delay before reattempting a failed handshake/upload")
@@ -1454,6 +1519,9 @@ source file/folder selection uses rsync syntax, meaning that:
1454
1519
  if ar.dr:
1455
1520
  ar.ow = True
1456
1521
 
1522
+ ar.sz *= 1024 * 1024
1523
+ ar.szm *= 1024 * 1024
1524
+
1457
1525
  ar.x = "|".join(ar.x or [])
1458
1526
 
1459
1527
  setattr(ar, "wlist", ar.url == "-")
Binary file
Binary file
copyparty/web/ui.css.gz CHANGED
Binary file
copyparty/web/up2k.js.gz CHANGED
Binary file
copyparty/web/util.js.gz CHANGED
Binary file
Binary file
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: copyparty
3
- Version: 1.15.7
3
+ Version: 1.15.8
4
4
  Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
5
5
  Author-email: ed <copyparty@ocv.me>
6
6
  License: MIT
@@ -273,7 +273,7 @@ also see [comparison to similar software](./docs/versus.md)
273
273
  * upload
274
274
  * ☑ basic: plain multipart, ie6 support
275
275
  * ☑ [up2k](#uploading): js, resumable, multithreaded
276
- * **no filesize limit!** ...unless you use Cloudflare, then it's 383.9 GiB
276
+ * **no filesize limit!** even on Cloudflare
277
277
  * ☑ stash: simple PUT filedropper
278
278
  * ☑ filename randomizer
279
279
  * ☑ write-only folders
@@ -708,7 +708,7 @@ up2k has several advantages:
708
708
  * uploads resume if you reboot your browser or pc, just upload the same files again
709
709
  * server detects any corruption; the client reuploads affected chunks
710
710
  * the client doesn't upload anything that already exists on the server
711
- * no filesize limit unless imposed by a proxy, for example Cloudflare, which blocks uploads over 383.9 GiB
711
+ * no filesize limit, even when a proxy limits the request size (for example Cloudflare)
712
712
  * much higher speeds than ftp/scp/tarpipe on some internet connections (mainly american ones) thanks to parallel connections
713
713
  * the last-modified timestamp of the file is preserved
714
714
 
@@ -744,6 +744,8 @@ note that since up2k has to read each file twice, `[🎈] bup` can *theoreticall
744
744
 
745
745
  if you are resuming a massive upload and want to skip hashing the files which already finished, you can enable `turbo` in the `[⚙️] config` tab, but please read the tooltip on that button
746
746
 
747
+ if the server is behind a proxy which imposes a request-size limit, you can configure up2k to sneak below the limit with server-option `--u2sz` (the default is 96 MiB to support Cloudflare)
748
+
747
749
 
748
750
  ### file-search
749
751
 
@@ -1,7 +1,7 @@
1
1
  copyparty/__init__.py,sha256=Chqw7uXX4r_-a2p6-xthrrqVHFI4aZdW45sWU7UvqeE,2597
2
- copyparty/__main__.py,sha256=jPcqdsSJZP6AMoPygb6t8PwxmLXGK3jUeUvt28JcnyE,110130
3
- copyparty/__version__.py,sha256=Z9byW4PqObsayHm5sQR8iTi3mFCP5dkS0YnU-6VoRT4,258
4
- copyparty/authsrv.py,sha256=clsE8whf32_eIRES1r7VvsVkc4OiJCjiBEJM6pYkwwI,98670
2
+ copyparty/__main__.py,sha256=oTzWyuZotTwi-dHpD41mVp_EW2mPSWDMKP8o_nxp9Yk,110180
3
+ copyparty/__version__.py,sha256=V6jLFRwMXDp4buHI89fYcQoINvqpP0XYLPVMn_QHEDg,258
4
+ copyparty/authsrv.py,sha256=-lImrFH6pm3gcI76vZiFfEgrQx3_STYTSS2soYX5y1Y,98711
5
5
  copyparty/broker_mp.py,sha256=jsHUM2BSfRVRyZT869iPCqYEHSqedk6VkwvygZwbEZE,4017
6
6
  copyparty/broker_mpw.py,sha256=PYFgQfssOCfdI6qayW1ZjO1j1-7oez094muhYMbPOz0,3339
7
7
  copyparty/broker_thr.py,sha256=MXrwjusP0z1LPURUhi5jx_TL3jrXhYcDrJPDSKu6EEU,1705
@@ -11,9 +11,9 @@ copyparty/cfg.py,sha256=33nLatBUmzRFKQ4KpoQei3ZY6EqRrlaHpQnvCNFXcHI,10112
11
11
  copyparty/dxml.py,sha256=lZpg-kn-kQsXRtNY1n6fRaS-b7uXzMCyv8ovKnhZcZc,1548
12
12
  copyparty/fsutil.py,sha256=5CshJWO7CflfaRRNOb3JxghUH7W5rmS_HWNmKfx42MM,4538
13
13
  copyparty/ftpd.py,sha256=G_h1urfIikzfCWGXnW9p-rioWdNM_Je6vWYq0-QSbC8,17580
14
- copyparty/httpcli.py,sha256=PIlYJa-1QTzsASD9jRbRJGsn8vAUNSg15qJEigcs9cs,192083
14
+ copyparty/httpcli.py,sha256=xhM8unCDyo7Gj3hmLdhncXUCgKHgb6a300bolO1WF4U,194152
15
15
  copyparty/httpconn.py,sha256=mQSgljh0Q-jyWjF4tQLrHbRKRe9WKl19kGqsGMsJpWo,6880
16
- copyparty/httpsrv.py,sha256=k0xSadmUNSyUAWlZdOMFdsBP1fQK50Ptv7O6O4jFy2Q,17182
16
+ copyparty/httpsrv.py,sha256=d_UiGnQKniBoEV68lNFgnYm-byda7uj56mFf-YC7piI,17223
17
17
  copyparty/ico.py,sha256=eWSxEae4wOCfheHl-m-wchYvFRAR_97kJDb4NGaB-Z8,3561
18
18
  copyparty/mdns.py,sha256=vC078llnL1v0pvL3mnwacuStFHPJUQuxo9Opj-IbHL4,18155
19
19
  copyparty/metrics.py,sha256=aV09nntEmKMIyde8xoPtj1ehDOQVQOHchRF4uMMNzqM,8855
@@ -31,8 +31,8 @@ copyparty/tftpd.py,sha256=jZbf2JpeJmkuQWJErmAPG-dKhtYNvIUHbkAgodSXw9Y,13582
31
31
  copyparty/th_cli.py,sha256=o6FMkerYvAXS455z3DUossVztu_nzFlYSQhs6qN6Jt8,4636
32
32
  copyparty/th_srv.py,sha256=hI9wY1E_9N9Cgqvtr8zADeVqqiLGTiTdAnYAA7WFvJw,29346
33
33
  copyparty/u2idx.py,sha256=JjgqwgJBNj6sTn4PJfuqM3VEHqlmoyGC5bk4_92K2h0,13414
34
- copyparty/up2k.py,sha256=PSVSdG00wC76mi0E7HF1WBFco4lujpSCTTtSzUusPVM,165096
35
- copyparty/util.py,sha256=Vj8F50G0taqNd7c8TQd56IRbRZGFE1r0wnnrymbk7j0,92349
34
+ copyparty/up2k.py,sha256=Zn33f8EkFpAnK_RTDyVNA97CKlS9MS3k5mnnvYh1w1k,165351
35
+ copyparty/util.py,sha256=7_-17F94TsLw644xLT8FfO_fH0DwViD54-grej3f8sY,92379
36
36
  copyparty/bos/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  copyparty/bos/bos.py,sha256=Wb7eWsXJgR5AFlBR9ZOyKrLTwy-Kct9RrGiOu4Jo37Y,1622
38
38
  copyparty/bos/path.py,sha256=yEjCq2ki9CvxA5sCT8pS0keEXwugs0ZeUyUhdBziOCI,777
@@ -54,10 +54,10 @@ copyparty/stolen/ifaddr/__init__.py,sha256=vpREjAyPubr5s1NJi91icXV3q1o4DrKAvHABw
54
54
  copyparty/stolen/ifaddr/_posix.py,sha256=-67NdfGrCktfQPakT2fLbjl2U00QMvyBGkSvrUuTOrU,2626
55
55
  copyparty/stolen/ifaddr/_shared.py,sha256=uNC4SdEIgdSLKvuUzsf1aM-H1Xrc_9mpLoOT43YukGs,6206
56
56
  copyparty/stolen/ifaddr/_win32.py,sha256=EE-QyoBgeB7lYQ6z62VjXNaRozaYfCkaJBHGNA8QtZM,4026
57
- copyparty/web/baguettebox.js.gz,sha256=4dS8-r4si84ca71l98672ahnRI86Aq95MU-bc5knykk,7962
57
+ copyparty/web/baguettebox.js.gz,sha256=YIaxFDsubJfGIdzzxA-cL6GwJVmpWZyaPhW9hHcOIIw,7964
58
58
  copyparty/web/browser.css.gz,sha256=4bAS9Xkl2fflhaxRSRSVoYQcpXsg1mCWxsYjId7phbU,11610
59
59
  copyparty/web/browser.html,sha256=ISpfvWEawufJCYZIqvuXiyUgiXgjmOTtScz4zrEaypI,4870
60
- copyparty/web/browser.js.gz,sha256=0WhsCM1Kr0PFryxz7hF31apLgvsJac4jWy8_ePfp_40,84989
60
+ copyparty/web/browser.js.gz,sha256=7rubbEoqFlNn7FPF0mz3L2LQeWuPzw95MYpIaZmcczE,84985
61
61
  copyparty/web/browser2.html,sha256=NRUZ08GH-e2YcGXcoz0UjYg6JIVF42u4IMX4HHwWTmg,1587
62
62
  copyparty/web/cf.html,sha256=lJThtNFNAQT1ClCHHlivAkDGE0LutedwopXD62Z8Nys,589
63
63
  copyparty/web/dbg-audio.js.gz,sha256=Ma-KZtK8LnmiwNvNKFKXMPYl_Nn_3U7GsJ6-DRWC2HE,688
@@ -79,13 +79,13 @@ copyparty/web/splash.html,sha256=pUbsso_W3Q7bso8fy8qxh-fHDrrLm39mBBTIlTeH63w,523
79
79
  copyparty/web/splash.js.gz,sha256=Xoccku-2vE3tABo-88q3Cl4koHs_AE76T8QvMy4u6T8,2540
80
80
  copyparty/web/svcs.html,sha256=P5YZimYLeQMT0uz6u3clQSNZRc5Zs0Ok-ffcbcGSYuc,11762
81
81
  copyparty/web/svcs.js.gz,sha256=k81ZvZ3I-f4fMHKrNGGOgOlvXnCBz0mVjD-8mieoWCA,520
82
- copyparty/web/ui.css.gz,sha256=dTAXEPgWB83CkYXlP4hWq18BrLFzRlYwxoCoM5rp-Yw,2786
83
- copyparty/web/up2k.js.gz,sha256=t6mUaIYN8lrEJoWO_-2MZ9nIuEplimQgCqdWSsHnuGA,22811
84
- copyparty/web/util.js.gz,sha256=uh_NAVPiMcOCTy9oxUiDKmKh1GokTKBPe3FYNMMUYlM,14781
85
- copyparty/web/w.hash.js.gz,sha256=7wP9EZQNXQxwZnCCFUVsi_-6TM9PLZJeZ9krutXRRj8,1060
82
+ copyparty/web/ui.css.gz,sha256=wloSacrHgP722hy4XiOvVY2GI9-V4zvfvzu84LLWS_o,2779
83
+ copyparty/web/up2k.js.gz,sha256=iMaZ2joij8ndkA2iFCTri6MnYRxXzQbRu9uwAnxZBj8,23096
84
+ copyparty/web/util.js.gz,sha256=NvjPYhIa0-C_NhUyW-Ra-XinUCRjj8G3pYq1zJHYWEk,14805
85
+ copyparty/web/w.hash.js.gz,sha256=5weFAxkcfHhu90tUKnVnTu04U_PQjhzUVRfvir199I0,1093
86
86
  copyparty/web/a/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
87
87
  copyparty/web/a/partyfuse.py,sha256=fa9bBYNJHvtWpNVjQyyFzx6JOK7MJL1u0zj80PBYQKs,27960
88
- copyparty/web/a/u2c.py,sha256=TkLmQL_3lwus0HI-m4cuP8E9qanfB8U9xGvW7zEJ8ho,47576
88
+ copyparty/web/a/u2c.py,sha256=QA0t_k422uLRh8Kt2eQIpcGYaaUZA1CGz_S-z23Yk-4,49343
89
89
  copyparty/web/a/webdav-cfg.bat,sha256=Y4NoGZlksAIg4cBMb7KdJrpKC6Nx97onaTl6yMjaimk,1449
90
90
  copyparty/web/dd/2.png,sha256=gJ14XFPzaw95L6z92fSq9eMPikSQyu-03P1lgiGe0_I,258
91
91
  copyparty/web/dd/3.png,sha256=4lho8Koz5tV7jJ4ODo6GMTScZfkqsT05yp48EDFIlyg,252
@@ -106,9 +106,9 @@ copyparty/web/deps/prismd.css.gz,sha256=ObUlksQVr-OuYlTz-I4B23TeBg2QDVVGRnWBz8cV
106
106
  copyparty/web/deps/scp.woff2,sha256=w99BDU5i8MukkMEL-iW0YO9H4vFFZSPWxbkH70ytaAg,8612
107
107
  copyparty/web/deps/sha512.ac.js.gz,sha256=lFZaCLumgWxrvEuDr4bqdKHsqjX82AbVAb7_F45Yk88,7033
108
108
  copyparty/web/deps/sha512.hw.js.gz,sha256=vqoXeracj-99Z5MfY3jK2N4WiSzYQdfjy0RnUlQDhSU,8110
109
- copyparty-1.15.7.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
110
- copyparty-1.15.7.dist-info/METADATA,sha256=DXlK1tdi4bTueoZXTbr33g_wqCpjko4sTPWpTeXXfGs,138774
111
- copyparty-1.15.7.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
112
- copyparty-1.15.7.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
113
- copyparty-1.15.7.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
114
- copyparty-1.15.7.dist-info/RECORD,,
109
+ copyparty-1.15.8.dist-info/LICENSE,sha256=gOr4h33pCsBEg9uIy9AYmb7qlocL4V9t2uPJS5wllr0,1072
110
+ copyparty-1.15.8.dist-info/METADATA,sha256=R5tW0crtzFZ4vKzGny4gQ6-IOERgBgj7LZtP14Huung,138915
111
+ copyparty-1.15.8.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
112
+ copyparty-1.15.8.dist-info/entry_points.txt,sha256=4zw6a3rqASywQomiYLObjjlxybaI65LYYOTJwgKz7b0,128
113
+ copyparty-1.15.8.dist-info/top_level.txt,sha256=LnYUPsDyk-8kFgM6YJLG4h820DQekn81cObKSu9g-sI,10
114
+ copyparty-1.15.8.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.1.0)
2
+ Generator: setuptools (75.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5