diff --git a/src/python/jw/pkg/lib/util.py b/src/python/jw/pkg/lib/util.py index 1295e4c7..0a3cf7d0 100644 --- a/src/python/jw/pkg/lib/util.py +++ b/src/python/jw/pkg/lib/util.py @@ -177,8 +177,11 @@ async def copy( throw: bool = True, recursive: bool = False, ) -> Exception | str | list[str]: + + # Destination address / addresses + ret: str | list[str] if not isinstance(src_uri, str): - ret: list[str] = [] + ret = [] for uri in src_uri: # TODO: Group identical netlocs into one CopyContext rr = await copy(uri, dst, owner, group, mode, throw, recursive) if isinstance(rr, Exception): @@ -190,72 +193,64 @@ async def copy( else: raise Exception(f'copy() returned unexpected type {type(rr)}') return ret - from .CopyContext import CopyContext - async with CopyContext(src_uri, dst) as ctx: - - async def __mkdir_p(path: str) -> None: - # -- Create each level of the destination path that does not - # exist yet; put() does not create parent directories - cur = '' - for part in path.strip('/').split('/'): - cur = f'{cur}/{part}' - if not await ctx.dst.is_dir(cur): - await ctx.dst.mkdir(cur) - - async def __copy_tree(src_path: str, dst_path: str) -> list[str]: - ret: list[str] = [] - for entry in await ctx.src.readdir(src_path): - s_path = f'{src_path}/{entry}' - d_path = f'{dst_path}/{entry}' - if await ctx.src.is_dir(s_path): - await __mkdir_p(d_path) - ret.extend(await __copy_tree(s_path, d_path)) - else: - result = await ctx.src.get(s_path, throw = True) - parent = d_path.rpartition('/')[0] - if parent: - await __mkdir_p(parent) - await ctx.dst.put( - path = d_path, - content = result.stdout, - owner = owner, - group = group, - mode = mode, - throw = True, - ) - ret.append(d_path) - return ret - - try: - if recursive and await ctx.src.is_dir(ctx.src.root): - # -- A directory source is mirrored below the destination - # root: into it, if the root is an existing directory, - # as it, if it does not exist yet - dst_root = ctx.dst.root - if await ctx.dst.is_dir(dst_root): - dst_root += '/' + os.path.basename(src_uri) - await __mkdir_p(dst_root) - return await __copy_tree(ctx.src.root, dst_root) - result = await ctx.src.get(ctx.src.root, throw = True) - dst_path = ctx.dst.root - if await ctx.dst.is_dir(ctx.dst.root): - dst_path += '/' + os.path.basename(src_uri) + async def __copy_tree(src_path: str, dst_path: str) -> list[str]: + ret: list[str] = [] + s_st = await ctx.src.stat(src_path) + if dst_path and not await ctx.dst.is_dir(dst_path): + await ctx.dst.mkdir(dst_path) + for dirent in await ctx.src.readdir(src_path): + s_path = f'{src_path}/{dirent}' if src_path else dirent + d_path = f'{dst_path}/{dirent}' if dst_path else dirent + s_st = await ctx.src.stat(s_path) + if s_st.is_dir: + ret.extend(await __copy_tree(s_path, d_path)) + continue + result = await ctx.src.get(s_path, throw = True) await ctx.dst.put( - path = dst_path, + path = d_path, content = result.stdout, owner = owner, group = group, mode = mode, throw = True, ) - return dst_path - except Exception as e: - if throw: - raise - log(ERR, f'Failed to copy {src_uri} -> {dst} ({str(e)})') - return e - assert False, 'Unreachable code' + ret.append(d_path) + return ret + + try: + + from .CopyContext import CopyContext + + async with CopyContext(src_uri, dst) as ctx: + + ret = '' if ctx.dst.chroot else ctx.dst.root + if ret: + # Land the source's basename within existing destination directories + basename = os.path.basename(Uri(src_uri).path.rstrip('/')) + if basename and await ctx.dst.is_dir(ret): + ret += '/' + basename + src_root = '' if ctx.src.chroot else ctx.src.root + + if recursive and await ctx.src.is_dir(src_root): + return await __copy_tree(src_root, ret) + + result = await ctx.src.get(src_root, throw = True) + await ctx.dst.put( + path = ret, + content = result.stdout, + owner = owner, + group = group, + mode = mode, + throw = True, + ) + return ret + + except Exception as e: + if throw: + raise + log(ERR, f'Failed to copy {src_uri} -> {dst} ({str(e)})') + return e async def get_username( # export args: Namespace | None = None,