Skip to content

Commit 0f0f8d9

Browse files
committed
support --shr with --xvol; closes #179
1 parent 1afbff7 commit 0f0f8d9

File tree

5 files changed

+269
-8
lines changed

5 files changed

+269
-8
lines changed

copyparty/authsrv.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ def __init__(
372372
self.shr_src: Optional[tuple[VFS, str]] = None # source vfs+rem of a share
373373
self.shr_files: set[str] = set() # filenames to include from shr_src
374374
self.shr_owner: str = "" # uname
375+
self.shr_all_aps: list[tuple[str, list[VFS]]] = []
375376
self.aread: dict[str, list[str]] = {}
376377
self.awrite: dict[str, list[str]] = {}
377378
self.amove: dict[str, list[str]] = {}
@@ -391,7 +392,7 @@ def __init__(
391392
self.dbpath = self.histpath
392393
self.all_vols = {vpath: self} # flattened recursive
393394
self.all_nodes = {vpath: self} # also jumpvols/shares
394-
self.all_aps = [(rp, self)]
395+
self.all_aps = [(rp, [self])]
395396
self.all_vps = [(vp, self)]
396397
else:
397398
self.histpath = self.dbpath = ""
@@ -415,7 +416,7 @@ def get_all_vols(
415416
self,
416417
vols: dict[str, "VFS"],
417418
nodes: dict[str, "VFS"],
418-
aps: list[tuple[str, "VFS"]],
419+
aps: list[tuple[str, list["VFS"]]],
419420
vps: list[tuple[str, "VFS"]],
420421
) -> None:
421422
nodes[self.vpath] = self
@@ -424,7 +425,11 @@ def get_all_vols(
424425
rp = self.realpath
425426
rp += "" if rp.endswith(os.sep) else os.sep
426427
vp = self.vpath + ("/" if self.vpath else "")
427-
aps.append((rp, self))
428+
hit = next((x[1] for x in aps if x[0] == rp), None)
429+
if hit:
430+
hit.append(self)
431+
else:
432+
aps.append((rp, [self]))
428433
vps.append((vp, self))
429434

430435
for v in self.nodes.values():
@@ -848,9 +853,11 @@ def chk_ap(self, ap: str, st: Optional[os.stat_result] = None) -> Optional["VFS"
848853
return None
849854

850855
if "xvol" in self.flags:
851-
for vap, vn in self.root.all_aps:
856+
all_aps = self.shr_all_aps or self.root.all_aps
857+
858+
for vap, vns in all_aps:
852859
if aps.startswith(vap):
853-
return vn
860+
return self if self in vns else vns[0]
854861

855862
if self.log:
856863
self.log("vfs", "xvol: %r" % (ap,), 3)
@@ -2554,6 +2561,28 @@ def _reload(self, verbosity: int = 9) -> None:
25542561
shn.shr_src = (s_vfs, s_rem)
25552562
shn.realpath = s_vfs.canonical(s_rem)
25562563

2564+
# root.all_aps doesn't include any shares, so make a copy where the
2565+
# share appears in all abspaths it can provide (for example for chk_ap)
2566+
ap = shn.realpath
2567+
if not ap.endswith(os.sep):
2568+
ap += os.sep
2569+
shn.shr_all_aps = [(x, y[:]) for x, y in vfs.all_aps]
2570+
exact = False
2571+
for ap2, vns in shn.shr_all_aps:
2572+
if ap == ap2:
2573+
exact = True
2574+
if ap2.startswith(ap):
2575+
try:
2576+
vp2 = vjoin(s_rem, ap2[len(ap) :])
2577+
vn2, _ = s_vfs.get(vp2, "*", False, False)
2578+
if vn2 == s_vfs or vn2.dbv == s_vfs:
2579+
vns.append(shn)
2580+
except:
2581+
pass
2582+
if not exact:
2583+
shn.shr_all_aps.append((ap, [shn]))
2584+
shn.shr_all_aps.sort(key=lambda x: len(x[0]), reverse=True)
2585+
25572586
if self.args.shr_v:
25582587
t = "mapped %s share [%s] by [%s] => [%s] => [%s]"
25592588
self.log(t % (s_pr, s_k, s_un, s_vp, shn.realpath))

copyparty/th_srv.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ def get(self, ptop: str, rem: str, mtime: float, fmt: str) -> Optional[str]:
284284
vn = next((x for x in allvols if x.realpath == ptop), None)
285285
if not vn:
286286
self.log("ptop %r not in %s" % (ptop, allvols), 3)
287-
vn = self.asrv.vfs.all_aps[0][1]
287+
vn = self.asrv.vfs.all_aps[0][1][0]
288288

289289
self.q.put((abspath, tpath, fmt, vn))
290290
self.log("conv %r :%s \033[0m%r" % (tpath, fmt, abspath), 6)

copyparty/util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2400,11 +2400,11 @@ def pathmod(
24002400

24012401
# try to map abspath to vpath
24022402
np = np.replace("/", os.sep)
2403-
for vn_ap, vn in vfs.all_aps:
2403+
for vn_ap, vns in vfs.all_aps:
24042404
if not np.startswith(vn_ap):
24052405
continue
24062406
zs = np[len(vn_ap) :].replace(os.sep, "/")
2407-
nvp = vjoin(vn.vpath, zs)
2407+
nvp = vjoin(vns[0].vpath, zs)
24082408
break
24092409

24102410
if nvp == "\n":

tests/test_shr.py

Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
#!/usr/bin/env python3
2+
# coding: utf-8
3+
from __future__ import print_function, unicode_literals
4+
5+
import json
6+
import os
7+
import shutil
8+
import sqlite3
9+
import tempfile
10+
import unittest
11+
12+
from copyparty.__init__ import ANYWIN
13+
from copyparty.authsrv import AuthSrv
14+
from copyparty.httpcli import HttpCli
15+
from copyparty.util import absreal
16+
from tests import util as tu
17+
from tests.util import Cfg
18+
19+
20+
class TestShr(unittest.TestCase):
21+
def log(self, src, msg, c=0):
22+
m = "%s" % (msg,)
23+
if (
24+
"warning: filesystem-path does not exist:" in m
25+
or "you are sharing a system directory:" in m
26+
or "symlink-based deduplication is enabled" in m
27+
or m.startswith("hint: argument")
28+
):
29+
return
30+
31+
print(("[%s] %s" % (src, msg)).encode("ascii", "replace").decode("ascii"))
32+
33+
def assertLD(self, url, auth, els, edl):
34+
ls = self.ls(url, auth)
35+
self.assertEqual(ls[0], len(els) == 2)
36+
if not ls[0]:
37+
return
38+
a = [list(sorted(els[0])), list(sorted(els[1]))]
39+
b = [list(sorted(ls[1])), list(sorted(ls[2]))]
40+
self.assertEqual(a, b)
41+
42+
if edl is None:
43+
edl = els[1]
44+
can_dl = []
45+
for fn in b[1]:
46+
if fn == "a.db":
47+
continue
48+
furl = url + "/" + fn
49+
if auth:
50+
furl += "?pw=p1"
51+
h, zb = self.curl(furl, True)
52+
if h.startswith("HTTP/1.1 200 "):
53+
can_dl.append(fn)
54+
self.assertEqual(edl, can_dl)
55+
56+
def setUp(self):
57+
self.td = tu.get_ramdisk()
58+
td = os.path.join(self.td, "vfs")
59+
os.mkdir(td)
60+
os.chdir(td)
61+
os.mkdir("d1")
62+
os.mkdir("d2")
63+
os.mkdir("d2/d3")
64+
for zs in ("d1/f1", "d2/f2", "d2/d3/f3"):
65+
with open(zs, "wb") as f:
66+
f.write(zs.encode("utf-8"))
67+
for dst in ("d1", "d2", "d2/d3"):
68+
src, fn = zs.rsplit("/", 1)
69+
os.symlink(absreal(zs), dst + "/l" + fn[-1:])
70+
71+
db = sqlite3.connect("a.db")
72+
with db:
73+
zs = r"create table sh (k text, pw text, vp text, pr text, st int, un text, t0 int, t1 int)"
74+
db.execute(zs)
75+
db.close()
76+
77+
def tearDown(self):
78+
os.chdir(tempfile.gettempdir())
79+
shutil.rmtree(self.td)
80+
81+
def cinit(self):
82+
self.asrv = AuthSrv(self.args, self.log)
83+
self.conn = tu.VHttpConn(self.args, self.asrv, self.log, b"", True)
84+
85+
def test1(self):
86+
self.args = Cfg(
87+
a=["u1:p1"],
88+
v=["::A,u1", "d1:v1:A,u1", "d2/d3:d2/d3:A,u1"],
89+
shr="/shr/",
90+
shr1="shr/",
91+
shr_db="a.db",
92+
shr_v=False,
93+
)
94+
self.cinit()
95+
96+
self.assertLD("", True, [["d1", "d2", "v1"], ["a.db"]], [])
97+
self.assertLD("d1", True, [[], ["f1", "l1", "l2", "l3"]], None)
98+
self.assertLD("v1", True, [[], ["f1", "l1", "l2", "l3"]], None)
99+
self.assertLD("d2", True, [["d3"], ["f2", "l1", "l2", "l3"]], None)
100+
self.assertLD("d2/d3", True, [[], ["f3", "l1", "l2", "l3"]], None)
101+
self.assertLD("d3", True, [], [])
102+
103+
jt = {
104+
"k": "r",
105+
"vp": ["/"],
106+
"pw": "",
107+
"exp": "99",
108+
"perms": ["read"],
109+
}
110+
print(self.post_json("?pw=p1&share", jt)[1])
111+
jt = {
112+
"k": "d2",
113+
"vp": ["/d2/"],
114+
"pw": "",
115+
"exp": "99",
116+
"perms": ["read"],
117+
}
118+
print(self.post_json("?pw=p1&share", jt)[1])
119+
self.conn.shutdown()
120+
self.cinit()
121+
122+
self.assertLD("", True, [["d1", "d2", "v1"], ["a.db"]], [])
123+
self.assertLD("d1", True, [[], ["f1", "l1", "l2", "l3"]], None)
124+
self.assertLD("v1", True, [[], ["f1", "l1", "l2", "l3"]], None)
125+
self.assertLD("d2", True, [["d3"], ["f2", "l1", "l2", "l3"]], None)
126+
self.assertLD("d2/d3", True, [[], ["f3", "l1", "l2", "l3"]], None)
127+
self.assertLD("d3", True, [], [])
128+
129+
self.assertLD("shr/d2", False, [[], ["f2", "l1", "l2", "l3"]], None)
130+
self.assertLD("shr/d2/d3", False, [], None)
131+
132+
self.assertLD("shr/r", False, [["d1"], ["a.db"]], [])
133+
self.assertLD("shr/r/d1", False, [[], ["f1", "l1", "l2", "l3"]], None)
134+
self.assertLD("shr/r/d2", False, [], None) # unfortunate
135+
self.assertLD("shr/r/d2/d3", False, [], None)
136+
137+
self.conn.shutdown()
138+
139+
def test2(self):
140+
self.args = Cfg(
141+
a=["u1:p1"],
142+
v=["::A,u1", "d1:v1:A,u1", "d2/d3:d2/d3:A,u1"],
143+
shr="/shr/",
144+
shr1="shr/",
145+
shr_db="a.db",
146+
shr_v=False,
147+
xvol=True,
148+
)
149+
self.cinit()
150+
151+
self.assertLD("", True, [["d1", "d2", "v1"], ["a.db"]], [])
152+
self.assertLD("d1", True, [[], ["f1", "l1", "l2", "l3"]], None)
153+
self.assertLD("v1", True, [[], ["f1", "l1", "l2", "l3"]], None)
154+
self.assertLD("d2", True, [["d3"], ["f2", "l1", "l2", "l3"]], None)
155+
self.assertLD("d2/d3", True, [[], ["f3", "l1", "l2", "l3"]], None)
156+
self.assertLD("d3", True, [], [])
157+
158+
jt = {
159+
"k": "r",
160+
"vp": ["/"],
161+
"pw": "",
162+
"exp": "99",
163+
"perms": ["read"],
164+
}
165+
print(self.post_json("?pw=p1&share", jt)[1])
166+
jt = {
167+
"k": "d2",
168+
"vp": ["/d2/"],
169+
"pw": "",
170+
"exp": "99",
171+
"perms": ["read"],
172+
}
173+
print(self.post_json("?pw=p1&share", jt)[1])
174+
self.conn.shutdown()
175+
self.cinit()
176+
177+
self.assertLD("", True, [["d1", "d2", "v1"], ["a.db"]], [])
178+
self.assertLD("d1", True, [[], ["f1", "l1", "l2", "l3"]], None)
179+
self.assertLD("v1", True, [[], ["f1", "l1", "l2", "l3"]], None)
180+
self.assertLD("d2", True, [["d3"], ["f2", "l1", "l2", "l3"]], None)
181+
self.assertLD("d2/d3", True, [[], ["f3", "l1", "l2", "l3"]], None)
182+
self.assertLD("d3", True, [], [])
183+
184+
self.assertLD("shr/d2", False, [[], ["f2", "l1", "l2", "l3"]], ["f2", "l2"])
185+
self.assertLD("shr/d2/d3", False, [], [])
186+
187+
self.assertLD("shr/r", False, [["d1"], ["a.db"]], [])
188+
self.assertLD(
189+
"shr/r/d1", False, [[], ["f1", "l1", "l2", "l3"]], ["f1", "l1", "l2"]
190+
)
191+
self.assertLD("shr/r/d2", False, [], []) # unfortunate
192+
self.assertLD("shr/r/d2/d3", False, [], [])
193+
194+
self.conn.shutdown()
195+
196+
def ls(self, url: str, auth: bool):
197+
zs = url + "?ls" + ("&pw=p1" if auth else "")
198+
h, b = self.curl(zs)
199+
if not h.startswith("HTTP/1.1 200 "):
200+
return (False, [], [])
201+
jo = json.loads(b)
202+
return (
203+
True,
204+
[x["href"].rstrip("/") for x in jo.get("dirs") or {}],
205+
[x["href"] for x in jo.get("files") or {}],
206+
)
207+
208+
def curl(self, url: str, binary=False):
209+
h = "GET /%s HTTP/1.1\r\nConnection: close\r\n\r\n"
210+
HttpCli(self.conn.setbuf((h % (url,)).encode("utf-8"))).run()
211+
if binary:
212+
h, b = self.conn.s._reply.split(b"\r\n\r\n", 1)
213+
return [h.decode("utf-8"), b]
214+
215+
return self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
216+
217+
def post_json(self, url: str, data):
218+
buf = json.dumps(data).encode("utf-8")
219+
msg = [
220+
"POST /%s HTTP/1.1" % (url,),
221+
"Connection: close",
222+
"Content-Type: application/json",
223+
"Content-Length: %d" % (len(buf),),
224+
"\r\n",
225+
]
226+
buf = "\r\n".join(msg).encode("utf-8") + buf
227+
print("PUT -->", buf)
228+
HttpCli(self.conn.setbuf(buf)).run()
229+
return self.conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)

tests/util.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,9 @@ def __init__(self, args, asrv, log):
260260
self.is_dut = True
261261
self.up2k = Up2k(self)
262262

263+
def reload(self, a, b):
264+
pass
265+
263266

264267
class VBrokerThr(BrokerThr):
265268
def __init__(self, hub):

0 commit comments

Comments
 (0)