copyparty 1.16.18__py3-none-any.whl → 1.16.20__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/svchub.py CHANGED
@@ -58,6 +58,7 @@ from .util import (
58
58
  expat_ver,
59
59
  gzip,
60
60
  load_ipu,
61
+ lock_file,
61
62
  min_ex,
62
63
  mp,
63
64
  odfusion,
@@ -67,6 +68,9 @@ from .util import (
67
68
  ub64enc,
68
69
  )
69
70
 
71
+ if HAVE_SQLITE3:
72
+ import sqlite3
73
+
70
74
  if TYPE_CHECKING:
71
75
  try:
72
76
  from .mdns import MDNS
@@ -78,6 +82,10 @@ if PY2:
78
82
  range = xrange # type: ignore
79
83
 
80
84
 
85
+ VER_SESSION_DB = 1
86
+ VER_SHARES_DB = 2
87
+
88
+
81
89
  class SvcHub(object):
82
90
  """
83
91
  Hosts all services which cannot be parallelized due to reliance on monolithic resources.
@@ -180,8 +188,14 @@ class SvcHub(object):
180
188
 
181
189
  if not args.use_fpool and args.j != 1:
182
190
  args.no_fpool = True
183
- t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems"
184
- self.log("root", t.format(args.j))
191
+ t = "multithreading enabled with -j {}, so disabling fpool -- this can reduce upload performance on some filesystems, and make some antivirus-softwares "
192
+ c = 0
193
+ if ANYWIN:
194
+ t += "(especially Microsoft Defender) stress your CPU and HDD severely during big uploads"
195
+ c = 3
196
+ else:
197
+ t += "consume more resources (CPU/HDD) than normal"
198
+ self.log("root", t.format(args.j), c)
185
199
 
186
200
  if not args.no_fpool and args.j != 1:
187
201
  t = "WARNING: ignoring --use-fpool because multithreading (-j{}) is enabled"
@@ -400,25 +414,48 @@ class SvcHub(object):
400
414
  self.log("root", t, 3)
401
415
  return
402
416
 
403
- import sqlite3
404
417
 
405
- create = True
418
+ # policy:
419
+ # the sessions-db is whatever, if something looks broken then just nuke it
420
+
406
421
  db_path = self.args.ses_db
407
- self.log("root", "opening sessions-db %s" % (db_path,))
408
- for n in range(2):
422
+ db_lock = db_path + ".lock"
423
+ try:
424
+ create = not os.path.getsize(db_path)
425
+ except:
426
+ create = True
427
+ zs = "creating new" if create else "opening"
428
+ self.log("root", "%s sessions-db %s" % (zs, db_path))
429
+
430
+ for tries in range(2):
431
+ sver = 0
409
432
  try:
410
433
  db = sqlite3.connect(db_path)
411
434
  cur = db.cursor()
412
435
  try:
436
+ zs = "select v from kv where k='sver'"
437
+ sver = cur.execute(zs).fetchall()[0][0]
438
+ if sver > VER_SESSION_DB:
439
+ zs = "this version of copyparty only understands session-db v%d and older; the db is v%d"
440
+ raise Exception(zs % (VER_SESSION_DB, sver))
441
+
413
442
  cur.execute("select count(*) from us").fetchone()
414
- create = False
415
- break
416
443
  except:
417
- pass
444
+ if sver:
445
+ raise
446
+ sver = 1
447
+ self._create_session_db(cur)
448
+ err = self._verify_session_db(cur, sver, db_path)
449
+ if err:
450
+ tries = 99
451
+ self.args.no_ses = True
452
+ self.log("root", err, 3)
453
+ break
454
+
418
455
  except Exception as ex:
419
- if n:
456
+ if tries or sver > VER_SESSION_DB:
420
457
  raise
421
- t = "sessions-db corrupt; deleting and recreating: %r"
458
+ t = "sessions-db is unusable; deleting and recreating: %r"
422
459
  self.log("root", t % (ex,), 3)
423
460
  try:
424
461
  cur.close() # type: ignore
@@ -428,8 +465,13 @@ class SvcHub(object):
428
465
  db.close() # type: ignore
429
466
  except:
430
467
  pass
468
+ try:
469
+ os.unlink(db_lock)
470
+ except:
471
+ pass
431
472
  os.unlink(db_path)
432
473
 
474
+ def _create_session_db(self, cur ) :
433
475
  sch = [
434
476
  r"create table kv (k text, v int)",
435
477
  r"create table us (un text, si text, t0 int)",
@@ -439,15 +481,44 @@ class SvcHub(object):
439
481
  r"create index us_t0 on us(t0)",
440
482
  r"insert into kv values ('sver', 1)",
441
483
  ]
484
+ for cmd in sch:
485
+ cur.execute(cmd)
486
+ self.log("root", "created new sessions-db")
442
487
 
443
- if create:
444
- for cmd in sch:
445
- cur.execute(cmd)
446
- self.log("root", "created new sessions-db")
447
- db.commit()
488
+ def _verify_session_db(self, cur , sver , db_path ) :
489
+ # ensure writable (maybe owned by other user)
490
+ db = cur.connection
491
+
492
+ try:
493
+ zil = cur.execute("select v from kv where k='pid'").fetchall()
494
+ if len(zil) > 1:
495
+ raise Exception()
496
+ owner = zil[0][0]
497
+ except:
498
+ owner = 0
499
+
500
+ if not lock_file(db_path + ".lock"):
501
+ t = "the sessions-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --ses-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now disable sessions and instead use plaintext passwords in cookies."
502
+ return t % (db_path, owner)
503
+
504
+ vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
505
+ if owner:
506
+ # wear-estimate: 2 cells; offsets 0x10, 0x50, 0x19720
507
+ for k, v in vars:
508
+ cur.execute("update kv set v=? where k=?", (v, k))
509
+ else:
510
+ # wear-estimate: 3~4 cells; offsets 0x10, 0x50, 0x19180, 0x19710, 0x36000, 0x360b0, 0x36b90
511
+ for k, v in vars:
512
+ cur.execute("insert into kv values(?, ?)", (k, v))
448
513
 
514
+ if sver < VER_SESSION_DB:
515
+ cur.execute("delete from kv where k='sver'")
516
+ cur.execute("insert into kv values('sver',?)", (VER_SESSION_DB,))
517
+
518
+ db.commit()
449
519
  cur.close()
450
520
  db.close()
521
+ return ""
451
522
 
452
523
  def setup_share_db(self) :
453
524
  al = self.args
@@ -456,7 +527,6 @@ class SvcHub(object):
456
527
  al.shr = ""
457
528
  return
458
529
 
459
- import sqlite3
460
530
 
461
531
  al.shr = al.shr.strip("/")
462
532
  if "/" in al.shr or not al.shr:
@@ -467,34 +537,48 @@ class SvcHub(object):
467
537
  al.shr = "/%s/" % (al.shr,)
468
538
  al.shr1 = al.shr[1:]
469
539
 
470
- create = True
471
- modified = False
540
+ # policy:
541
+ # the shares-db is important, so panic if something is wrong
542
+
472
543
  db_path = self.args.shr_db
473
- self.log("root", "opening shares-db %s" % (db_path,))
474
- for n in range(2):
475
- try:
476
- db = sqlite3.connect(db_path)
477
- cur = db.cursor()
478
- try:
479
- cur.execute("select count(*) from sh").fetchone()
480
- create = False
481
- break
482
- except:
483
- pass
484
- except Exception as ex:
485
- if n:
486
- raise
487
- t = "shares-db corrupt; deleting and recreating: %r"
488
- self.log("root", t % (ex,), 3)
489
- try:
490
- cur.close() # type: ignore
491
- except:
492
- pass
493
- try:
494
- db.close() # type: ignore
495
- except:
496
- pass
497
- os.unlink(db_path)
544
+ db_lock = db_path + ".lock"
545
+ try:
546
+ create = not os.path.getsize(db_path)
547
+ except:
548
+ create = True
549
+ zs = "creating new" if create else "opening"
550
+ self.log("root", "%s shares-db %s" % (zs, db_path))
551
+
552
+ sver = 0
553
+ try:
554
+ db = sqlite3.connect(db_path)
555
+ cur = db.cursor()
556
+ if not create:
557
+ zs = "select v from kv where k='sver'"
558
+ sver = cur.execute(zs).fetchall()[0][0]
559
+ if sver > VER_SHARES_DB:
560
+ zs = "this version of copyparty only understands shares-db v%d and older; the db is v%d"
561
+ raise Exception(zs % (VER_SHARES_DB, sver))
562
+
563
+ cur.execute("select count(*) from sh").fetchone()
564
+ except Exception as ex:
565
+ t = "could not open shares-db; will now panic...\nthe following database must be repaired or deleted before you can launch copyparty:\n%s\n\nERROR: %s\n\nadditional details:\n%s\n"
566
+ self.log("root", t % (db_path, ex, min_ex()), 1)
567
+ raise
568
+
569
+ try:
570
+ zil = cur.execute("select v from kv where k='pid'").fetchall()
571
+ if len(zil) > 1:
572
+ raise Exception()
573
+ owner = zil[0][0]
574
+ except:
575
+ owner = 0
576
+
577
+ if not lock_file(db_lock):
578
+ t = "the shares-db [%s] is already in use by another copyparty instance (pid:%d). This is not supported; please provide another database with --shr-db or give this copyparty-instance its entirely separate config-folder by setting another path in the XDG_CONFIG_HOME env-var. You can also disable this safeguard by setting env-var PRTY_NO_DB_LOCK=1. Will now panic."
579
+ t = t % (db_path, owner)
580
+ self.log("root", t, 1)
581
+ raise Exception(t)
498
582
 
499
583
  sch1 = [
500
584
  r"create table kv (k text, v int)",
@@ -506,32 +590,35 @@ class SvcHub(object):
506
590
  r"create index sf_k on sf(k)",
507
591
  r"create index sh_k on sh(k)",
508
592
  r"create index sh_t1 on sh(t1)",
593
+ r"insert into kv values ('sver', 2)",
509
594
  ]
510
595
 
511
- if create:
512
- dver = 2
513
- modified = True
596
+ if not sver:
597
+ sver = VER_SHARES_DB
514
598
  for cmd in sch1 + sch2:
515
599
  cur.execute(cmd)
516
600
  self.log("root", "created new shares-db")
517
- else:
518
- (dver,) = cur.execute("select v from kv where k = 'sver'").fetchall()[0]
519
601
 
520
- if dver == 1:
521
- modified = True
602
+ if sver == 1:
522
603
  for cmd in sch2:
523
604
  cur.execute(cmd)
524
605
  cur.execute("update sh set st = 0")
525
606
  self.log("root", "shares-db schema upgrade ok")
526
607
 
527
- if modified:
528
- for cmd in [
529
- r"delete from kv where k = 'sver'",
530
- r"insert into kv values ('sver', %d)" % (2,),
531
- ]:
532
- cur.execute(cmd)
533
- db.commit()
608
+ if sver < VER_SHARES_DB:
609
+ cur.execute("delete from kv where k='sver'")
610
+ cur.execute("insert into kv values('sver',?)", (VER_SHARES_DB,))
534
611
 
612
+ vars = (("pid", os.getpid()), ("ts", int(time.time() * 1000)))
613
+ if owner:
614
+ # wear-estimate: same as sessions-db
615
+ for k, v in vars:
616
+ cur.execute("update kv set v=? where k=?", (v, k))
617
+ else:
618
+ for k, v in vars:
619
+ cur.execute("insert into kv values(?, ?)", (k, v))
620
+
621
+ db.commit()
535
622
  cur.close()
536
623
  db.close()
537
624
 
@@ -669,10 +756,11 @@ class SvcHub(object):
669
756
  t += ", "
670
757
  t += "\033[0mNG: \033[35m" + sng
671
758
 
672
- t += "\033[0m, see --deps"
673
- self.log("dependencies", t, 6)
759
+ t += "\033[0m, see --deps (this is fine btw)"
760
+ self.log("optional-dependencies", t, 6)
674
761
 
675
762
  def _check_env(self) :
763
+ al = self.args
676
764
  try:
677
765
  files = os.listdir(E.cfg)
678
766
  except:
@@ -689,6 +777,21 @@ class SvcHub(object):
689
777
  if self.args.bauth_last:
690
778
  self.log("root", "WARNING: ignoring --bauth-last due to --no-bauth", 3)
691
779
 
780
+ have_tcp = False
781
+ for zs in al.i:
782
+ if not zs.startswith("unix:"):
783
+ have_tcp = True
784
+ if not have_tcp:
785
+ zb = False
786
+ zs = "z zm zm4 zm6 zmv zmvv zs zsv zv"
787
+ for zs in zs.split():
788
+ if getattr(al, zs, False):
789
+ setattr(al, zs, False)
790
+ zb = True
791
+ if zb:
792
+ t = "only listening on unix-sockets; cannot enable zeroconf/mdns/ssdp as requested"
793
+ self.log("root", t, 3)
794
+
692
795
  if not self.args.no_dav:
693
796
  from .dxml import DXML_OK
694
797
 
@@ -753,7 +856,7 @@ class SvcHub(object):
753
856
  vl = [os.path.expandvars(os.path.expanduser(x)) for x in vl]
754
857
  setattr(al, k, vl)
755
858
 
756
- for k in "lo hist ssl_log".split(" "):
859
+ for k in "lo hist dbpath ssl_log".split(" "):
757
860
  vs = getattr(al, k)
758
861
  if vs:
759
862
  vs = os.path.expandvars(os.path.expanduser(vs))
copyparty/tcpsrv.py CHANGED
@@ -563,7 +563,7 @@ class TcpSrv(object):
563
563
  ip = None
564
564
  ips = list(t1) + list(t2)
565
565
  qri = self.args.qri
566
- if self.args.zm and not qri:
566
+ if self.args.zm and not qri and ips:
567
567
  name = self.args.name + ".local"
568
568
  t1[name] = next(v for v in (t1 or t2).values())
569
569
  ips = [name] + ips
copyparty/th_cli.py CHANGED
@@ -1,18 +1,23 @@
1
1
  # coding: utf-8
2
2
  from __future__ import print_function, unicode_literals
3
3
 
4
+ import errno
4
5
  import os
6
+ import stat
5
7
 
6
8
  from .__init__ import TYPE_CHECKING
7
9
  from .authsrv import VFS
8
10
  from .bos import bos
9
11
  from .th_srv import EXTS_AC, HAVE_WEBP, thumb_path
10
- from .util import Cooldown
12
+ from .util import Cooldown, Pebkac
11
13
 
12
14
  if TYPE_CHECKING:
13
15
  from .httpsrv import HttpSrv
14
16
 
15
17
 
18
+ IOERROR = "reading the file was denied by the server os; either due to filesystem permissions, selinux, apparmor, or similar:\n%r"
19
+
20
+
16
21
  class ThumbCli(object):
17
22
  def __init__(self, hsrv ) :
18
23
  self.broker = hsrv.broker
@@ -121,7 +126,7 @@ class ThumbCli(object):
121
126
 
122
127
  tpath = thumb_path(histpath, rem, mtime, fmt, self.fmt_ffa)
123
128
  tpaths = [tpath]
124
- if fmt == "w":
129
+ if fmt[:1] == "w":
125
130
  # also check for jpg (maybe webp is unavailable)
126
131
  tpaths.append(tpath.rsplit(".", 1)[0] + ".jpg")
127
132
 
@@ -154,8 +159,22 @@ class ThumbCli(object):
154
159
  if abort:
155
160
  return None
156
161
 
157
- if not bos.path.getsize(os.path.join(ptop, rem)):
158
- return None
162
+ ap = os.path.join(ptop, rem)
163
+ try:
164
+ st = bos.stat(ap)
165
+ if not st.st_size or not stat.S_ISREG(st.st_mode):
166
+ return None
167
+
168
+ with open(ap, "rb", 4) as f:
169
+ if not f.read(4):
170
+ raise Exception()
171
+ except OSError as ex:
172
+ if ex.errno == errno.ENOENT:
173
+ raise Pebkac(404)
174
+ else:
175
+ raise Pebkac(500, IOERROR % (ex,))
176
+ except Exception as ex:
177
+ raise Pebkac(500, IOERROR % (ex,))
159
178
 
160
179
  x = self.broker.ask("thumbsrv.get", ptop, rem, mtime, fmt)
161
180
  return x.get() # type: ignore
copyparty/th_srv.py CHANGED
@@ -4,8 +4,10 @@ from __future__ import print_function, unicode_literals
4
4
  import hashlib
5
5
  import logging
6
6
  import os
7
+ import re
7
8
  import shutil
8
9
  import subprocess as sp
10
+ import tempfile
9
11
  import threading
10
12
  import time
11
13
 
@@ -18,6 +20,7 @@ from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, au_unpk, ffprobe
18
20
  from .util import BytesIO # type: ignore
19
21
  from .util import (
20
22
  FFMPEG_URL,
23
+ VF_CAREFUL,
21
24
  Cooldown,
22
25
  Daemon,
23
26
  afsenc,
@@ -45,6 +48,10 @@ HAVE_WEBP = False
45
48
 
46
49
  EXTS_TH = set(["jpg", "webp", "png"])
47
50
  EXTS_AC = set(["opus", "owa", "caf", "mp3"])
51
+ EXTS_SPEC_SAFE = set("aif aiff flac mp3 opus wav".split())
52
+
53
+ PTN_TS = re.compile("^-?[0-9a-f]{8,10}$")
54
+
48
55
 
49
56
  try:
50
57
  if os.environ.get("PRTY_NO_PIL"):
@@ -160,12 +167,15 @@ class ThumbSrv(object):
160
167
 
161
168
  self.mutex = threading.Lock()
162
169
  self.busy = {}
170
+ self.untemp = {}
163
171
  self.ram = {}
164
172
  self.memcond = threading.Condition(self.mutex)
165
173
  self.stopping = False
166
174
  self.rm_nullthumbs = True # forget failed conversions on startup
167
175
  self.nthr = max(1, self.args.th_mt)
168
176
 
177
+ self.exts_spec_unsafe = set(self.args.th_spec_cnv.split(","))
178
+
169
179
  self.q = Queue(self.nthr * 4)
170
180
  for n in range(self.nthr):
171
181
  Daemon(self.worker, "thumb-{}-{}".format(n, self.nthr))
@@ -382,8 +392,12 @@ class ThumbSrv(object):
382
392
  self.log(msg, c)
383
393
  if getattr(ex, "returncode", 0) != 321:
384
394
  if fun == funs[-1]:
385
- with open(ttpath, "wb") as _:
386
- pass
395
+ try:
396
+ with open(ttpath, "wb") as _:
397
+ pass
398
+ except Exception as ex:
399
+ t = "failed to create the file [%s]: %r"
400
+ self.log(t % (ttpath, ex), 3)
387
401
  else:
388
402
  # ffmpeg may spawn empty files on windows
389
403
  try:
@@ -396,13 +410,24 @@ class ThumbSrv(object):
396
410
 
397
411
  try:
398
412
  wrename(self.log, ttpath, tpath, vn.flags)
399
- except:
413
+ except Exception as ex:
414
+ if not os.path.exists(tpath):
415
+ t = "failed to move [%s] to [%s]: %r"
416
+ self.log(t % (ttpath, tpath, ex), 3)
400
417
  pass
401
418
 
419
+ untemp = []
402
420
  with self.mutex:
403
421
  subs = self.busy[tpath]
404
422
  del self.busy[tpath]
405
423
  self.ram.pop(ttpath, None)
424
+ untemp = self.untemp.pop(ttpath, None) or []
425
+
426
+ for ap in untemp:
427
+ try:
428
+ wunlink(self.log, ap, VF_CAREFUL)
429
+ except:
430
+ pass
406
431
 
407
432
  for x in subs:
408
433
  with x:
@@ -655,15 +680,43 @@ class ThumbSrv(object):
655
680
  if "ac" not in ret:
656
681
  raise Exception("not audio")
657
682
 
683
+ fext = abspath.split(".")[-1].lower()
684
+
658
685
  # https://trac.ffmpeg.org/ticket/10797
659
686
  # expect 1 GiB every 600 seconds when duration is tricky;
660
687
  # simple filetypes are generally safer so let's special-case those
661
- safe = ("flac", "wav", "aif", "aiff", "opus")
662
- coeff = 1800 if abspath.split(".")[-1].lower() in safe else 600
663
- dur = ret[".dur"][1] if ".dur" in ret else 300
688
+ coeff = 1800 if fext in EXTS_SPEC_SAFE else 600
689
+ dur = ret[".dur"][1] if ".dur" in ret else 900
664
690
  need = 0.2 + dur / coeff
665
691
  self.wait4ram(need, tpath)
666
692
 
693
+ infile = abspath
694
+ if dur >= 900 or fext in self.exts_spec_unsafe:
695
+ with tempfile.NamedTemporaryFile(suffix=".spec.flac", delete=False) as f:
696
+ f.write(b"h")
697
+ infile = f.name
698
+ try:
699
+ self.untemp[tpath].append(infile)
700
+ except:
701
+ self.untemp[tpath] = [infile]
702
+
703
+ # fmt: off
704
+ cmd = [
705
+ b"ffmpeg",
706
+ b"-nostdin",
707
+ b"-v", b"error",
708
+ b"-hide_banner",
709
+ b"-i", fsenc(abspath),
710
+ b"-map", b"0:a:0",
711
+ b"-ac", b"1",
712
+ b"-ar", b"48000",
713
+ b"-sample_fmt", b"s16",
714
+ b"-t", b"900",
715
+ b"-y", fsenc(infile),
716
+ ]
717
+ # fmt: on
718
+ self._run_ff(cmd, vn)
719
+
667
720
  fc = "[0:a:0]aresample=48000{},showspectrumpic=s="
668
721
  if "3" in fmt:
669
722
  fc += "1280x1024,crop=1420:1056:70:48[o]"
@@ -683,7 +736,7 @@ class ThumbSrv(object):
683
736
  b"-nostdin",
684
737
  b"-v", b"error",
685
738
  b"-hide_banner",
686
- b"-i", fsenc(abspath),
739
+ b"-i", fsenc(infile),
687
740
  b"-filter_complex", fc.encode("utf-8"),
688
741
  b"-map", b"[o]",
689
742
  b"-frames:v", b"1",
@@ -987,6 +1040,8 @@ class ThumbSrv(object):
987
1040
  # thumb file
988
1041
  try:
989
1042
  b64, ts, ext = f.split(".")
1043
+ if len(ts) > 8 and PTN_TS.match(ts):
1044
+ ts = "yeahokay"
990
1045
  if len(b64) != 24 or len(ts) != 8 or ext not in exts:
991
1046
  raise Exception()
992
1047
  except:
copyparty/u2idx.py CHANGED
@@ -128,9 +128,9 @@ class U2idx(object):
128
128
 
129
129
 
130
130
  ptop = vn.realpath
131
- histpath = self.asrv.vfs.histtab.get(ptop)
131
+ histpath = self.asrv.vfs.dbpaths.get(ptop)
132
132
  if not histpath:
133
- self.log("no histpath for %r" % (ptop,))
133
+ self.log("no dbpath for %r" % (ptop,))
134
134
  return None
135
135
 
136
136
  db_path = os.path.join(histpath, "up2k.db")
copyparty/up2k.py CHANGED
@@ -91,7 +91,7 @@ VF_AFFECTS_INDEXING = set(zsg.split(" "))
91
91
 
92
92
  SBUSY = "cannot receive uploads right now;\nserver busy with %s.\nPlease wait; the client will retry..."
93
93
 
94
- HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume)"
94
+ HINT_HISTPATH = "you could try moving the database to another location (preferably an SSD or NVME drive) using either the --hist argument (global option for all volumes), or the hist volflag (just for this volume), or, if you want to keep the thumbnails in the current location and only move the database itself, then use --dbpath or volflag dbpath"
95
95
 
96
96
 
97
97
  NULLSTAT = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
@@ -1089,9 +1089,9 @@ class Up2k(object):
1089
1089
  self, ptop , flags
1090
1090
  ) :
1091
1091
  """mutex(main,reg) me"""
1092
- histpath = self.vfs.histtab.get(ptop)
1092
+ histpath = self.vfs.dbpaths.get(ptop)
1093
1093
  if not histpath:
1094
- self.log("no histpath for %r" % (ptop,))
1094
+ self.log("no dbpath for %r" % (ptop,))
1095
1095
  return None
1096
1096
 
1097
1097
  db_path = os.path.join(histpath, "up2k.db")
@@ -1336,12 +1336,15 @@ class Up2k(object):
1336
1336
  ]
1337
1337
  excl += [absreal(x) for x in excl]
1338
1338
  excl += list(self.vfs.histtab.values())
1339
+ excl += list(self.vfs.dbpaths.values())
1339
1340
  if WINDOWS:
1340
1341
  excl = [x.replace("/", "\\") for x in excl]
1341
1342
  else:
1342
1343
  # ~/.wine/dosdevices/z:/ and such
1343
1344
  excl.extend(("/dev", "/proc", "/run", "/sys"))
1344
1345
 
1346
+ excl = list({k: 1 for k in excl})
1347
+
1345
1348
  if self.args.re_dirsz:
1346
1349
  db.c.execute("delete from ds")
1347
1350
  db.n += 1
@@ -5078,7 +5081,7 @@ class Up2k(object):
5078
5081
 
5079
5082
  def _snap_reg(self, ptop , reg ) :
5080
5083
  now = time.time()
5081
- histpath = self.vfs.histtab.get(ptop)
5084
+ histpath = self.vfs.dbpaths.get(ptop)
5082
5085
  if not histpath:
5083
5086
  return
5084
5087