More work on file transmission

This commit is contained in:
Kovid Goyal 2021-11-11 20:05:54 +05:30
parent efbf156f82
commit a1ca607f35
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 40 additions and 25 deletions

View File

@ -238,16 +238,18 @@ class Manager:
try: try:
os.makedirs(f.expanded_local_path, exist_ok=True) os.makedirs(f.expanded_local_path, exist_ok=True)
except OSError as err: except OSError as err:
return str(err) return f'Failed to create directory with error: {err}'
elif f.ftype is FileType.link: elif f.ftype is FileType.link:
target = rid_map.get(f.remote_target) target = rid_map.get(f.remote_target)
if target is None: if target is None:
return f'Hard link with remote id: {f.remote_target} not found' return f'Hard link with remote id: {f.remote_target} not found'
try: try:
os.makedirs(os.path.dirname(f.expanded_local_path), exist_ok=True) os.makedirs(os.path.dirname(f.expanded_local_path), exist_ok=True)
with suppress(FileNotFoundError):
os.remove(f.expanded_local_path)
os.link(target.expanded_local_path, f.expanded_local_path) os.link(target.expanded_local_path, f.expanded_local_path)
except OSError as err: except OSError as err:
return str(err) return f'Failed to create hardlink with error: {err}'
elif f.ftype is FileType.symlink: elif f.ftype is FileType.symlink:
if f.remote_target: if f.remote_target:
target = rid_map.get(f.remote_target) target = rid_map.get(f.remote_target)
@ -258,7 +260,12 @@ class Manager:
lt = os.path.relpath(lt, os.path.dirname(f.expanded_local_path)) lt = os.path.relpath(lt, os.path.dirname(f.expanded_local_path))
else: else:
lt = f.remote_symlink_value.decode('utf-8') lt = f.remote_symlink_value.decode('utf-8')
os.symlink(lt, f.expanded_local_path) with suppress(FileNotFoundError):
os.remove(f.expanded_local_path)
try:
os.symlink(lt, f.expanded_local_path)
except OSError as err:
return f'Failed to create symlink with error: {err}'
with suppress(OSError): with suppress(OSError):
f.apply_metadata() f.apply_metadata()
return '' return ''
@ -274,9 +281,9 @@ class Manager:
def collect_files(self, cli_opts: TransferCLIOptions) -> None: def collect_files(self, cli_opts: TransferCLIOptions) -> None:
self.files = list(files_for_receive(cli_opts, self.dest, self.files, self.remote_home, self.spec)) self.files = list(files_for_receive(cli_opts, self.dest, self.files, self.remote_home, self.spec))
self.progress_tracker.total_size_of_all_files = sum(max(0, f.expected_size) for f in self.files) self.files_to_be_transferred = {f.file_id: f for f in self.files if f.ftype not in (FileType.directory, FileType.link)}
self.progress_tracker.total_size_of_all_files = sum(max(0, f.expected_size) for f in self.files_to_be_transferred.values())
self.progress_tracker.total_bytes_to_transfer = self.progress_tracker.total_size_of_all_files self.progress_tracker.total_bytes_to_transfer = self.progress_tracker.total_size_of_all_files
self.fid_map = {f.file_id: f for f in self.files}
def on_file_transfer_response(self, ftc: FileTransmissionCommand) -> str: def on_file_transfer_response(self, ftc: FileTransmissionCommand) -> str:
if self.state is State.waiting_for_permission: if self.state is State.waiting_for_permission:
@ -317,7 +324,7 @@ class Manager:
return f'Unexpected response from terminal: {ftc}' return f'Unexpected response from terminal: {ftc}'
elif self.state is State.transferring: elif self.state is State.transferring:
if ftc.action in (Action.data, Action.end_data): if ftc.action in (Action.data, Action.end_data):
f = self.fid_map.get(ftc.file_id) f = self.files_to_be_transferred.get(ftc.file_id)
if f is None: if f is None:
return f'Got data for unknown file id: {ftc.file_id}' return f'Got data for unknown file id: {ftc.file_id}'
is_last = ftc.action is Action.end_data is_last = ftc.action is Action.end_data
@ -327,8 +334,8 @@ class Manager:
return str(err) return str(err)
self.progress_tracker.file_written(f, amt_written, is_last) self.progress_tracker.file_written(f, amt_written, is_last)
if is_last: if is_last:
del self.fid_map[ftc.file_id] del self.files_to_be_transferred[ftc.file_id]
if not self.fid_map: if not self.files_to_be_transferred:
return self.finalize_transfer() return self.finalize_transfer()
return '' return ''

View File

@ -12,7 +12,7 @@ from dataclasses import Field, dataclass, field, fields
from enum import Enum, auto from enum import Enum, auto
from functools import partial from functools import partial
from gettext import gettext as _ from gettext import gettext as _
from itertools import chain from itertools import chain, count
from time import monotonic from time import monotonic
from typing import ( from typing import (
IO, Any, Callable, DefaultDict, Deque, Dict, Iterable, Iterator, List, IO, Any, Callable, DefaultDict, Deque, Dict, Iterable, Iterator, List,
@ -67,7 +67,11 @@ def split_for_transfer(
def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union['FileTransmissionCommand', 'TransmissionError']]: def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union['FileTransmissionCommand', 'TransmissionError']]:
file_map: DefaultDict[str, List[FileTransmissionCommand]] = defaultdict(list) file_map: DefaultDict[Tuple[int, int], List[FileTransmissionCommand]] = defaultdict(list)
counter = count()
def skey(sr: os.stat_result) -> Tuple[int, int]:
return sr.st_dev, sr.st_ino
def make_ftc(path: str, spec_id: str, sr: Optional[os.stat_result] = None, parent: str = '') -> FileTransmissionCommand: def make_ftc(path: str, spec_id: str, sr: Optional[os.stat_result] = None, parent: str = '') -> FileTransmissionCommand:
if sr is None: if sr is None:
@ -80,10 +84,12 @@ def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union[
ftype = FileType.regular ftype = FileType.regular
else: else:
raise ValueError('Not an appropriate file type') raise ValueError('Not an appropriate file type')
return FileTransmissionCommand( ans = FileTransmissionCommand(
action=Action.file, file_id=spec_id, mtime=sr.st_mtime_ns, permissions=stat.S_IMODE(sr.st_mode), action=Action.file, file_id=spec_id, mtime=sr.st_mtime_ns, permissions=stat.S_IMODE(sr.st_mode),
name=path, status=f'{sr.st_dev}:{sr.st_ino}', size=sr.st_size, ftype=ftype, parent=parent name=path, status=str(next(counter)), size=sr.st_size, ftype=ftype, parent=parent
) )
file_map[skey(sr)].append(ans)
return ans
for spec_id, spec in file_specs: for spec_id, spec in file_specs:
path = spec path = spec
@ -106,7 +112,6 @@ def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union[
except ValueError: except ValueError:
yield TransmissionError(file_id=spec_id, code='EINVAL', msg='Not a valid filetype') yield TransmissionError(file_id=spec_id, code='EINVAL', msg='Not a valid filetype')
continue continue
file_map[ftc.status].append(ftc)
if ftc.ftype is FileType.directory: if ftc.ftype is FileType.directory:
try: try:
x_ok = os.access(path, os.X_OK) x_ok = os.access(path, os.X_OK)
@ -117,15 +122,14 @@ def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union[
for dirpath, dirnames, filenames in di: for dirpath, dirnames, filenames in di:
for dname in chain(dirnames, filenames): for dname in chain(dirnames, filenames):
try: try:
dftc = make_ftc(os.path.join(dirpath, dname), spec_id, parent=ftc.status) make_ftc(os.path.join(dirpath, dname), spec_id, parent=ftc.status)
except (ValueError, OSError): except (ValueError, OSError):
continue continue
file_map[dftc.status].append(dftc)
for fkey, cmds in file_map.items(): def resolve_symlink(ftc: FileTransmissionCommand) -> FileTransmissionCommand:
base = cmds[0] if ftc.ftype is FileType.symlink:
if base.ftype is FileType.symlink:
try: try:
dest = os.path.realpath(base.name) dest = os.path.realpath(ftc.name)
except OSError: except OSError:
pass pass
else: else:
@ -134,13 +138,17 @@ def iter_file_metadata(file_specs: Iterable[Tuple[str, str]]) -> Iterator[Union[
except OSError: except OSError:
pass pass
else: else:
fkey = f'{s.st_dev}:{s.st_ino}' tgt = file_map.get(skey(s))
if fkey in file_map: if tgt is not None:
base.data = fkey.encode('utf-8', 'replace') ftc.data = tgt[0].status.encode('utf-8')
yield base return ftc
for fkey, cmds in file_map.items():
base = cmds[0]
yield resolve_symlink(base)
if len(cmds) > 1 and base.ftype is FileType.regular: if len(cmds) > 1 and base.ftype is FileType.regular:
for q in cmds[1:]: for q in cmds:
if q.ftype is FileType.regular: if q is not base and q.ftype is FileType.regular:
q.ftype = FileType.link q.ftype = FileType.link
q.data = base.status.encode('utf-8', 'replace') q.data = base.status.encode('utf-8', 'replace')
yield q yield q