|
1 | 1 | import base64
|
2 | 2 | import hashlib
|
3 | 3 | import os
|
4 |
| -import shlex |
5 | 4 | from logging import getLogger as get_logger
|
6 | 5 | from typing import IO, BinaryIO, Callable, Iterator, List, Optional, Tuple
|
7 | 6 |
|
@@ -226,9 +225,9 @@ def sftp2_download(
|
226 | 225 | if followlinks and src_path.is_symlink():
|
227 | 226 | src_path = src_path.readlink()
|
228 | 227 | if src_path.is_dir():
|
229 |
| - raise IsADirectoryError("Is a directory: %r" % src_url) |
| 228 | + raise IsADirectoryError(f"Is a directory: {src_url!r}") |
230 | 229 | if str(dst_url).endswith("/"):
|
231 |
| - raise IsADirectoryError("Is a directory: %r" % dst_url) |
| 230 | + raise IsADirectoryError(f"Is a directory: {dst_url!r}") |
232 | 231 |
|
233 | 232 | dst_path.parent.makedirs(exist_ok=True)
|
234 | 233 |
|
@@ -267,9 +266,9 @@ def sftp2_upload(
|
267 | 266 | if followlinks and os.path.islink(src_url):
|
268 | 267 | src_url = os.readlink(src_url)
|
269 | 268 | if os.path.isdir(src_url):
|
270 |
| - raise IsADirectoryError("Is a directory: %r" % src_url) |
| 269 | + raise IsADirectoryError(f"Is a directory: {src_url!r}") |
271 | 270 | if str(dst_url).endswith("/"):
|
272 |
| - raise IsADirectoryError("Is a directory: %r" % dst_url) |
| 271 | + raise IsADirectoryError(f"Is a directory: {dst_url!r}") |
273 | 272 |
|
274 | 273 | src_path = FSPath(src_url)
|
275 | 274 | if isinstance(dst_url, Sftp2Path):
|
@@ -327,28 +326,25 @@ def sftp2_concat(src_paths: List[PathLike], dst_path: PathLike) -> None:
|
327 | 326 |
|
328 | 327 | if all_same_backend and len(src_paths) > 1:
|
329 | 328 | # Use server-side cat command for efficiency
|
330 |
| - try: |
331 |
| - src_paths_quoted = [] |
332 |
| - for src_path in src_paths: |
333 |
| - src_obj = Sftp2Path(src_path) |
334 |
| - src_paths_quoted.append(shlex.quote(src_obj._real_path)) |
335 |
| - |
336 |
| - dst_quoted = shlex.quote(dst_path_obj._real_path) |
337 |
| - |
338 |
| - # Use server-side cat command to avoid data transfer |
339 |
| - cmd = f"cat {' '.join(src_paths_quoted)} > {dst_quoted}" |
340 |
| - exit_code, stdout, stderr = dst_path_obj._execute_command(cmd) |
341 |
| - |
342 |
| - if exit_code == 0: |
343 |
| - # Server-side concat successful |
344 |
| - return |
345 |
| - else: |
346 |
| - # Log the failure but fall back to SFTP method |
347 |
| - _logger.debug(f"Server-side concat failed (exit {exit_code}): {stderr}") |
348 |
| - |
349 |
| - except Exception as e: |
350 |
| - # Log the exception but fall back to SFTP method |
351 |
| - _logger.debug(f"Server-side concat failed with exception: {e}") |
| 329 | + def get_real_path(path: PathLike) -> str: |
| 330 | + return Sftp2Path(path)._real_path |
| 331 | + |
| 332 | + exec_result = dst_path_obj._exec_command( |
| 333 | + [ |
| 334 | + "cat", |
| 335 | + *map(get_real_path, src_paths), |
| 336 | + ">", |
| 337 | + get_real_path(dst_path), |
| 338 | + ] |
| 339 | + ) |
| 340 | + |
| 341 | + if exec_result.returncode != 0: |
| 342 | + # Log the failure but fall back to SFTP method |
| 343 | + _logger.error(exec_result.stderr) |
| 344 | + raise OSError( |
| 345 | + f"Failed to concat files, returncode: {exec_result.returncode}, " |
| 346 | + f"{exec_result.stderr}" |
| 347 | + ) |
352 | 348 |
|
353 | 349 | # Fallback to traditional SFTP concat (download then upload)
|
354 | 350 | with dst_path_obj.open("wb") as dst_file:
|
|
0 commit comments