More work on file transmission

This commit is contained in:
Kovid Goyal
2021-10-14 18:29:02 +05:30
parent a653233050
commit 706494016c
2 changed files with 63 additions and 11 deletions

View File

@@ -70,13 +70,13 @@ def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union[
def make_ftc(path: str, spec_id: str, sr: Optional[os.stat_result] = None) -> FileTransmissionCommand: def make_ftc(path: str, spec_id: str, sr: Optional[os.stat_result] = None) -> FileTransmissionCommand:
if sr is None: if sr is None:
sr = os.stat(path) sr = os.stat(path, follow_symlinks=False)
if stat.S_ISREG(sr.st_mode): if stat.S_ISLNK(sr.st_mode):
ftype = FileType.regular
elif stat.S_ISLNK(sr.st_mode):
ftype = FileType.symlink ftype = FileType.symlink
elif stat.S_ISDIR(sr.st_mode): elif stat.S_ISDIR(sr.st_mode):
ftype = FileType.directory ftype = FileType.directory
elif stat.S_ISREG(sr.st_mode):
ftype = FileType.regular
else: else:
raise ValueError('Not an appropriate file type') raise ValueError('Not an appropriate file type')
return FileTransmissionCommand( return FileTransmissionCommand(
@@ -91,7 +91,7 @@ def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union[
if not os.path.isabs(path): if not os.path.isabs(path):
path = abspath(path, use_home=True) path = abspath(path, use_home=True)
try: try:
sr = os.stat(path) sr = os.stat(path, follow_symlinks=False)
read_ok = os.access(path, os.R_OK) read_ok = os.access(path, os.R_OK)
except OSError as err: except OSError as err:
errname = errno.errorcode.get(err.errno, 'EFAIL') errname = errno.errorcode.get(err.errno, 'EFAIL')
@@ -129,7 +129,7 @@ def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union[
pass pass
else: else:
try: try:
s = os.stat(dest) s = os.stat(dest, follow_symlinks=False)
except OSError: except OSError:
pass pass
else: else:
@@ -533,7 +533,7 @@ class ActiveSend:
def __init__(self, request_id: str, quiet: int, bypass: str, num_of_args: int) -> None: def __init__(self, request_id: str, quiet: int, bypass: str, num_of_args: int) -> None:
self.id = request_id self.id = request_id
self.args_remaining = num_of_args self.expected_num_of_args = num_of_args
self.bypass_ok: Optional[bool] = None self.bypass_ok: Optional[bool] = None
self.accepted = False self.accepted = False
if bypass: if bypass:
@@ -547,7 +547,7 @@ class ActiveSend:
@property @property
def spec_complete(self) -> bool: def spec_complete(self) -> bool:
return self.args_remaining < 1 return self.expected_num_of_args <= len(self.file_specs)
def add_file_spec(self, cmd: FileTransmissionCommand) -> None: def add_file_spec(self, cmd: FileTransmissionCommand) -> None:
if len(self.file_specs) > 8192 or self.spec_complete: if len(self.file_specs) > 8192 or self.spec_complete:
@@ -559,7 +559,7 @@ class ActiveSend:
return monotonic() - self.last_activity_at > (60 * EXPIRE_TIME) return monotonic() - self.last_activity_at > (60 * EXPIRE_TIME)
def close(self) -> None: def close(self) -> None:
raise NotImplementedError('TODO: Implement this') pass # TODO: Implement this
class FileTransmission: class FileTransmission:

View File

@@ -86,9 +86,9 @@ class TestFileTransmission(BaseTest):
b = tuple(f(r) for r in b if r.get('status') != 'PROGRESS') b = tuple(f(r) for r in b if r.get('status') != 'PROGRESS')
self.ae(a, b) self.ae(a, b)
def assertResponses(self, ft, **kw): def assertResponses(self, ft, limit=1024, **kw):
self.responses.append(response(**kw)) self.responses.append(response(**kw))
self.cr(ft.test_responses, self.responses) self.cr(ft.test_responses[:limit], self.responses[:limit])
def assertPathEqual(self, a, b): def assertPathEqual(self, a, b):
a = os.path.abspath(os.path.realpath(a)) a = os.path.abspath(os.path.realpath(a))
@@ -132,6 +132,58 @@ class TestFileTransmission(BaseTest):
# test size of delta # test size of delta
patch(a_path, a_path, c_path, max_delta_len=256) patch(a_path, a_path, c_path, max_delta_len=256)
def test_file_get(self):
# send refusal
for quiet in (0, 1, 2):
ft = FileTransmission(allow=False)
ft.handle_serialized_command(serialized_cmd(action='receive', id='x', quiet=quiet))
self.cr(ft.test_responses, [] if quiet == 2 else [response(id='x', status='EPERM:User refused the transfer')])
self.assertFalse(ft.active_sends)
# reading metadata for specs
cwd = os.path.join(self.tdir, 'cwd')
home = os.path.join(self.tdir, 'home')
os.mkdir(cwd), os.mkdir(home)
with set_paths(cwd=cwd, home=home):
ft = FileTransmission()
self.responses = []
ft.handle_serialized_command(serialized_cmd(action='receive', size=1))
self.assertResponses(ft, status='OK')
ft.handle_serialized_command(serialized_cmd(action='file', file_id='missing', data=b'XXX'))
self.responses.append(response(status='ENOENT:Failed to read spec', file_id='missing'))
self.assertResponses(ft, status='OK')
ft = FileTransmission()
self.responses = []
ft.handle_serialized_command(serialized_cmd(action='receive', size=2))
self.assertResponses(ft, status='OK')
with open(os.path.join(home, 'a'), 'w') as f:
f.write('a')
os.mkdir(f.name + 'd')
with open(os.path.join(f.name + 'd', 'b'), 'w') as f2:
f2.write('bbb')
os.symlink(f.name, f.name + 'd/s')
os.link(f.name, f.name + 'd/h')
os.symlink('XXX', f.name + 'd/q')
ft.handle_serialized_command(serialized_cmd(action='file', file_id='a', data=b'a'))
ft.handle_serialized_command(serialized_cmd(action='file', file_id='b', data=b'ad'))
files = {r['name']: r for r in ft.test_responses if r['action'] == 'file'}
self.ae(len(files), 6)
q = files[f.name]
tgt = q['status'].encode('ascii')
self.ae(q['size'], 1), self.assertNotIn('ftype', q)
q = files[f.name + 'd']
self.ae(q['ftype'], 'directory')
q = files[f.name + 'd/b']
self.ae(q['size'], 3)
q = files[f.name + 'd/s']
self.ae(q['ftype'], 'symlink')
self.ae(q['data'], tgt)
q = files[f.name + 'd/h']
self.ae(q['ftype'], 'link')
self.ae(q['data'], tgt)
q = files[f.name + 'd/q']
self.ae(q['ftype'], 'symlink')
self.assertNotIn('data', q)
def test_file_put(self): def test_file_put(self):
# send refusal # send refusal
for quiet in (0, 1, 2): for quiet in (0, 1, 2):