dfs-sync 1.0.2__tar.gz → 1.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dfs_sync-1.0.2/dfs_sync.egg-info → dfs_sync-1.0.3}/PKG-INFO +1 -1
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/__init__.py +1 -1
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/core/transfer_manager.py +8 -6
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/utils/path_handler.py +51 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3/dfs_sync.egg-info}/PKG-INFO +1 -1
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/LICENSE +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/MANIFEST.in +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/README.md +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/cli/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/cli/main.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/core/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/core/config.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/core/logger.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/protocols/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/protocols/base.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/protocols/sftp_client.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/utils/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/utils/file_filter.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/utils/file_info_cache.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs/utils/progress_display.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs_sync.egg-info/SOURCES.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs_sync.egg-info/dependency_links.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs_sync.egg-info/entry_points.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs_sync.egg-info/requires.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/dfs_sync.egg-info/top_level.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/pyproject.toml +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/requirements.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.3}/setup.cfg +0 -0
|
@@ -365,9 +365,10 @@ class TransferManager:
|
|
|
365
365
|
if file_info.is_directory:
|
|
366
366
|
continue
|
|
367
367
|
|
|
368
|
-
#
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
# 计算本地文件路径(Windows 下自动净化非法字符)
|
|
369
|
+
local_file_path = self.path_handler.remote_to_local_path(
|
|
370
|
+
file_info.path, remote_path, local_path
|
|
371
|
+
)
|
|
371
372
|
|
|
372
373
|
# 应用文件过滤器
|
|
373
374
|
if self.file_filter.should_ignore(file_info.path, remote_path):
|
|
@@ -437,9 +438,10 @@ class TransferManager:
|
|
|
437
438
|
if not file_info or file_info.is_directory:
|
|
438
439
|
continue
|
|
439
440
|
|
|
440
|
-
#
|
|
441
|
-
|
|
442
|
-
|
|
441
|
+
# 计算本地文件路径(Windows 下自动净化非法字符)
|
|
442
|
+
local_file_path = self.path_handler.remote_to_local_path(
|
|
443
|
+
file_path, remote_path, local_path
|
|
444
|
+
)
|
|
443
445
|
|
|
444
446
|
# 直接创建任务(使用已获取的基础信息)
|
|
445
447
|
await self._add_file_task_with_basic_info(
|
|
@@ -411,6 +411,57 @@ class PathHandler:
|
|
|
411
411
|
"""
|
|
412
412
|
return os.path.expanduser(path)
|
|
413
413
|
|
|
414
|
+
@staticmethod
|
|
415
|
+
def sanitize_windows_filename(name: str) -> str:
|
|
416
|
+
"""
|
|
417
|
+
将文件名组件中 Windows 不支持的字符替换为百分号编码形式。
|
|
418
|
+
Windows 文件名非法字符(不含路径分隔符): : * ? " < > |
|
|
419
|
+
"""
|
|
420
|
+
replacements = {
|
|
421
|
+
':': '%3A',
|
|
422
|
+
'*': '%2A',
|
|
423
|
+
'?': '%3F',
|
|
424
|
+
'"': '%22',
|
|
425
|
+
'<': '%3C',
|
|
426
|
+
'>': '%3E',
|
|
427
|
+
'|': '%7C',
|
|
428
|
+
}
|
|
429
|
+
for char, encoded in replacements.items():
|
|
430
|
+
name = name.replace(char, encoded)
|
|
431
|
+
return name
|
|
432
|
+
|
|
433
|
+
def remote_to_local_path(self, remote_file_path: str, remote_base: str, local_base: str) -> str:
|
|
434
|
+
"""
|
|
435
|
+
将远程文件路径转换为本地路径,在 Windows 上自动净化非法字符。
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
remote_file_path: 远程文件完整路径(使用正斜杠)
|
|
439
|
+
remote_base: 远程基础目录路径
|
|
440
|
+
local_base: 本地基础目录路径
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
适合当前操作系统的本地文件路径
|
|
444
|
+
"""
|
|
445
|
+
remote_file_norm = remote_file_path.replace('\\', '/')
|
|
446
|
+
remote_base_norm = remote_base.replace('\\', '/')
|
|
447
|
+
if not remote_base_norm.endswith('/'):
|
|
448
|
+
remote_base_norm += '/'
|
|
449
|
+
|
|
450
|
+
if remote_file_norm.startswith(remote_base_norm):
|
|
451
|
+
rel_str = remote_file_norm[len(remote_base_norm):]
|
|
452
|
+
else:
|
|
453
|
+
rel_str = os.path.relpath(remote_file_path, remote_base).replace('\\', '/')
|
|
454
|
+
|
|
455
|
+
components = [c for c in rel_str.split('/') if c]
|
|
456
|
+
if not components:
|
|
457
|
+
return local_base
|
|
458
|
+
|
|
459
|
+
if platform.system() == 'Windows':
|
|
460
|
+
components = [self.sanitize_windows_filename(c) for c in components]
|
|
461
|
+
|
|
462
|
+
local_rel = os.path.join(*components)
|
|
463
|
+
return os.path.join(local_base, local_rel)
|
|
464
|
+
|
|
414
465
|
def get_path_type(self, path: str) -> str:
|
|
415
466
|
"""
|
|
416
467
|
获取路径类型
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|