copyparty 1.14.4__py3-none-any.whl → 1.15.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/__main__.py +14 -6
- copyparty/__version__.py +3 -3
- copyparty/authsrv.py +95 -8
- copyparty/broker_mp.py +4 -0
- copyparty/broker_mpw.py +4 -0
- copyparty/broker_thr.py +1 -0
- copyparty/cfg.py +9 -7
- copyparty/httpcli.py +41 -11
- copyparty/svchub.py +90 -39
- copyparty/up2k.py +156 -44
- copyparty/util.py +2 -0
- copyparty/web/a/u2c.py +109 -42
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.14.4.dist-info → copyparty-1.15.1.dist-info}/METADATA +39 -6
- {copyparty-1.14.4.dist-info → copyparty-1.15.1.dist-info}/RECORD +22 -22
- {copyparty-1.14.4.dist-info → copyparty-1.15.1.dist-info}/WHEEL +1 -1
- {copyparty-1.14.4.dist-info → copyparty-1.15.1.dist-info}/LICENSE +0 -0
- {copyparty-1.14.4.dist-info → copyparty-1.15.1.dist-info}/entry_points.txt +0 -0
- {copyparty-1.14.4.dist-info → copyparty-1.15.1.dist-info}/top_level.txt +0 -0
copyparty/svchub.py
CHANGED
@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
|
|
3
3
|
|
4
4
|
import argparse
|
5
5
|
import base64
|
6
|
-
import calendar
|
7
6
|
import errno
|
8
7
|
import gzip
|
9
8
|
import logging
|
@@ -16,7 +15,7 @@ import string
|
|
16
15
|
import sys
|
17
16
|
import threading
|
18
17
|
import time
|
19
|
-
from datetime import datetime
|
18
|
+
from datetime import datetime
|
20
19
|
|
21
20
|
# from inspect import currentframe
|
22
21
|
# print(currentframe().f_lineno)
|
@@ -98,6 +97,7 @@ class SvcHub(object):
|
|
98
97
|
self.argv = argv
|
99
98
|
self.E = args.E
|
100
99
|
self.no_ansi = args.no_ansi
|
100
|
+
self.tz = UTC if args.log_utc else None
|
101
101
|
self.logf = None
|
102
102
|
self.logf_base_fn = ""
|
103
103
|
self.is_dut = False # running in unittest; always False
|
@@ -112,7 +112,8 @@ class SvcHub(object):
|
|
112
112
|
self.httpsrv_up = 0
|
113
113
|
|
114
114
|
self.log_mutex = threading.Lock()
|
115
|
-
self.
|
115
|
+
self.cday = 0
|
116
|
+
self.cmon = 0
|
116
117
|
self.tstack = 0.0
|
117
118
|
|
118
119
|
self.iphash = HMaccas(os.path.join(self.E.cfg, "iphash"), 8)
|
@@ -214,6 +215,9 @@ class SvcHub(object):
|
|
214
215
|
noch.update([x for x in zsl if x])
|
215
216
|
args.chpw_no = noch
|
216
217
|
|
218
|
+
if not self.args.no_ses:
|
219
|
+
self.setup_session_db()
|
220
|
+
|
217
221
|
if args.shr:
|
218
222
|
self.setup_share_db()
|
219
223
|
|
@@ -362,6 +366,64 @@ class SvcHub(object):
|
|
362
366
|
|
363
367
|
self.broker = Broker(self)
|
364
368
|
|
369
|
+
def setup_session_db(self) :
|
370
|
+
if not HAVE_SQLITE3:
|
371
|
+
self.args.no_ses = True
|
372
|
+
t = "WARNING: sqlite3 not available; disabling sessions, will use plaintext passwords in cookies"
|
373
|
+
self.log("root", t, 3)
|
374
|
+
return
|
375
|
+
|
376
|
+
import sqlite3
|
377
|
+
|
378
|
+
create = True
|
379
|
+
db_path = self.args.ses_db
|
380
|
+
self.log("root", "opening sessions-db %s" % (db_path,))
|
381
|
+
for n in range(2):
|
382
|
+
try:
|
383
|
+
db = sqlite3.connect(db_path)
|
384
|
+
cur = db.cursor()
|
385
|
+
try:
|
386
|
+
cur.execute("select count(*) from us").fetchone()
|
387
|
+
create = False
|
388
|
+
break
|
389
|
+
except:
|
390
|
+
pass
|
391
|
+
except Exception as ex:
|
392
|
+
if n:
|
393
|
+
raise
|
394
|
+
t = "sessions-db corrupt; deleting and recreating: %r"
|
395
|
+
self.log("root", t % (ex,), 3)
|
396
|
+
try:
|
397
|
+
cur.close() # type: ignore
|
398
|
+
except:
|
399
|
+
pass
|
400
|
+
try:
|
401
|
+
db.close() # type: ignore
|
402
|
+
except:
|
403
|
+
pass
|
404
|
+
os.unlink(db_path)
|
405
|
+
|
406
|
+
sch = [
|
407
|
+
r"create table kv (k text, v int)",
|
408
|
+
r"create table us (un text, si text, t0 int)",
|
409
|
+
# username, session-id, creation-time
|
410
|
+
r"create index us_un on us(un)",
|
411
|
+
r"create index us_si on us(si)",
|
412
|
+
r"create index us_t0 on us(t0)",
|
413
|
+
r"insert into kv values ('sver', 1)",
|
414
|
+
]
|
415
|
+
|
416
|
+
assert db # type: ignore
|
417
|
+
assert cur # type: ignore
|
418
|
+
if create:
|
419
|
+
for cmd in sch:
|
420
|
+
cur.execute(cmd)
|
421
|
+
self.log("root", "created new sessions-db")
|
422
|
+
db.commit()
|
423
|
+
|
424
|
+
cur.close()
|
425
|
+
db.close()
|
426
|
+
|
365
427
|
def setup_share_db(self) :
|
366
428
|
al = self.args
|
367
429
|
if not HAVE_SQLITE3:
|
@@ -538,7 +600,7 @@ class SvcHub(object):
|
|
538
600
|
fng = []
|
539
601
|
t_ff = "transcode audio, create spectrograms, video thumbnails"
|
540
602
|
to_check = [
|
541
|
-
(HAVE_SQLITE3, "sqlite", "
|
603
|
+
(HAVE_SQLITE3, "sqlite", "sessions and file/media indexing"),
|
542
604
|
(HAVE_PIL, "pillow", "image thumbnails (plenty fast)"),
|
543
605
|
(HAVE_VIPS, "vips", "image thumbnails (faster, eats more ram)"),
|
544
606
|
(HAVE_WEBP, "pillow-webp", "create thumbnails as webp files"),
|
@@ -785,7 +847,7 @@ class SvcHub(object):
|
|
785
847
|
self.args.nc = min(self.args.nc, soft // 2)
|
786
848
|
|
787
849
|
def _logname(self) :
|
788
|
-
dt = datetime.now(
|
850
|
+
dt = datetime.now(self.tz)
|
789
851
|
fn = str(self.args.lo)
|
790
852
|
for fs in "YmdHMS":
|
791
853
|
fs = "%" + fs
|
@@ -938,6 +1000,11 @@ class SvcHub(object):
|
|
938
1000
|
|
939
1001
|
self._reload(rescan_all_vols=rescan_all_vols, up2k=up2k)
|
940
1002
|
|
1003
|
+
def _reload_sessions(self) :
|
1004
|
+
with self.asrv.mutex:
|
1005
|
+
self.asrv.load_sessions(True)
|
1006
|
+
self.broker.reload_sessions()
|
1007
|
+
|
941
1008
|
def stop_thr(self) :
|
942
1009
|
while not self.stop_req:
|
943
1010
|
with self.stop_cond:
|
@@ -1058,12 +1125,12 @@ class SvcHub(object):
|
|
1058
1125
|
return
|
1059
1126
|
|
1060
1127
|
with self.log_mutex:
|
1061
|
-
|
1128
|
+
dt = datetime.now(self.tz)
|
1062
1129
|
ts = self.log_dfmt % (
|
1063
|
-
|
1064
|
-
|
1065
|
-
(
|
1066
|
-
|
1130
|
+
dt.year,
|
1131
|
+
dt.month * 100 + dt.day,
|
1132
|
+
(dt.hour * 100 + dt.minute) * 100 + dt.second,
|
1133
|
+
dt.microsecond // self.log_div,
|
1067
1134
|
)
|
1068
1135
|
|
1069
1136
|
if c and not self.args.no_ansi:
|
@@ -1084,41 +1151,26 @@ class SvcHub(object):
|
|
1084
1151
|
if not self.args.no_logflush:
|
1085
1152
|
self.logf.flush()
|
1086
1153
|
|
1087
|
-
|
1088
|
-
|
1089
|
-
self._set_next_day()
|
1154
|
+
if dt.day != self.cday or dt.month != self.cmon:
|
1155
|
+
self._set_next_day(dt)
|
1090
1156
|
|
1091
|
-
def _set_next_day(self) :
|
1092
|
-
if self.
|
1157
|
+
def _set_next_day(self, dt ) :
|
1158
|
+
if self.cday and self.logf and self.logf_base_fn != self._logname():
|
1093
1159
|
self.logf.close()
|
1094
1160
|
self._setup_logfile("")
|
1095
1161
|
|
1096
|
-
|
1097
|
-
|
1098
|
-
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
1099
|
-
day_now = dt.day
|
1100
|
-
while dt.day == day_now:
|
1101
|
-
dt += timedelta(hours=12)
|
1102
|
-
|
1103
|
-
dt = dt.replace(hour=0, minute=0, second=0)
|
1104
|
-
try:
|
1105
|
-
tt = dt.utctimetuple()
|
1106
|
-
except:
|
1107
|
-
# still makes me hella uncomfortable
|
1108
|
-
tt = dt.timetuple()
|
1109
|
-
|
1110
|
-
self.next_day = calendar.timegm(tt)
|
1162
|
+
self.cday = dt.day
|
1163
|
+
self.cmon = dt.month
|
1111
1164
|
|
1112
1165
|
def _log_enabled(self, src , msg , c = 0) :
|
1113
1166
|
"""handles logging from all components"""
|
1114
1167
|
with self.log_mutex:
|
1115
|
-
|
1116
|
-
if
|
1117
|
-
dt = datetime.fromtimestamp(now, UTC)
|
1168
|
+
dt = datetime.now(self.tz)
|
1169
|
+
if dt.day != self.cday or dt.month != self.cmon:
|
1118
1170
|
zs = "{}\n" if self.no_ansi else "\033[36m{}\033[0m\n"
|
1119
1171
|
zs = zs.format(dt.strftime("%Y-%m-%d"))
|
1120
1172
|
print(zs, end="")
|
1121
|
-
self._set_next_day()
|
1173
|
+
self._set_next_day(dt)
|
1122
1174
|
if self.logf:
|
1123
1175
|
self.logf.write(zs)
|
1124
1176
|
|
@@ -1137,12 +1189,11 @@ class SvcHub(object):
|
|
1137
1189
|
else:
|
1138
1190
|
msg = "%s%s\033[0m" % (c, msg)
|
1139
1191
|
|
1140
|
-
zd = datetime.fromtimestamp(now, UTC)
|
1141
1192
|
ts = self.log_efmt % (
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
|
1193
|
+
dt.hour,
|
1194
|
+
dt.minute,
|
1195
|
+
dt.second,
|
1196
|
+
dt.microsecond // self.log_div,
|
1146
1197
|
)
|
1147
1198
|
msg = fmt % (ts, src, msg)
|
1148
1199
|
try:
|
copyparty/up2k.py
CHANGED
@@ -1459,7 +1459,7 @@ class Up2k(object):
|
|
1459
1459
|
self.log("file: {}".format(abspath))
|
1460
1460
|
|
1461
1461
|
try:
|
1462
|
-
hashes = self._hashlist_from_file(
|
1462
|
+
hashes, _ = self._hashlist_from_file(
|
1463
1463
|
abspath, "a{}, ".format(self.pp.n)
|
1464
1464
|
)
|
1465
1465
|
except Exception as ex:
|
@@ -1653,6 +1653,7 @@ class Up2k(object):
|
|
1653
1653
|
qex = " where " + qex
|
1654
1654
|
|
1655
1655
|
rewark = []
|
1656
|
+
f404 = []
|
1656
1657
|
|
1657
1658
|
with self.mutex:
|
1658
1659
|
b_left = 0
|
@@ -1669,7 +1670,8 @@ class Up2k(object):
|
|
1669
1670
|
if self.stop:
|
1670
1671
|
return -1
|
1671
1672
|
|
1672
|
-
|
1673
|
+
zs = zb[:-1].decode("utf-8").replace("\x00\x02", "\n")
|
1674
|
+
w, drd, dfn = zs.split("\x00\x01")
|
1673
1675
|
with self.mutex:
|
1674
1676
|
q = "select mt, sz from up where rd=? and fn=? and +w=?"
|
1675
1677
|
try:
|
@@ -1695,9 +1697,14 @@ class Up2k(object):
|
|
1695
1697
|
pf = "v{}, {:.0f}+".format(n_left, b_left / 1024 / 1024)
|
1696
1698
|
self.pp.msg = pf + abspath
|
1697
1699
|
|
1698
|
-
|
1699
|
-
|
1700
|
-
|
1700
|
+
try:
|
1701
|
+
stl = bos.lstat(abspath)
|
1702
|
+
st = bos.stat(abspath) if stat.S_ISLNK(stl.st_mode) else stl
|
1703
|
+
except Exception as ex:
|
1704
|
+
self.log("missing file: %s" % (abspath,), 3)
|
1705
|
+
f404.append((drd, dfn, w))
|
1706
|
+
continue
|
1707
|
+
|
1701
1708
|
mt2 = int(stl.st_mtime)
|
1702
1709
|
sz2 = st.st_size
|
1703
1710
|
|
@@ -1708,7 +1715,7 @@ class Up2k(object):
|
|
1708
1715
|
self.log("file: {}".format(abspath))
|
1709
1716
|
|
1710
1717
|
try:
|
1711
|
-
hashes = self._hashlist_from_file(abspath, pf)
|
1718
|
+
hashes, _ = self._hashlist_from_file(abspath, pf)
|
1712
1719
|
except Exception as ex:
|
1713
1720
|
self.log("hash: {} @ [{}]".format(repr(ex), abspath))
|
1714
1721
|
continue
|
@@ -1734,12 +1741,15 @@ class Up2k(object):
|
|
1734
1741
|
t = t.format(abspath, w, sz, mt, w2, sz2, mt2)
|
1735
1742
|
self.log(t, 1)
|
1736
1743
|
|
1737
|
-
if e2vp and rewark:
|
1744
|
+
if e2vp and (rewark or f404):
|
1738
1745
|
self.hub.retcode = 1
|
1739
1746
|
Daemon(self.hub.sigterm)
|
1740
|
-
|
1747
|
+
t = "in volume /%s: %s files missing, %s files have incorrect hashes"
|
1748
|
+
t = t % (vol.vpath, len(f404), len(rewark))
|
1749
|
+
self.log(t, 1)
|
1750
|
+
raise Exception(t)
|
1741
1751
|
|
1742
|
-
if not e2vu or not rewark:
|
1752
|
+
if not e2vu or (not rewark and not f404):
|
1743
1753
|
return 0
|
1744
1754
|
|
1745
1755
|
with self.mutex:
|
@@ -1747,9 +1757,13 @@ class Up2k(object):
|
|
1747
1757
|
q = "update up set w = ?, sz = ?, mt = ? where rd = ? and fn = ? limit 1"
|
1748
1758
|
cur.execute(q, (w, sz, int(mt), rd, fn))
|
1749
1759
|
|
1760
|
+
for _, _, w in f404:
|
1761
|
+
q = "delete from up where w = ? limit 1"
|
1762
|
+
cur.execute(q, (w,))
|
1763
|
+
|
1750
1764
|
cur.connection.commit()
|
1751
1765
|
|
1752
|
-
return len(rewark)
|
1766
|
+
return len(rewark) + len(f404)
|
1753
1767
|
|
1754
1768
|
def _build_tags_index(self, vol ) :
|
1755
1769
|
ptop = vol.realpath
|
@@ -1964,7 +1978,8 @@ class Up2k(object):
|
|
1964
1978
|
if c2.execute(q, (row[0][:16],)).fetchone():
|
1965
1979
|
continue
|
1966
1980
|
|
1967
|
-
|
1981
|
+
zs = "\x00\x01".join(row).replace("\n", "\x00\x02")
|
1982
|
+
gf.write((zs + "\n").encode("utf-8"))
|
1968
1983
|
n += 1
|
1969
1984
|
|
1970
1985
|
c2.close()
|
@@ -2663,10 +2678,13 @@ class Up2k(object):
|
|
2663
2678
|
jcur = self.cur.get(ptop)
|
2664
2679
|
reg = self.registry[ptop]
|
2665
2680
|
vfs = self.asrv.vfs.all_vols[cj["vtop"]]
|
2666
|
-
n4g = vfs.flags.get("noforget")
|
2681
|
+
n4g = bool(vfs.flags.get("noforget"))
|
2667
2682
|
rand = vfs.flags.get("rand") or cj.get("rand")
|
2668
2683
|
lost = []
|
2669
2684
|
|
2685
|
+
safe_dedup = vfs.flags.get("safededup") or 50
|
2686
|
+
data_ok = safe_dedup < 10 or n4g
|
2687
|
+
|
2670
2688
|
vols = [(ptop, jcur)] if jcur else []
|
2671
2689
|
if vfs.flags.get("xlink"):
|
2672
2690
|
vols += [(k, v) for k, v in self.cur.items() if k != ptop]
|
@@ -2674,7 +2692,7 @@ class Up2k(object):
|
|
2674
2692
|
# force upload time rather than last-modified
|
2675
2693
|
cj["lmod"] = int(time.time())
|
2676
2694
|
|
2677
|
-
alts
|
2695
|
+
alts = []
|
2678
2696
|
for ptop, cur in vols:
|
2679
2697
|
allv = self.asrv.vfs.all_vols
|
2680
2698
|
cvfs = next((v for v in allv.values() if v.realpath == ptop), vfs)
|
@@ -2704,13 +2722,12 @@ class Up2k(object):
|
|
2704
2722
|
wark, st.st_size, dsize, st.st_mtime, dtime, dp_abs
|
2705
2723
|
)
|
2706
2724
|
self.log(t)
|
2707
|
-
raise Exception(
|
2725
|
+
raise Exception()
|
2708
2726
|
except Exception as ex:
|
2709
2727
|
if n4g:
|
2710
2728
|
st = os.stat_result((0, -1, -1, 0, 0, 0, 0, 0, 0, 0))
|
2711
2729
|
else:
|
2712
|
-
|
2713
|
-
lost.append((cur, dp_dir, dp_fn))
|
2730
|
+
lost.append((cur, dp_dir, dp_fn))
|
2714
2731
|
continue
|
2715
2732
|
|
2716
2733
|
j = {
|
@@ -2733,18 +2750,42 @@ class Up2k(object):
|
|
2733
2750
|
if k in cj:
|
2734
2751
|
j[k] = cj[k]
|
2735
2752
|
|
2753
|
+
# offset of 1st diff in vpaths
|
2754
|
+
zig = (
|
2755
|
+
n + 1
|
2756
|
+
for n, (c1, c2) in enumerate(
|
2757
|
+
zip(dp_dir + "\r", cj["prel"] + "\n")
|
2758
|
+
)
|
2759
|
+
if c1 != c2
|
2760
|
+
)
|
2736
2761
|
score = (
|
2737
|
-
(
|
2738
|
-
+ (
|
2762
|
+
(6969 if st.st_dev == dev else 0)
|
2763
|
+
+ (3210 if dp_dir == cj["prel"] else next(zig))
|
2739
2764
|
+ (1 if dp_fn == cj["name"] else 0)
|
2740
2765
|
)
|
2741
|
-
alts.append((score, -len(alts), j))
|
2742
|
-
|
2743
|
-
|
2744
|
-
|
2745
|
-
|
2746
|
-
|
2747
|
-
|
2766
|
+
alts.append((score, -len(alts), j, cur, dp_dir, dp_fn))
|
2767
|
+
|
2768
|
+
job = None
|
2769
|
+
inc_ap = djoin(cj["ptop"], cj["prel"], cj["name"])
|
2770
|
+
for dupe in sorted(alts, reverse=True):
|
2771
|
+
rj = dupe[2]
|
2772
|
+
orig_ap = djoin(rj["ptop"], rj["prel"], rj["name"])
|
2773
|
+
if data_ok or inc_ap == orig_ap:
|
2774
|
+
data_ok = True
|
2775
|
+
job = rj
|
2776
|
+
break
|
2777
|
+
else:
|
2778
|
+
self.log("asserting contents of %s" % (orig_ap,))
|
2779
|
+
dhashes, st = self._hashlist_from_file(orig_ap)
|
2780
|
+
dwark = up2k_wark_from_hashlist(self.salt, st.st_size, dhashes)
|
2781
|
+
if wark != dwark:
|
2782
|
+
t = "will not dedup (fs index desync): fs=%s, db=%s, file: %s"
|
2783
|
+
self.log(t % (dwark, wark, orig_ap))
|
2784
|
+
lost.append(dupe[3:])
|
2785
|
+
continue
|
2786
|
+
data_ok = True
|
2787
|
+
job = rj
|
2788
|
+
break
|
2748
2789
|
|
2749
2790
|
if job and wark in reg:
|
2750
2791
|
# self.log("pop " + wark + " " + job["name"] + " handle_json db", 4)
|
@@ -2753,7 +2794,7 @@ class Up2k(object):
|
|
2753
2794
|
if lost:
|
2754
2795
|
c2 = None
|
2755
2796
|
for cur, dp_dir, dp_fn in lost:
|
2756
|
-
t = "forgetting
|
2797
|
+
t = "forgetting desynced db entry: /{}"
|
2757
2798
|
self.log(t.format(vjoin(vjoin(vfs.vpath, dp_dir), dp_fn)))
|
2758
2799
|
self.db_rm(cur, dp_dir, dp_fn, cj["size"])
|
2759
2800
|
if c2 and c2 != cur:
|
@@ -2788,7 +2829,13 @@ class Up2k(object):
|
|
2788
2829
|
del reg[wark]
|
2789
2830
|
break
|
2790
2831
|
|
2791
|
-
|
2832
|
+
inc_ap = djoin(cj["ptop"], cj["prel"], cj["name"])
|
2833
|
+
orig_ap = djoin(rj["ptop"], rj["prel"], rj["name"])
|
2834
|
+
|
2835
|
+
if self.args.nw or n4g or not st:
|
2836
|
+
pass
|
2837
|
+
|
2838
|
+
elif st.st_size != rj["size"]:
|
2792
2839
|
t = "will not dedup (fs index desync): {}, size fs={} db={}, mtime fs={} db={}, file: {}"
|
2793
2840
|
t = t.format(
|
2794
2841
|
wark, st.st_size, rj["size"], st.st_mtime, rj["lmod"], path
|
@@ -2796,6 +2843,15 @@ class Up2k(object):
|
|
2796
2843
|
self.log(t)
|
2797
2844
|
del reg[wark]
|
2798
2845
|
|
2846
|
+
elif inc_ap != orig_ap and not data_ok and "done" in reg[wark]:
|
2847
|
+
self.log("asserting contents of %s" % (orig_ap,))
|
2848
|
+
dhashes, _ = self._hashlist_from_file(orig_ap)
|
2849
|
+
dwark = up2k_wark_from_hashlist(self.salt, st.st_size, dhashes)
|
2850
|
+
if wark != dwark:
|
2851
|
+
t = "will not dedup (fs index desync): fs=%s, idx=%s, file: %s"
|
2852
|
+
self.log(t % (dwark, wark, orig_ap))
|
2853
|
+
del reg[wark]
|
2854
|
+
|
2799
2855
|
if job or wark in reg:
|
2800
2856
|
job = job or reg[wark]
|
2801
2857
|
if (
|
@@ -3048,7 +3104,22 @@ class Up2k(object):
|
|
3048
3104
|
fp = djoin(fdir, fname)
|
3049
3105
|
if job.get("replace") and bos.path.exists(fp):
|
3050
3106
|
self.log("replacing existing file at {}".format(fp))
|
3051
|
-
|
3107
|
+
cur = None
|
3108
|
+
ptop = job["ptop"]
|
3109
|
+
vf = self.flags.get(ptop) or {}
|
3110
|
+
st = bos.stat(fp)
|
3111
|
+
try:
|
3112
|
+
vrel = vjoin(job["prel"], fname)
|
3113
|
+
xlink = bool(vf.get("xlink"))
|
3114
|
+
cur, wark, _, _, _, _ = self._find_from_vpath(ptop, vrel)
|
3115
|
+
self._forget_file(ptop, vrel, cur, wark, True, st.st_size, xlink)
|
3116
|
+
except Exception as ex:
|
3117
|
+
self.log("skipping replace-relink: %r" % (ex,))
|
3118
|
+
finally:
|
3119
|
+
if cur:
|
3120
|
+
cur.connection.commit()
|
3121
|
+
|
3122
|
+
wunlink(self.log, fp, vf)
|
3052
3123
|
|
3053
3124
|
if self.args.plain_ip:
|
3054
3125
|
dip = ip.replace(":", ".")
|
@@ -3067,17 +3138,25 @@ class Up2k(object):
|
|
3067
3138
|
verbose = True,
|
3068
3139
|
rm = False,
|
3069
3140
|
lmod = 0,
|
3141
|
+
fsrc = None,
|
3070
3142
|
) :
|
3143
|
+
if src == dst or (fsrc and fsrc == dst):
|
3144
|
+
t = "symlinking a file to itself?? orig(%s) fsrc(%s) link(%s)"
|
3145
|
+
raise Exception(t % (src, fsrc, dst))
|
3146
|
+
|
3071
3147
|
if verbose:
|
3072
|
-
|
3148
|
+
t = "linking dupe:\n point-to: {0}\n link-loc: {1}"
|
3149
|
+
if fsrc:
|
3150
|
+
t += "\n data-src: {2}"
|
3151
|
+
self.log(t.format(src, dst, fsrc))
|
3073
3152
|
|
3074
3153
|
if self.args.nw:
|
3075
3154
|
return
|
3076
3155
|
|
3077
3156
|
linked = False
|
3078
3157
|
try:
|
3079
|
-
if "
|
3080
|
-
raise Exception("disabled in config")
|
3158
|
+
if not flags.get("dedup"):
|
3159
|
+
raise Exception("dedup is disabled in config")
|
3081
3160
|
|
3082
3161
|
lsrc = src
|
3083
3162
|
ldst = dst
|
@@ -3114,7 +3193,7 @@ class Up2k(object):
|
|
3114
3193
|
linked = True
|
3115
3194
|
except Exception as ex:
|
3116
3195
|
self.log("cannot hardlink: " + repr(ex))
|
3117
|
-
if "
|
3196
|
+
if "hardlinkonly" in flags:
|
3118
3197
|
raise Exception("symlink-fallback disabled in cfg")
|
3119
3198
|
|
3120
3199
|
if not linked:
|
@@ -3133,7 +3212,15 @@ class Up2k(object):
|
|
3133
3212
|
linked = True
|
3134
3213
|
except Exception as ex:
|
3135
3214
|
self.log("cannot link; creating copy: " + repr(ex))
|
3136
|
-
|
3215
|
+
if bos.path.isfile(src):
|
3216
|
+
csrc = src
|
3217
|
+
elif fsrc and bos.path.isfile(fsrc):
|
3218
|
+
csrc = fsrc
|
3219
|
+
else:
|
3220
|
+
t = "BUG: no valid sources to link from! orig(%s) fsrc(%s) link(%s)"
|
3221
|
+
self.log(t, 1)
|
3222
|
+
raise Exception(t % (src, fsrc, dst))
|
3223
|
+
shutil.copy2(fsenc(csrc), fsenc(dst))
|
3137
3224
|
|
3138
3225
|
if lmod and (not linked or SYMTIME):
|
3139
3226
|
times = (int(time.time()), int(lmod))
|
@@ -3695,8 +3782,11 @@ class Up2k(object):
|
|
3695
3782
|
cur = None
|
3696
3783
|
try:
|
3697
3784
|
ptop = dbv.realpath
|
3785
|
+
xlink = bool(dbv.flags.get("xlink"))
|
3698
3786
|
cur, wark, _, _, _, _ = self._find_from_vpath(ptop, volpath)
|
3699
|
-
self._forget_file(
|
3787
|
+
self._forget_file(
|
3788
|
+
ptop, volpath, cur, wark, True, st.st_size, xlink
|
3789
|
+
)
|
3700
3790
|
finally:
|
3701
3791
|
if cur:
|
3702
3792
|
cur.connection.commit()
|
@@ -3920,13 +4010,15 @@ class Up2k(object):
|
|
3920
4010
|
if c2 and c2 != c1:
|
3921
4011
|
self._copy_tags(c1, c2, w)
|
3922
4012
|
|
4013
|
+
xlink = bool(svn.flags.get("xlink"))
|
4014
|
+
|
3923
4015
|
with self.reg_mutex:
|
3924
4016
|
has_dupes = self._forget_file(
|
3925
|
-
svn.realpath, srem, c1, w, is_xvol, fsize_ or fsize
|
4017
|
+
svn.realpath, srem, c1, w, is_xvol, fsize_ or fsize, xlink
|
3926
4018
|
)
|
3927
4019
|
|
3928
4020
|
if not is_xvol:
|
3929
|
-
has_dupes = self._relink(w, svn.realpath, srem, dabs)
|
4021
|
+
has_dupes = self._relink(w, svn.realpath, srem, dabs, c1, xlink)
|
3930
4022
|
|
3931
4023
|
curs.add(c1)
|
3932
4024
|
|
@@ -4069,6 +4161,7 @@ class Up2k(object):
|
|
4069
4161
|
wark ,
|
4070
4162
|
drop_tags ,
|
4071
4163
|
sz ,
|
4164
|
+
xlink ,
|
4072
4165
|
) :
|
4073
4166
|
"""
|
4074
4167
|
mutex(main,reg) me
|
@@ -4080,7 +4173,7 @@ class Up2k(object):
|
|
4080
4173
|
if wark and cur:
|
4081
4174
|
self.log("found {} in db".format(wark))
|
4082
4175
|
if drop_tags:
|
4083
|
-
if self._relink(wark, ptop, vrem, ""):
|
4176
|
+
if self._relink(wark, ptop, vrem, "", cur, xlink):
|
4084
4177
|
has_dupes = True
|
4085
4178
|
drop_tags = False
|
4086
4179
|
|
@@ -4112,7 +4205,15 @@ class Up2k(object):
|
|
4112
4205
|
|
4113
4206
|
return has_dupes
|
4114
4207
|
|
4115
|
-
def _relink(
|
4208
|
+
def _relink(
|
4209
|
+
self,
|
4210
|
+
wark ,
|
4211
|
+
sptop ,
|
4212
|
+
srem ,
|
4213
|
+
dabs ,
|
4214
|
+
vcur ,
|
4215
|
+
xlink ,
|
4216
|
+
) :
|
4116
4217
|
"""
|
4117
4218
|
update symlinks from file at svn/srem to dabs (rename),
|
4118
4219
|
or to first remaining full if no dabs (delete)
|
@@ -4128,6 +4229,8 @@ class Up2k(object):
|
|
4128
4229
|
argv = (wark[:16], wark)
|
4129
4230
|
|
4130
4231
|
for ptop, cur in self.cur.items():
|
4232
|
+
if not xlink and cur and cur != vcur:
|
4233
|
+
continue
|
4131
4234
|
for rd, fn in cur.execute(q, argv):
|
4132
4235
|
if rd.startswith("//") or fn.startswith("//"):
|
4133
4236
|
rd, fn = s3dec(rd, fn)
|
@@ -4214,7 +4317,13 @@ class Up2k(object):
|
|
4214
4317
|
except:
|
4215
4318
|
pass
|
4216
4319
|
|
4217
|
-
|
4320
|
+
# this creates a link pointing from dabs to alink; alink may
|
4321
|
+
# not exist yet, which becomes problematic if the symlinking
|
4322
|
+
# fails and it has to fall back on hardlinking/copying files
|
4323
|
+
# (for example a volume with symlinked dupes but no --dedup);
|
4324
|
+
# fsrc=sabs is then a source that currently resolves to copy
|
4325
|
+
|
4326
|
+
self._symlink(dabs, alink, flags, False, lmod=lmod or 0, fsrc=sabs)
|
4218
4327
|
|
4219
4328
|
return len(full) + len(links)
|
4220
4329
|
|
@@ -4243,8 +4352,11 @@ class Up2k(object):
|
|
4243
4352
|
|
4244
4353
|
return wark
|
4245
4354
|
|
4246
|
-
def _hashlist_from_file(
|
4247
|
-
|
4355
|
+
def _hashlist_from_file(
|
4356
|
+
self, path , prefix = ""
|
4357
|
+
) :
|
4358
|
+
st = bos.stat(path)
|
4359
|
+
fsz = st.st_size
|
4248
4360
|
csz = up2k_chunksize(fsz)
|
4249
4361
|
ret = []
|
4250
4362
|
suffix = " MB, {}".format(path)
|
@@ -4257,7 +4369,7 @@ class Up2k(object):
|
|
4257
4369
|
while fsz > 0:
|
4258
4370
|
# same as `hash_at` except for `imutex` / bufsz
|
4259
4371
|
if self.stop:
|
4260
|
-
return []
|
4372
|
+
return [], st
|
4261
4373
|
|
4262
4374
|
if self.pp:
|
4263
4375
|
mb = fsz // (1024 * 1024)
|
@@ -4278,7 +4390,7 @@ class Up2k(object):
|
|
4278
4390
|
digest = base64.urlsafe_b64encode(digest)
|
4279
4391
|
ret.append(digest.decode("utf-8"))
|
4280
4392
|
|
4281
|
-
return ret
|
4393
|
+
return ret, st
|
4282
4394
|
|
4283
4395
|
def _new_upload(self, job , vfs , depth ) :
|
4284
4396
|
pdir = djoin(job["ptop"], job["prel"])
|
@@ -4579,7 +4691,7 @@ class Up2k(object):
|
|
4579
4691
|
self.salt, inf.st_size, int(inf.st_mtime), rd, fn
|
4580
4692
|
)
|
4581
4693
|
else:
|
4582
|
-
hashes = self._hashlist_from_file(abspath)
|
4694
|
+
hashes, _ = self._hashlist_from_file(abspath)
|
4583
4695
|
if not hashes:
|
4584
4696
|
return False
|
4585
4697
|
|