copyparty 1.12.2__py3-none-any.whl → 1.13.1__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/metrics.py CHANGED
@@ -179,7 +179,7 @@ class Metrics(object):
179
179
  tnbytes = 0
180
180
  tnfiles = 0
181
181
  for vpath, vol in allvols:
182
- cur = idx.get_cur(vol.realpath)
182
+ cur = idx.get_cur(vol)
183
183
  if not cur:
184
184
  continue
185
185
 
copyparty/svchub.py CHANGED
@@ -520,7 +520,7 @@ class SvcHub(object):
520
520
  al.exp_md = odfusion(exp, al.exp_md.replace(" ", ","))
521
521
  al.exp_lg = odfusion(exp, al.exp_lg.replace(" ", ","))
522
522
 
523
- for k in ["no_hash", "no_idx"]:
523
+ for k in ["no_hash", "no_idx", "og_ua"]:
524
524
  ptn = getattr(self.args, k)
525
525
  if ptn:
526
526
  setattr(self.args, k, re.compile(ptn))
@@ -551,6 +551,10 @@ class SvcHub(object):
551
551
  except:
552
552
  raise Exception("invalid --mv-retry [%s]" % (self.args.mv_retry,))
553
553
 
554
+ al.tcolor = al.tcolor.lstrip("#")
555
+ if len(al.tcolor) == 3: # fc5 => ffcc55
556
+ al.tcolor = "".join([x * 2 for x in al.tcolor])
557
+
554
558
  return True
555
559
 
556
560
  def _ipa2re(self, txt) :
copyparty/tcpsrv.py CHANGED
@@ -460,6 +460,12 @@ class TcpSrv(object):
460
460
  sys.stderr.flush()
461
461
 
462
462
  def _qr(self, t1 , t2 ) :
463
+ t2c = {zs: zli for zs, zli in t2.items() if zs in ("127.0.0.1", "::1")}
464
+ t2b = {zs: zli for zs, zli in t2.items() if ":" in zs and zs not in t2c}
465
+ t2 = {zs: zli for zs, zli in t2.items() if zs not in t2b and zs not in t2c}
466
+ t2.update(t2b) # first ipv4, then ipv6...
467
+ t2.update(t2c) # ...and finally localhost
468
+
463
469
  ip = None
464
470
  ips = list(t1) + list(t2)
465
471
  qri = self.args.qri
copyparty/u2idx.py CHANGED
@@ -59,6 +59,17 @@ class U2idx(object):
59
59
  def log(self, msg , c = 0) :
60
60
  self.log_func("u2idx", msg, c)
61
61
 
62
+ def shutdown(self) :
63
+ for cur in self.cur.values():
64
+ db = cur.connection
65
+ try:
66
+ db.interrupt()
67
+ except:
68
+ pass
69
+
70
+ cur.close()
71
+ db.close()
72
+
62
73
  def fsearch(
63
74
  self, uname , vols , body
64
75
  ) :
@@ -78,14 +89,18 @@ class U2idx(object):
78
89
  except:
79
90
  raise Pebkac(500, min_ex())
80
91
 
81
- def get_cur(self, ptop ) :
92
+ def get_cur(self, vn ) :
82
93
  if not HAVE_SQLITE3:
83
94
  return None
84
95
 
85
- cur = self.cur.get(ptop)
96
+ cur = self.cur.get(vn.realpath)
86
97
  if cur:
87
98
  return cur
88
99
 
100
+ if "e2d" not in vn.flags:
101
+ return None
102
+
103
+ ptop = vn.realpath
89
104
  histpath = self.asrv.vfs.histtab.get(ptop)
90
105
  if not histpath:
91
106
  self.log("no histpath for [{}]".format(ptop))
@@ -314,7 +329,7 @@ class U2idx(object):
314
329
  ptop = vol.realpath
315
330
  flags = vol.flags
316
331
 
317
- cur = self.get_cur(ptop)
332
+ cur = self.get_cur(vol)
318
333
  if not cur:
319
334
  continue
320
335
 
copyparty/up2k.py CHANGED
@@ -136,6 +136,7 @@ class Up2k(object):
136
136
  self.need_rescan = set()
137
137
  self.db_act = 0.0
138
138
 
139
+ self.reg_mutex = threading.Lock()
139
140
  self.registry = {}
140
141
  self.flags = {}
141
142
  self.droppable = {}
@@ -143,7 +144,7 @@ class Up2k(object):
143
144
  self.volsize = {}
144
145
  self.volstate = {}
145
146
  self.vol_act = {}
146
- self.busy_aps = set()
147
+ self.busy_aps = {}
147
148
  self.dupesched = {}
148
149
  self.snap_prev = {}
149
150
 
@@ -182,7 +183,7 @@ class Up2k(object):
182
183
  t = "could not initialize sqlite3, will use in-memory registry only"
183
184
  self.log(t, 3)
184
185
 
185
- self.fstab = Fstab(self.log_func)
186
+ self.fstab = Fstab(self.log_func, self.args)
186
187
  self.gen_fk = self._gen_fk if self.args.log_fk else gen_filekey
187
188
 
188
189
  if self.args.hash_mt < 2:
@@ -200,11 +201,15 @@ class Up2k(object):
200
201
  Daemon(self.deferred_init, "up2k-deferred-init")
201
202
 
202
203
  def reload(self, rescan_all_vols ) :
203
- """mutex me"""
204
+ """mutex(main) me"""
204
205
  self.log("reload #{} scheduled".format(self.gid + 1))
205
206
  all_vols = self.asrv.vfs.all_vols
206
207
 
207
- scan_vols = [k for k, v in all_vols.items() if v.realpath not in self.registry]
208
+ with self.reg_mutex:
209
+ scan_vols = [
210
+ k for k, v in all_vols.items() if v.realpath not in self.registry
211
+ ]
212
+
208
213
  if rescan_all_vols:
209
214
  scan_vols = list(all_vols.keys())
210
215
 
@@ -217,7 +222,7 @@ class Up2k(object):
217
222
  if self.stop:
218
223
  # up-mt consistency not guaranteed if init is interrupted;
219
224
  # drop caches for a full scan on next boot
220
- with self.mutex:
225
+ with self.mutex, self.reg_mutex:
221
226
  self._drop_caches()
222
227
 
223
228
  if self.pp:
@@ -283,10 +288,27 @@ class Up2k(object):
283
288
  min(1000 * 24 * 60 * 60 - 1, time.time() - self.db_act)
284
289
  ),
285
290
  }
286
- return json.dumps(ret, indent=4)
291
+ return json.dumps(ret, separators=(",\n", ": "))
292
+
293
+ def find_job_by_ap(self, ptop , ap ) :
294
+ try:
295
+ if ANYWIN:
296
+ ap = ap.replace("\\", "/")
297
+
298
+ vp = ap[len(ptop) :].strip("/")
299
+ dn, fn = vsplit(vp)
300
+ with self.reg_mutex:
301
+ tab2 = self.registry[ptop]
302
+ for job in tab2.values():
303
+ if job["prel"] == dn and job["name"] == fn:
304
+ return json.dumps(job, separators=(",\n", ": "))
305
+ except:
306
+ pass
307
+
308
+ return "{}"
287
309
 
288
310
  def get_unfinished_by_user(self, uname, ip) :
289
- if PY2 or not self.mutex.acquire(timeout=2):
311
+ if PY2 or not self.reg_mutex.acquire(timeout=2):
290
312
  return '[{"timeout":1}]'
291
313
 
292
314
  ret = []
@@ -315,17 +337,25 @@ class Up2k(object):
315
337
  )
316
338
  ret.append(zt5)
317
339
  finally:
318
- self.mutex.release()
340
+ self.reg_mutex.release()
341
+
342
+ if ANYWIN:
343
+ ret = [(x[0], x[1].replace("\\", "/"), x[2], x[3], x[4]) for x in ret]
319
344
 
320
345
  ret.sort(reverse=True)
321
346
  ret2 = [
322
- {"at": at, "vp": "/" + vp, "pd": 100 - ((nn * 100) // (nh or 1)), "sz": sz}
347
+ {
348
+ "at": at,
349
+ "vp": "/" + quotep(vp),
350
+ "pd": 100 - ((nn * 100) // (nh or 1)),
351
+ "sz": sz,
352
+ }
323
353
  for (at, vp, sz, nn, nh) in ret
324
354
  ]
325
- return json.dumps(ret2, indent=0)
355
+ return json.dumps(ret2, separators=(",\n", ": "))
326
356
 
327
357
  def get_unfinished(self) :
328
- if PY2 or not self.mutex.acquire(timeout=0.5):
358
+ if PY2 or not self.reg_mutex.acquire(timeout=0.5):
329
359
  return ""
330
360
 
331
361
  ret = {}
@@ -347,17 +377,17 @@ class Up2k(object):
347
377
 
348
378
  ret[ptop] = (nbytes, nfiles)
349
379
  finally:
350
- self.mutex.release()
380
+ self.reg_mutex.release()
351
381
 
352
- return json.dumps(ret, indent=4)
382
+ return json.dumps(ret, separators=(",\n", ": "))
353
383
 
354
384
  def get_volsize(self, ptop ) :
355
- with self.mutex:
385
+ with self.reg_mutex:
356
386
  return self._get_volsize(ptop)
357
387
 
358
388
  def get_volsizes(self, ptops ) :
359
389
  ret = []
360
- with self.mutex:
390
+ with self.reg_mutex:
361
391
  for ptop in ptops:
362
392
  ret.append(self._get_volsize(ptop))
363
393
 
@@ -385,7 +415,7 @@ class Up2k(object):
385
415
  def _rescan(
386
416
  self, all_vols , scan_vols , wait , fscan
387
417
  ) :
388
- """mutex me"""
418
+ """mutex(main) me"""
389
419
  if not wait and self.pp:
390
420
  return "cannot initiate; scan is already in progress"
391
421
 
@@ -667,7 +697,7 @@ class Up2k(object):
667
697
  self.log(msg, c=3)
668
698
 
669
699
  live_vols = []
670
- with self.mutex:
700
+ with self.mutex, self.reg_mutex:
671
701
  # only need to protect register_vpath but all in one go feels right
672
702
  for vol in vols:
673
703
  try:
@@ -709,7 +739,7 @@ class Up2k(object):
709
739
 
710
740
  if self.args.re_dhash or [zv for zv in vols if "e2tsr" in zv.flags]:
711
741
  self.args.re_dhash = False
712
- with self.mutex:
742
+ with self.mutex, self.reg_mutex:
713
743
  self._drop_caches()
714
744
 
715
745
  for vol in vols:
@@ -786,7 +816,9 @@ class Up2k(object):
786
816
  self.volstate[vol.vpath] = "online (mtp soon)"
787
817
 
788
818
  for vol in need_vac:
789
- reg = self.register_vpath(vol.realpath, vol.flags)
819
+ with self.mutex, self.reg_mutex:
820
+ reg = self.register_vpath(vol.realpath, vol.flags)
821
+
790
822
  assert reg
791
823
  cur, _ = reg
792
824
  with self.mutex:
@@ -800,7 +832,9 @@ class Up2k(object):
800
832
  if vol.flags["dbd"] == "acid":
801
833
  continue
802
834
 
803
- reg = self.register_vpath(vol.realpath, vol.flags)
835
+ with self.mutex, self.reg_mutex:
836
+ reg = self.register_vpath(vol.realpath, vol.flags)
837
+
804
838
  try:
805
839
  assert reg
806
840
  cur, db_path = reg
@@ -847,6 +881,7 @@ class Up2k(object):
847
881
  def register_vpath(
848
882
  self, ptop , flags
849
883
  ) :
884
+ """mutex(main,reg) me"""
850
885
  histpath = self.asrv.vfs.histtab.get(ptop)
851
886
  if not histpath:
852
887
  self.log("no histpath for [{}]".format(ptop))
@@ -1030,7 +1065,9 @@ class Up2k(object):
1030
1065
  dev = cst.st_dev if vol.flags.get("xdev") else 0
1031
1066
 
1032
1067
  with self.mutex:
1033
- reg = self.register_vpath(top, vol.flags)
1068
+ with self.reg_mutex:
1069
+ reg = self.register_vpath(top, vol.flags)
1070
+
1034
1071
  assert reg and self.pp
1035
1072
  cur, db_path = reg
1036
1073
 
@@ -1627,7 +1664,7 @@ class Up2k(object):
1627
1664
 
1628
1665
  def _build_tags_index(self, vol ) :
1629
1666
  ptop = vol.realpath
1630
- with self.mutex:
1667
+ with self.mutex, self.reg_mutex:
1631
1668
  reg = self.register_vpath(ptop, vol.flags)
1632
1669
 
1633
1670
  assert reg and self.pp
@@ -1648,6 +1685,7 @@ class Up2k(object):
1648
1685
  return ret
1649
1686
 
1650
1687
  def _drop_caches(self) :
1688
+ """mutex(main,reg) me"""
1651
1689
  self.log("dropping caches for a full filesystem scan")
1652
1690
  for vol in self.asrv.vfs.all_vols.values():
1653
1691
  reg = self.register_vpath(vol.realpath, vol.flags)
@@ -1823,7 +1861,7 @@ class Up2k(object):
1823
1861
  params ,
1824
1862
  flt ,
1825
1863
  ) :
1826
- """mutex me"""
1864
+ """mutex(main) me"""
1827
1865
  n = 0
1828
1866
  c2 = cur.connection.cursor()
1829
1867
  tf = tempfile.SpooledTemporaryFile(1024 * 1024 * 8, "w+b", prefix="cpp-tq-")
@@ -2157,7 +2195,7 @@ class Up2k(object):
2157
2195
  ip ,
2158
2196
  at ,
2159
2197
  ) :
2160
- """will mutex"""
2198
+ """will mutex(main)"""
2161
2199
  assert self.mtag
2162
2200
 
2163
2201
  try:
@@ -2189,7 +2227,7 @@ class Up2k(object):
2189
2227
  abspath ,
2190
2228
  tags ,
2191
2229
  ) :
2192
- """mutex me"""
2230
+ """mutex(main) me"""
2193
2231
  assert self.mtag
2194
2232
 
2195
2233
  if not bos.path.isfile(abspath):
@@ -2474,28 +2512,36 @@ class Up2k(object):
2474
2512
 
2475
2513
  cur.connection.commit()
2476
2514
 
2477
- def _job_volchk(self, cj ) :
2478
- if not self.register_vpath(cj["ptop"], cj["vcfg"]):
2479
- if cj["ptop"] not in self.registry:
2480
- raise Pebkac(410, "location unavailable")
2481
-
2482
- def handle_json(self, cj , busy_aps ) :
2515
+ def handle_json(
2516
+ self, cj , busy_aps
2517
+ ) :
2518
+ # busy_aps is u2fh (always undefined if -j0) so this is safe
2483
2519
  self.busy_aps = busy_aps
2520
+ got_lock = False
2484
2521
  try:
2485
2522
  # bit expensive; 3.9=10x 3.11=2x
2486
2523
  if self.mutex.acquire(timeout=10):
2487
- self._job_volchk(cj)
2488
- self.mutex.release()
2524
+ got_lock = True
2525
+ with self.reg_mutex:
2526
+ return self._handle_json(cj)
2489
2527
  else:
2490
2528
  t = "cannot receive uploads right now;\nserver busy with {}.\nPlease wait; the client will retry..."
2491
2529
  raise Pebkac(503, t.format(self.blocked or "[unknown]"))
2492
2530
  except TypeError:
2493
2531
  if not PY2:
2494
2532
  raise
2495
- with self.mutex:
2496
- self._job_volchk(cj)
2533
+ with self.mutex, self.reg_mutex:
2534
+ return self._handle_json(cj)
2535
+ finally:
2536
+ if got_lock:
2537
+ self.mutex.release()
2497
2538
 
2539
+ def _handle_json(self, cj ) :
2498
2540
  ptop = cj["ptop"]
2541
+ if not self.register_vpath(ptop, cj["vcfg"]):
2542
+ if ptop not in self.registry:
2543
+ raise Pebkac(410, "location unavailable")
2544
+
2499
2545
  cj["name"] = sanitize_fn(cj["name"], "", [".prologue.html", ".epilogue.html"])
2500
2546
  cj["poke"] = now = self.db_act = self.vol_act[ptop] = time.time()
2501
2547
  wark = self._get_wark(cj)
@@ -2510,7 +2556,7 @@ class Up2k(object):
2510
2556
  # refuse out-of-order / multithreaded uploading if sprs False
2511
2557
  sprs = self.fstab.get(pdir) != "ng"
2512
2558
 
2513
- with self.mutex:
2559
+ if True:
2514
2560
  jcur = self.cur.get(ptop)
2515
2561
  reg = self.registry[ptop]
2516
2562
  vfs = self.asrv.vfs.all_vols[cj["vtop"]]
@@ -2948,7 +2994,7 @@ class Up2k(object):
2948
2994
  def handle_chunk(
2949
2995
  self, ptop , wark , chash
2950
2996
  ) :
2951
- with self.mutex:
2997
+ with self.mutex, self.reg_mutex:
2952
2998
  self.db_act = self.vol_act[ptop] = time.time()
2953
2999
  job = self.registry[ptop].get(wark)
2954
3000
  if not job:
@@ -2991,7 +3037,7 @@ class Up2k(object):
2991
3037
  return chunksize, ofs, path, job["lmod"], job["sprs"]
2992
3038
 
2993
3039
  def release_chunk(self, ptop , wark , chash ) :
2994
- with self.mutex:
3040
+ with self.reg_mutex:
2995
3041
  job = self.registry[ptop].get(wark)
2996
3042
  if job:
2997
3043
  job["busy"].pop(chash, None)
@@ -2999,7 +3045,7 @@ class Up2k(object):
2999
3045
  return True
3000
3046
 
3001
3047
  def confirm_chunk(self, ptop , wark , chash ) :
3002
- with self.mutex:
3048
+ with self.mutex, self.reg_mutex:
3003
3049
  self.db_act = self.vol_act[ptop] = time.time()
3004
3050
  try:
3005
3051
  job = self.registry[ptop][wark]
@@ -3022,16 +3068,16 @@ class Up2k(object):
3022
3068
 
3023
3069
  if self.args.nw:
3024
3070
  self.regdrop(ptop, wark)
3025
- return ret, dst
3026
3071
 
3027
3072
  return ret, dst
3028
3073
 
3029
3074
  def finish_upload(self, ptop , wark , busy_aps ) :
3030
3075
  self.busy_aps = busy_aps
3031
- with self.mutex:
3076
+ with self.mutex, self.reg_mutex:
3032
3077
  self._finish_upload(ptop, wark)
3033
3078
 
3034
3079
  def _finish_upload(self, ptop , wark ) :
3080
+ """mutex(main,reg) me"""
3035
3081
  try:
3036
3082
  job = self.registry[ptop][wark]
3037
3083
  pdir = djoin(job["ptop"], job["prel"])
@@ -3104,6 +3150,7 @@ class Up2k(object):
3104
3150
  cur.connection.commit()
3105
3151
 
3106
3152
  def regdrop(self, ptop , wark ) :
3153
+ """mutex(main,reg) me"""
3107
3154
  olds = self.droppable[ptop]
3108
3155
  if wark:
3109
3156
  olds.append(wark)
@@ -3198,16 +3245,23 @@ class Up2k(object):
3198
3245
  at ,
3199
3246
  skip_xau = False,
3200
3247
  ) :
3248
+ """mutex(main) me"""
3201
3249
  self.db_rm(db, rd, fn, sz)
3202
3250
 
3251
+ if not ip:
3252
+ db_ip = ""
3253
+ else:
3254
+ # plugins may expect this to look like an actual IP
3255
+ db_ip = "1.1.1.1" if self.args.no_db_ip else ip
3256
+
3203
3257
  sql = "insert into up values (?,?,?,?,?,?,?)"
3204
- v = (wark, int(ts), sz, rd, fn, ip or "", int(at or 0))
3258
+ v = (wark, int(ts), sz, rd, fn, db_ip, int(at or 0))
3205
3259
  try:
3206
3260
  db.execute(sql, v)
3207
3261
  except:
3208
3262
  assert self.mem_cur
3209
3263
  rd, fn = s3enc(self.mem_cur, rd, fn)
3210
- v = (wark, int(ts), sz, rd, fn, ip or "", int(at or 0))
3264
+ v = (wark, int(ts), sz, rd, fn, db_ip, int(at or 0))
3211
3265
  db.execute(sql, v)
3212
3266
 
3213
3267
  self.volsize[db] += sz
@@ -3311,7 +3365,7 @@ class Up2k(object):
3311
3365
  vn, rem = self.asrv.vfs.get(vpath, uname, *permsets[0])
3312
3366
  vn, rem = vn.get_dbv(rem)
3313
3367
  ptop = vn.realpath
3314
- with self.mutex:
3368
+ with self.mutex, self.reg_mutex:
3315
3369
  abrt_cfg = self.flags.get(ptop, {}).get("u2abort", 1)
3316
3370
  addr = (ip or "\n") if abrt_cfg in (1, 2) else ""
3317
3371
  user = (uname or "\n") if abrt_cfg in (1, 3) else ""
@@ -3319,7 +3373,10 @@ class Up2k(object):
3319
3373
  for wark, job in reg.items():
3320
3374
  if (user and user != job["user"]) or (addr and addr != job["addr"]):
3321
3375
  continue
3322
- if djoin(job["prel"], job["name"]) == rem:
3376
+ jrem = djoin(job["prel"], job["name"])
3377
+ if ANYWIN:
3378
+ jrem = jrem.replace("\\", "/")
3379
+ if jrem == rem:
3323
3380
  if job["ptop"] != ptop:
3324
3381
  t = "job.ptop [%s] != vol.ptop [%s] ??"
3325
3382
  raise Exception(t % (job["ptop"] != ptop))
@@ -3415,7 +3472,7 @@ class Up2k(object):
3415
3472
  continue
3416
3473
 
3417
3474
  n_files += 1
3418
- with self.mutex:
3475
+ with self.mutex, self.reg_mutex:
3419
3476
  cur = None
3420
3477
  try:
3421
3478
  ptop = dbv.realpath
@@ -3533,6 +3590,7 @@ class Up2k(object):
3533
3590
  def _mv_file(
3534
3591
  self, uname , svp , dvp , curs
3535
3592
  ) :
3593
+ """mutex(main) me; will mutex(reg)"""
3536
3594
  svn, srem = self.asrv.vfs.get(svp, uname, True, False, True)
3537
3595
  svn, srem = svn.get_dbv(srem)
3538
3596
 
@@ -3613,7 +3671,9 @@ class Up2k(object):
3613
3671
  if c2 and c2 != c1:
3614
3672
  self._copy_tags(c1, c2, w)
3615
3673
 
3616
- has_dupes = self._forget_file(svn.realpath, srem, c1, w, is_xvol, fsize)
3674
+ with self.reg_mutex:
3675
+ has_dupes = self._forget_file(svn.realpath, srem, c1, w, is_xvol, fsize)
3676
+
3617
3677
  if not is_xvol:
3618
3678
  has_dupes = self._relink(w, svn.realpath, srem, dabs)
3619
3679
 
@@ -3743,7 +3803,10 @@ class Up2k(object):
3743
3803
  drop_tags ,
3744
3804
  sz ,
3745
3805
  ) :
3746
- """forgets file in db, fixes symlinks, does not delete"""
3806
+ """
3807
+ mutex(main,reg) me
3808
+ forgets file in db, fixes symlinks, does not delete
3809
+ """
3747
3810
  srd, sfn = vsplit(vrem)
3748
3811
  has_dupes = False
3749
3812
  self.log("forgetting {}".format(vrem))
@@ -4068,7 +4131,7 @@ class Up2k(object):
4068
4131
  self.do_snapshot()
4069
4132
 
4070
4133
  def do_snapshot(self) :
4071
- with self.mutex:
4134
+ with self.mutex, self.reg_mutex:
4072
4135
  for k, reg in self.registry.items():
4073
4136
  self._snap_reg(k, reg)
4074
4137
 
@@ -4136,7 +4199,7 @@ class Up2k(object):
4136
4199
 
4137
4200
  path2 = "{}.{}".format(path, os.getpid())
4138
4201
  body = {"droppable": self.droppable[ptop], "registry": reg}
4139
- j = json.dumps(body, indent=2, sort_keys=True).encode("utf-8")
4202
+ j = json.dumps(body, sort_keys=True, separators=(",\n", ": ")).encode("utf-8")
4140
4203
  with gzip.GzipFile(path2, "wb") as f:
4141
4204
  f.write(j)
4142
4205
 
@@ -4209,7 +4272,7 @@ class Up2k(object):
4209
4272
  raise Exception("invalid hash task")
4210
4273
 
4211
4274
  try:
4212
- if not self._hash_t(task):
4275
+ if not self._hash_t(task) and self.stop:
4213
4276
  return
4214
4277
  except Exception as ex:
4215
4278
  self.log("failed to hash %s: %s" % (task, ex), 1)
@@ -4219,7 +4282,7 @@ class Up2k(object):
4219
4282
  ) :
4220
4283
  ptop, vtop, flags, rd, fn, ip, at, usr, skip_xau = task
4221
4284
  # self.log("hashq {} pop {}/{}/{}".format(self.n_hashq, ptop, rd, fn))
4222
- with self.mutex:
4285
+ with self.mutex, self.reg_mutex:
4223
4286
  if not self.register_vpath(ptop, flags):
4224
4287
  return True
4225
4288
 
@@ -4237,7 +4300,7 @@ class Up2k(object):
4237
4300
 
4238
4301
  wark = up2k_wark_from_hashlist(self.salt, inf.st_size, hashes)
4239
4302
 
4240
- with self.mutex:
4303
+ with self.mutex, self.reg_mutex:
4241
4304
  self.idx_wark(
4242
4305
  self.flags[ptop],
4243
4306
  rd,
@@ -4313,6 +4376,18 @@ class Up2k(object):
4313
4376
  for x in list(self.spools):
4314
4377
  self._unspool(x)
4315
4378
 
4379
+ for cur in self.cur.values():
4380
+ db = cur.connection
4381
+ try:
4382
+ db.interrupt()
4383
+ except:
4384
+ pass
4385
+
4386
+ cur.close()
4387
+ db.close()
4388
+
4389
+ self.registry = {}
4390
+
4316
4391
 
4317
4392
  def up2k_chunksize(filesize ) :
4318
4393
  chunksize = 1024 * 1024
copyparty/util.py CHANGED
@@ -35,6 +35,9 @@ from .__init__ import ANYWIN, EXE, MACOS, PY2, TYPE_CHECKING, VT100, WINDOWS
35
35
  from .__version__ import S_BUILD_DT, S_VERSION
36
36
  from .stolen import surrogateescape
37
37
 
38
+ ub64dec = base64.urlsafe_b64decode
39
+ ub64enc = base64.urlsafe_b64encode
40
+
38
41
  try:
39
42
  from datetime import datetime, timezone
40
43
 
@@ -738,15 +741,46 @@ class CachedSet(object):
738
741
  self.oldest = now
739
742
 
740
743
 
744
+ class CachedDict(object):
745
+ def __init__(self, maxage ) :
746
+ self.c = {}
747
+ self.maxage = maxage
748
+ self.oldest = 0.0
749
+
750
+ def set(self, k , v ) :
751
+ now = time.time()
752
+ self.c[k] = (now, v)
753
+ if now - self.oldest < self.maxage:
754
+ return
755
+
756
+ c = self.c = {k: v for k, v in self.c.items() if now - v[0] < self.maxage}
757
+ try:
758
+ self.oldest = min([x[0] for x in c.values()])
759
+ except:
760
+ self.oldest = now
761
+
762
+ def get(self, k ) :
763
+ try:
764
+ ts, ret = self.c[k]
765
+ now = time.time()
766
+ if now - ts > self.maxage:
767
+ del self.c[k]
768
+ return None
769
+ return ret
770
+ except:
771
+ return None
772
+
773
+
741
774
  class FHC(object):
742
775
  class CE(object):
743
776
  def __init__(self, fh ) :
744
777
  self.ts = 0
745
778
  self.fhs = [fh]
779
+ self.all_fhs = set([fh])
746
780
 
747
781
  def __init__(self) :
748
782
  self.cache = {}
749
- self.aps = set()
783
+ self.aps = {}
750
784
 
751
785
  def close(self, path ) :
752
786
  try:
@@ -758,7 +792,7 @@ class FHC(object):
758
792
  fh.close()
759
793
 
760
794
  del self.cache[path]
761
- self.aps.remove(path)
795
+ del self.aps[path]
762
796
 
763
797
  def clean(self) :
764
798
  if not self.cache:
@@ -779,9 +813,12 @@ class FHC(object):
779
813
  return self.cache[path].fhs.pop()
780
814
 
781
815
  def put(self, path , fh ) :
782
- self.aps.add(path)
816
+ if path not in self.aps:
817
+ self.aps[path] = 0
818
+
783
819
  try:
784
820
  ce = self.cache[path]
821
+ ce.all_fhs.add(fh)
785
822
  ce.fhs.append(fh)
786
823
  except:
787
824
  ce = self.CE(fh)
@@ -1976,6 +2013,7 @@ def vsplit(vpath ) :
1976
2013
  return vpath.rsplit("/", 1) # type: ignore
1977
2014
 
1978
2015
 
2016
+ # vpath-join
1979
2017
  def vjoin(rd , fn ) :
1980
2018
  if rd and fn:
1981
2019
  return rd + "/" + fn
@@ -1983,6 +2021,14 @@ def vjoin(rd , fn ) :
1983
2021
  return rd or fn
1984
2022
 
1985
2023
 
2024
+ # url-join
2025
+ def ujoin(rd , fn ) :
2026
+ if rd and fn:
2027
+ return rd.rstrip("/") + "/" + fn.lstrip("/")
2028
+ else:
2029
+ return rd or fn
2030
+
2031
+
1986
2032
  def _w8dec2(txt ) :
1987
2033
  """decodes filesystem-bytes to wtf8"""
1988
2034
  return surrogateescape.decodefilename(txt)