copyparty 1.7.6__py3-none-any.whl → 1.8.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/httpcli.py CHANGED
@@ -58,6 +58,7 @@ from .util import (
58
58
  html_escape,
59
59
  humansize,
60
60
  ipnorm,
61
+ loadpy,
61
62
  min_ex,
62
63
  quotep,
63
64
  rand_name,
@@ -169,13 +170,16 @@ class HttpCli(object):
169
170
  def log(self, msg , c = 0) :
170
171
  ptn = self.asrv.re_pwd
171
172
  if ptn and ptn.search(msg):
172
- msg = ptn.sub(self.unpwd, msg)
173
+ if self.asrv.ah.on:
174
+ msg = ptn.sub("\033[7m pw \033[27m", msg)
175
+ else:
176
+ msg = ptn.sub(self.unpwd, msg)
173
177
 
174
178
  self.log_func(self.log_src, msg, c)
175
179
 
176
180
  def unpwd(self, m ) :
177
- a, b = m.groups()
178
- return "=\033[7m {} \033[27m{}".format(self.asrv.iacct[a], b)
181
+ a, b, c = m.groups()
182
+ return "{}\033[7m {} \033[27m{}".format(a, self.asrv.iacct[b], c)
179
183
 
180
184
  def _check_nonfatal(self, ex , post ) :
181
185
  if post:
@@ -379,13 +383,14 @@ class HttpCli(object):
379
383
  zs = base64.b64decode(zb).decode("utf-8")
380
384
  # try "pwd", "x:pwd", "pwd:x"
381
385
  for bauth in [zs] + zs.split(":", 1)[::-1]:
382
- if self.asrv.iacct.get(bauth):
386
+ hpw = self.asrv.ah.hash(bauth)
387
+ if self.asrv.iacct.get(hpw):
383
388
  break
384
389
  except:
385
390
  pass
386
391
 
387
392
  self.pw = uparam.get("pw") or self.headers.get("pw") or bauth or cookie_pw
388
- self.uname = self.asrv.iacct.get(self.pw) or "*"
393
+ self.uname = self.asrv.iacct.get(self.asrv.ah.hash(self.pw)) or "*"
389
394
  self.rvol = self.asrv.vfs.aread[self.uname]
390
395
  self.wvol = self.asrv.vfs.awrite[self.uname]
391
396
  self.mvol = self.asrv.vfs.amove[self.uname]
@@ -759,11 +764,27 @@ class HttpCli(object):
759
764
  return True
760
765
 
761
766
  if not self.can_read and not self.can_write and not self.can_get:
762
- if self.vpath:
763
- self.log("inaccessible: [{}]".format(self.vpath))
764
- return self.tx_404(True)
767
+ t = "@{} has no access to [{}]"
768
+ self.log(t.format(self.uname, self.vpath))
769
+
770
+ if self.avn and "on403" in self.avn.flags:
771
+ vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
772
+ ret = self.on40x(vn.flags["on403"], vn, rem)
773
+ if ret == "true":
774
+ return True
775
+ elif ret == "false":
776
+ return False
777
+ elif ret == "allow":
778
+ self.log("plugin override; access permitted")
779
+ self.can_read = self.can_write = self.can_move = True
780
+ self.can_delete = self.can_get = self.can_upget = True
781
+ else:
782
+ return self.tx_404(True)
783
+ else:
784
+ if self.vpath:
785
+ return self.tx_404(True)
765
786
 
766
- self.uparam["h"] = ""
787
+ self.uparam["h"] = ""
767
788
 
768
789
  if "tree" in self.uparam:
769
790
  return self.tx_tree()
@@ -1354,7 +1375,9 @@ class HttpCli(object):
1354
1375
  lim = vfs.get_dbv(rem)[0].lim
1355
1376
  fdir = vfs.canonical(rem)
1356
1377
  if lim:
1357
- fdir, rem = lim.all(self.ip, rem, remains, fdir)
1378
+ fdir, rem = lim.all(
1379
+ self.ip, rem, remains, vfs.realpath, fdir, self.conn.hsrv.broker
1380
+ )
1358
1381
 
1359
1382
  fn = None
1360
1383
  if rem and not self.trailing_slash and not bos.path.isdir(fdir):
@@ -1487,6 +1510,7 @@ class HttpCli(object):
1487
1510
  lim.bup(self.ip, post_sz)
1488
1511
  try:
1489
1512
  lim.chk_sz(post_sz)
1513
+ lim.chk_vsz(self.conn.hsrv.broker, vfs.realpath, post_sz)
1490
1514
  except:
1491
1515
  bos.unlink(path)
1492
1516
  raise
@@ -1961,7 +1985,7 @@ class HttpCli(object):
1961
1985
  return True
1962
1986
 
1963
1987
  def get_pwd_cookie(self, pwd ) :
1964
- if pwd in self.asrv.iacct:
1988
+ if self.asrv.ah.hash(pwd) in self.asrv.iacct:
1965
1989
  msg = "login ok"
1966
1990
  dur = int(60 * 60 * self.args.logout)
1967
1991
  else:
@@ -2097,7 +2121,9 @@ class HttpCli(object):
2097
2121
  lim = vfs.get_dbv(rem)[0].lim
2098
2122
  fdir_base = vfs.canonical(rem)
2099
2123
  if lim:
2100
- fdir_base, rem = lim.all(self.ip, rem, -1, fdir_base)
2124
+ fdir_base, rem = lim.all(
2125
+ self.ip, rem, -1, vfs.realpath, fdir_base, self.conn.hsrv.broker
2126
+ )
2101
2127
  upload_vpath = "{}/{}".format(vfs.vpath, rem).strip("/")
2102
2128
  if not nullwrite:
2103
2129
  bos.makedirs(fdir_base)
@@ -2190,6 +2216,7 @@ class HttpCli(object):
2190
2216
  try:
2191
2217
  lim.chk_df(tabspath, sz, True)
2192
2218
  lim.chk_sz(sz)
2219
+ lim.chk_vsz(self.conn.hsrv.broker, vfs.realpath, sz)
2193
2220
  lim.chk_bup(self.ip)
2194
2221
  lim.chk_nup(self.ip)
2195
2222
  except:
@@ -2365,7 +2392,7 @@ class HttpCli(object):
2365
2392
  fp = vfs.canonical(rp)
2366
2393
  lim = vfs.get_dbv(rem)[0].lim
2367
2394
  if lim:
2368
- fp, rp = lim.all(self.ip, rp, clen, fp)
2395
+ fp, rp = lim.all(self.ip, rp, clen, vfs.realpath, fp, self.conn.hsrv.broker)
2369
2396
  bos.makedirs(fp)
2370
2397
 
2371
2398
  fp = os.path.join(fp, fn)
@@ -2436,6 +2463,25 @@ class HttpCli(object):
2436
2463
  if p_field != "body":
2437
2464
  raise Pebkac(400, "expected body, got {}".format(p_field))
2438
2465
 
2466
+ xbu = vfs.flags.get("xbu")
2467
+ if xbu:
2468
+ if not runhook(
2469
+ self.log,
2470
+ xbu,
2471
+ fp,
2472
+ self.vpath,
2473
+ self.host,
2474
+ self.uname,
2475
+ time.time(),
2476
+ 0,
2477
+ self.ip,
2478
+ time.time(),
2479
+ "",
2480
+ ):
2481
+ t = "save blocked by xbu server config"
2482
+ self.log(t, 1)
2483
+ raise Pebkac(403, t)
2484
+
2439
2485
  if bos.path.exists(fp):
2440
2486
  bos.unlink(fp)
2441
2487
 
@@ -2447,6 +2493,7 @@ class HttpCli(object):
2447
2493
  lim.bup(self.ip, sz)
2448
2494
  try:
2449
2495
  lim.chk_sz(sz)
2496
+ lim.chk_vsz(self.conn.hsrv.broker, vfs.realpath, sz)
2450
2497
  except:
2451
2498
  bos.unlink(fp)
2452
2499
  raise
@@ -2455,6 +2502,39 @@ class HttpCli(object):
2455
2502
  new_lastmod3 = int(new_lastmod * 1000)
2456
2503
  sha512 = sha512[:56]
2457
2504
 
2505
+ xau = vfs.flags.get("xau")
2506
+ if xau and not runhook(
2507
+ self.log,
2508
+ xau,
2509
+ fp,
2510
+ self.vpath,
2511
+ self.host,
2512
+ self.uname,
2513
+ new_lastmod,
2514
+ sz,
2515
+ self.ip,
2516
+ new_lastmod,
2517
+ "",
2518
+ ):
2519
+ t = "save blocked by xau server config"
2520
+ self.log(t, 1)
2521
+ os.unlink(fp)
2522
+ raise Pebkac(403, t)
2523
+
2524
+ vfs, rem = vfs.get_dbv(rem)
2525
+ self.conn.hsrv.broker.say(
2526
+ "up2k.hash_file",
2527
+ vfs.realpath,
2528
+ vfs.vpath,
2529
+ vfs.flags,
2530
+ vsplit(rem)[0],
2531
+ fn,
2532
+ self.ip,
2533
+ new_lastmod,
2534
+ self.uname,
2535
+ True,
2536
+ )
2537
+
2458
2538
  response = json.dumps(
2459
2539
  {"ok": True, "lastmod": new_lastmod3, "size": sz, "sha512": sha512}
2460
2540
  )
@@ -2989,6 +3069,20 @@ class HttpCli(object):
2989
3069
  self.reply(html.encode("utf-8"), status=rc)
2990
3070
  return True
2991
3071
 
3072
+ def on40x(self, mods , vn , rem ) :
3073
+ for mpath in mods:
3074
+ try:
3075
+ mod = loadpy(mpath, self.args.hot_handlers)
3076
+ except Exception as ex:
3077
+ self.log("import failed: {!r}".format(ex))
3078
+ continue
3079
+
3080
+ ret = mod.main(self, vn, rem)
3081
+ if ret:
3082
+ return ret.lower()
3083
+
3084
+ return "" # unhandled / fallthrough
3085
+
2992
3086
  def scanvol(self) :
2993
3087
  if not self.can_read or not self.can_write:
2994
3088
  raise Pebkac(403, "not allowed for user " + self.uname)
@@ -3306,7 +3400,21 @@ class HttpCli(object):
3306
3400
  try:
3307
3401
  st = bos.stat(abspath)
3308
3402
  except:
3309
- return self.tx_404()
3403
+ if "on404" not in vn.flags:
3404
+ return self.tx_404()
3405
+
3406
+ ret = self.on40x(vn.flags["on404"], vn, rem)
3407
+ if ret == "true":
3408
+ return True
3409
+ elif ret == "false":
3410
+ return False
3411
+ elif ret == "retry":
3412
+ try:
3413
+ st = bos.stat(abspath)
3414
+ except:
3415
+ return self.tx_404()
3416
+ else:
3417
+ return self.tx_404()
3310
3418
 
3311
3419
  if rem.startswith(".hist/up2k.") or (
3312
3420
  rem.endswith("/dir.txt") and rem.startswith(".hist/th/")
copyparty/ico.py CHANGED
@@ -17,7 +17,9 @@ class Ico(object):
17
17
  def get(self, ext , as_thumb , chrome ) :
18
18
  """placeholder to make thumbnails not break"""
19
19
 
20
- zb = hashlib.sha1(ext.encode("utf-8")).digest()[2:4]
20
+ bext = ext.encode("ascii", "replace")
21
+ ext = bext.decode("utf-8")
22
+ zb = hashlib.sha1(bext).digest()[2:4]
21
23
  if PY2:
22
24
  zb = [ord(x) for x in zb]
23
25
 
@@ -33,7 +35,7 @@ class Ico(object):
33
35
  h = int(100 / (float(sw) / float(sh)))
34
36
  w = 100
35
37
 
36
- if chrome and as_thumb:
38
+ if chrome:
37
39
  # cannot handle more than ~2000 unique SVGs
38
40
  if HAVE_PIL:
39
41
  # svg: 3s, cache: 6s, this: 8s
@@ -43,8 +45,19 @@ class Ico(object):
43
45
  w = 64
44
46
  img = Image.new("RGB", (w, h), "#" + c[:6])
45
47
  pb = ImageDraw.Draw(img)
46
- tw, th = pb.textsize(ext)
47
- pb.text(((w - tw) // 2, (h - th) // 2), ext, fill="#" + c[6:])
48
+ try:
49
+ _, _, tw, th = pb.textbbox((0, 0), ext)
50
+ except:
51
+ tw, th = pb.textsize(ext)
52
+
53
+ tw += len(ext)
54
+ cw = tw // len(ext)
55
+ x = ((w - tw) // 2) - (cw * 2) // 3
56
+ fill = "#" + c[6:]
57
+ for ch in ext:
58
+ pb.text((x, (h - th) // 2), " %s " % (ch,), fill=fill)
59
+ x += cw
60
+
48
61
  img = img.resize((w * 3, h * 3), Image.NEAREST)
49
62
 
50
63
  buf = BytesIO()
copyparty/pwhash.py ADDED
@@ -0,0 +1,145 @@
1
+ # coding: utf-8
2
+ from __future__ import print_function, unicode_literals
3
+
4
+ import argparse
5
+ import base64
6
+ import hashlib
7
+ import sys
8
+ import threading
9
+
10
+ from .__init__ import unicode
11
+
12
+
13
+ class PWHash(object):
14
+ def __init__(self, args ):
15
+ self.args = args
16
+
17
+ try:
18
+ alg, ac = args.ah_alg.split(",")
19
+ except:
20
+ alg = args.ah_alg
21
+ ac = {}
22
+
23
+ if alg == "none":
24
+ alg = ""
25
+
26
+ self.alg = alg
27
+ self.ac = ac
28
+ if not alg:
29
+ self.on = False
30
+ self.hash = unicode
31
+ return
32
+
33
+ self.on = True
34
+ self.salt = args.ah_salt.encode("utf-8")
35
+ self.cache = {}
36
+ self.mutex = threading.Lock()
37
+ self.hash = self._cache_hash
38
+
39
+ if alg == "sha2":
40
+ self._hash = self._gen_sha2
41
+ elif alg == "scrypt":
42
+ self._hash = self._gen_scrypt
43
+ elif alg == "argon2":
44
+ self._hash = self._gen_argon2
45
+ else:
46
+ t = "unsupported password hashing algorithm [{}], must be one of these: argon2 scrypt sha2 none"
47
+ raise Exception(t.format(alg))
48
+
49
+ def _cache_hash(self, plain ) :
50
+ with self.mutex:
51
+ try:
52
+ return self.cache[plain]
53
+ except:
54
+ pass
55
+
56
+ if not plain:
57
+ return ""
58
+
59
+ if len(plain) > 255:
60
+ raise Exception("password too long")
61
+
62
+ if len(self.cache) > 9000:
63
+ self.cache = {}
64
+
65
+ ret = self._hash(plain)
66
+ self.cache[plain] = ret
67
+ return ret
68
+
69
+ def _gen_sha2(self, plain ) :
70
+ its = int(self.ac[0]) if self.ac else 424242
71
+ bplain = plain.encode("utf-8")
72
+ ret = b"\n"
73
+ for _ in range(its):
74
+ ret = hashlib.sha512(self.salt + bplain + ret).digest()
75
+
76
+ return "+" + base64.urlsafe_b64encode(ret[:24]).decode("utf-8")
77
+
78
+ def _gen_scrypt(self, plain ) :
79
+ cost = 2 << 13
80
+ its = 2
81
+ blksz = 8
82
+ para = 4
83
+ try:
84
+ cost = 2 << int(self.ac[0])
85
+ its = int(self.ac[1])
86
+ blksz = int(self.ac[2])
87
+ para = int(self.ac[3])
88
+ except:
89
+ pass
90
+
91
+ ret = plain.encode("utf-8")
92
+ for _ in range(its):
93
+ ret = hashlib.scrypt(ret, salt=self.salt, n=cost, r=blksz, p=para, dklen=24)
94
+
95
+ return "+" + base64.urlsafe_b64encode(ret).decode("utf-8")
96
+
97
+ def _gen_argon2(self, plain ) :
98
+ from argon2.low_level import Type as ArgonType
99
+ from argon2.low_level import hash_secret
100
+
101
+ time_cost = 3
102
+ mem_cost = 256
103
+ parallelism = 4
104
+ version = 19
105
+ try:
106
+ time_cost = int(self.ac[0])
107
+ mem_cost = int(self.ac[1])
108
+ parallelism = int(self.ac[2])
109
+ version = int(self.ac[3])
110
+ except:
111
+ pass
112
+
113
+ bplain = plain.encode("utf-8")
114
+
115
+ bret = hash_secret(
116
+ secret=bplain,
117
+ salt=self.salt,
118
+ time_cost=time_cost,
119
+ memory_cost=mem_cost * 1024,
120
+ parallelism=parallelism,
121
+ hash_len=24,
122
+ type=ArgonType.ID,
123
+ version=version,
124
+ )
125
+ ret = bret.split(b"$")[-1].decode("utf-8")
126
+ return "+" + ret.replace("/", "_").replace("+", "-")
127
+
128
+ def stdin(self) :
129
+ while True:
130
+ ln = sys.stdin.readline().strip()
131
+ if not ln:
132
+ break
133
+ print(self.hash(ln))
134
+
135
+ def cli(self) :
136
+ import getpass
137
+
138
+ while True:
139
+ p1 = getpass.getpass("password> ")
140
+ p2 = getpass.getpass("again or just hit ENTER> ")
141
+ if p2 and p1 != p2:
142
+ print("\033[31minputs don't match; try again\033[0m", file=sys.stderr)
143
+ continue
144
+ print(self.hash(p1))
145
+ print()
copyparty/u2idx.py CHANGED
@@ -66,7 +66,7 @@ class U2idx(object):
66
66
 
67
67
  fsize = body["size"]
68
68
  fhash = body["hash"]
69
- wark = up2k_wark_from_hashlist(self.args.salt, fsize, fhash)
69
+ wark = up2k_wark_from_hashlist(self.args.warksalt, fsize, fhash)
70
70
 
71
71
  uq = "substr(w,1,16) = ? and w = ?"
72
72
  uv = [wark[:16], wark]
copyparty/up2k.py CHANGED
@@ -41,6 +41,7 @@ from .util import (
41
41
  gen_filekey,
42
42
  gen_filekey_dbg,
43
43
  hidedir,
44
+ humansize,
44
45
  min_ex,
45
46
  quotep,
46
47
  rand_name,
@@ -56,6 +57,7 @@ from .util import (
56
57
  sfsenc,
57
58
  spack,
58
59
  statdir,
60
+ unhumanize,
59
61
  vjoin,
60
62
  vsplit,
61
63
  w8b64dec,
@@ -107,7 +109,7 @@ class Up2k(object):
107
109
  self.args = hub.args
108
110
  self.log_func = hub.log
109
111
 
110
- self.salt = self.args.salt
112
+ self.salt = self.args.warksalt
111
113
  self.r_hash = re.compile("^[0-9a-zA-Z_-]{44}$")
112
114
 
113
115
  self.gid = 0
@@ -122,6 +124,8 @@ class Up2k(object):
122
124
  self.registry = {}
123
125
  self.flags = {}
124
126
  self.droppable = {}
127
+ self.volnfiles = {}
128
+ self.volsize = {}
125
129
  self.volstate = {}
126
130
  self.vol_act = {}
127
131
  self.busy_aps = set()
@@ -258,6 +262,20 @@ class Up2k(object):
258
262
  }
259
263
  return json.dumps(ret, indent=4)
260
264
 
265
+ def get_volsize(self, ptop ) :
266
+ with self.mutex:
267
+ return self._get_volsize(ptop)
268
+
269
+ def _get_volsize(self, ptop ) :
270
+ cur = self.cur[ptop]
271
+ nbytes = self.volsize[cur]
272
+ nfiles = self.volnfiles[cur]
273
+ for j in list(self.registry.get(ptop, {}).values()):
274
+ nbytes += j["size"]
275
+ nfiles += 1
276
+
277
+ return (nbytes, nfiles)
278
+
261
279
  def rescan(
262
280
  self, all_vols , scan_vols , wait , fscan
263
281
  ) :
@@ -807,6 +825,8 @@ class Up2k(object):
807
825
  try:
808
826
  cur = self._open_db(db_path)
809
827
  self.cur[ptop] = cur
828
+ self.volsize[cur] = 0
829
+ self.volnfiles[cur] = 0
810
830
 
811
831
  # speeds measured uploading 520 small files on a WD20SPZX (SMR 2.5" 5400rpm 4kb)
812
832
  dbd = flags["dbd"]
@@ -914,6 +934,24 @@ class Up2k(object):
914
934
 
915
935
  db.c.connection.commit()
916
936
 
937
+ if vol.flags.get("vmaxb") or vol.flags.get("vmaxn"):
938
+ zs = "select count(sz), sum(sz) from up"
939
+ vn, vb = db.c.execute(zs).fetchone()
940
+ vb = vb or 0
941
+ vb += vn * 2048
942
+ self.volsize[db.c] = vb
943
+ self.volnfiles[db.c] = vn
944
+ vmaxb = unhumanize(vol.flags.get("vmaxb") or "0")
945
+ vmaxn = unhumanize(vol.flags.get("vmaxn") or "0")
946
+ t = "{} / {} ( {} / {} files) in {}".format(
947
+ humansize(vb, True),
948
+ humansize(vmaxb, True),
949
+ humansize(vn, True).rstrip("B"),
950
+ humansize(vmaxn, True).rstrip("B"),
951
+ vol.realpath,
952
+ )
953
+ self.log(t)
954
+
917
955
  return True, bool(n_add or n_rm or do_vac)
918
956
 
919
957
  def _build_dir(
@@ -1089,7 +1127,7 @@ class Up2k(object):
1089
1127
  top, rp, dts, lmod, dsz, sz
1090
1128
  )
1091
1129
  self.log(t)
1092
- self.db_rm(db.c, rd, fn)
1130
+ self.db_rm(db.c, rd, fn, 0)
1093
1131
  ret += 1
1094
1132
  db.n += 1
1095
1133
  in_db = []
@@ -1172,7 +1210,7 @@ class Up2k(object):
1172
1210
  rm_files = [x for x in hits if x not in seen_files]
1173
1211
  n_rm = len(rm_files)
1174
1212
  for fn in rm_files:
1175
- self.db_rm(db.c, rd, fn)
1213
+ self.db_rm(db.c, rd, fn, 0)
1176
1214
 
1177
1215
  if n_rm:
1178
1216
  self.log("forgot {} deleted files".format(n_rm))
@@ -2281,7 +2319,9 @@ class Up2k(object):
2281
2319
  if lost:
2282
2320
  c2 = None
2283
2321
  for cur, dp_dir, dp_fn in lost:
2284
- self.db_rm(cur, dp_dir, dp_fn)
2322
+ t = "forgetting deleted file: /{}"
2323
+ self.log(t.format(vjoin(vjoin(vfs.vpath, dp_dir), dp_fn)))
2324
+ self.db_rm(cur, dp_dir, dp_fn, cj["size"])
2285
2325
  if c2 and c2 != cur:
2286
2326
  c2.connection.commit()
2287
2327
 
@@ -2415,7 +2455,14 @@ class Up2k(object):
2415
2455
 
2416
2456
  if vfs.lim:
2417
2457
  ap2, cj["prel"] = vfs.lim.all(
2418
- cj["addr"], cj["prel"], cj["size"], ap1, reg
2458
+ cj["addr"],
2459
+ cj["prel"],
2460
+ cj["size"],
2461
+ cj["ptop"],
2462
+ ap1,
2463
+ self.hub.broker,
2464
+ reg,
2465
+ "up2k._get_volsize",
2419
2466
  )
2420
2467
  bos.makedirs(ap2)
2421
2468
  vfs.lim.nup(cj["addr"])
@@ -2733,7 +2780,7 @@ class Up2k(object):
2733
2780
 
2734
2781
  self._symlink(dst, d2, self.flags[ptop], lmod=lmod)
2735
2782
  if cur:
2736
- self.db_rm(cur, rd, fn)
2783
+ self.db_rm(cur, rd, fn, job["size"])
2737
2784
  self.db_add(cur, vflags, rd, fn, lmod, *z2[3:])
2738
2785
 
2739
2786
  if cur:
@@ -2776,7 +2823,7 @@ class Up2k(object):
2776
2823
 
2777
2824
  self.db_act = self.vol_act[ptop] = time.time()
2778
2825
  try:
2779
- self.db_rm(cur, rd, fn)
2826
+ self.db_rm(cur, rd, fn, sz)
2780
2827
  self.db_add(
2781
2828
  cur,
2782
2829
  vflags,
@@ -2806,13 +2853,17 @@ class Up2k(object):
2806
2853
 
2807
2854
  return True
2808
2855
 
2809
- def db_rm(self, db , rd , fn ) :
2856
+ def db_rm(self, db , rd , fn , sz ) :
2810
2857
  sql = "delete from up where rd = ? and fn = ?"
2811
2858
  try:
2812
- db.execute(sql, (rd, fn))
2859
+ r = db.execute(sql, (rd, fn))
2813
2860
  except:
2814
2861
  assert self.mem_cur
2815
- db.execute(sql, s3enc(self.mem_cur, rd, fn))
2862
+ r = db.execute(sql, s3enc(self.mem_cur, rd, fn))
2863
+
2864
+ if r.rowcount:
2865
+ self.volsize[db] -= sz
2866
+ self.volnfiles[db] -= 1
2816
2867
 
2817
2868
  def db_add(
2818
2869
  self,
@@ -2841,6 +2892,9 @@ class Up2k(object):
2841
2892
  v = (wark, int(ts), sz, rd, fn, ip or "", int(at or 0))
2842
2893
  db.execute(sql, v)
2843
2894
 
2895
+ self.volsize[db] += sz
2896
+ self.volnfiles[db] += 1
2897
+
2844
2898
  xau = False if skip_xau else vflags.get("xau")
2845
2899
  dst = djoin(ptop, rd, fn)
2846
2900
  if xau and not runhook(
@@ -2988,12 +3042,12 @@ class Up2k(object):
2988
3042
  break
2989
3043
 
2990
3044
  abspath = djoin(adir, fn)
3045
+ st = bos.stat(abspath)
2991
3046
  volpath = "{}/{}".format(vrem, fn).strip("/")
2992
3047
  vpath = "{}/{}".format(dbv.vpath, volpath).strip("/")
2993
3048
  self.log("rm {}\n {}".format(vpath, abspath))
2994
3049
  _ = dbv.get(volpath, uname, *permsets[0])
2995
3050
  if xbd:
2996
- st = bos.stat(abspath)
2997
3051
  if not runhook(
2998
3052
  self.log,
2999
3053
  xbd,
@@ -3017,14 +3071,26 @@ class Up2k(object):
3017
3071
  try:
3018
3072
  ptop = dbv.realpath
3019
3073
  cur, wark, _, _, _, _ = self._find_from_vpath(ptop, volpath)
3020
- self._forget_file(ptop, volpath, cur, wark, True)
3074
+ self._forget_file(ptop, volpath, cur, wark, True, st.st_size)
3021
3075
  finally:
3022
3076
  if cur:
3023
3077
  cur.connection.commit()
3024
3078
 
3025
3079
  bos.unlink(abspath)
3026
3080
  if xad:
3027
- runhook(self.log, xad, abspath, vpath, "", uname, 0, 0, ip, 0, "")
3081
+ runhook(
3082
+ self.log,
3083
+ xad,
3084
+ abspath,
3085
+ vpath,
3086
+ "",
3087
+ uname,
3088
+ st.st_mtime,
3089
+ st.st_size,
3090
+ ip,
3091
+ 0,
3092
+ "",
3093
+ )
3028
3094
 
3029
3095
  if is_dir:
3030
3096
  ok, ng = rmdirs(self.log_func, scandir, True, atop, 1)
@@ -3200,7 +3266,7 @@ class Up2k(object):
3200
3266
  if c2 and c2 != c1:
3201
3267
  self._copy_tags(c1, c2, w)
3202
3268
 
3203
- self._forget_file(svn.realpath, srem, c1, w, c1 != c2)
3269
+ self._forget_file(svn.realpath, srem, c1, w, c1 != c2, fsize)
3204
3270
  self._relink(w, svn.realpath, srem, dabs)
3205
3271
  curs.add(c1)
3206
3272
 
@@ -3276,6 +3342,7 @@ class Up2k(object):
3276
3342
  cur ,
3277
3343
  wark ,
3278
3344
  drop_tags ,
3345
+ sz ,
3279
3346
  ) :
3280
3347
  """forgets file in db, fixes symlinks, does not delete"""
3281
3348
  srd, sfn = vsplit(vrem)
@@ -3290,7 +3357,7 @@ class Up2k(object):
3290
3357
  q = "delete from mt where w=?"
3291
3358
  cur.execute(q, (wark[:16],))
3292
3359
 
3293
- self.db_rm(cur, srd, sfn)
3360
+ self.db_rm(cur, srd, sfn, sz)
3294
3361
 
3295
3362
  reg = self.registry.get(ptop)
3296
3363
  if reg: