dfs-sync 1.0.2__tar.gz → 1.0.4__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.4}/PKG-INFO +1 -1
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/__init__.py +1 -1
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/core/transfer_manager.py +8 -6
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/utils/path_handler.py +51 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/utils/progress_display.py +17 -8
- {dfs_sync-1.0.2 → dfs_sync-1.0.4/dfs_sync.egg-info}/PKG-INFO +1 -1
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/LICENSE +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/MANIFEST.in +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/README.md +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/cli/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/cli/main.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/core/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/core/config.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/core/logger.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/protocols/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/protocols/base.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/protocols/sftp_client.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/utils/__init__.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/utils/file_filter.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs/utils/file_info_cache.py +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs_sync.egg-info/SOURCES.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs_sync.egg-info/dependency_links.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs_sync.egg-info/entry_points.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs_sync.egg-info/requires.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/dfs_sync.egg-info/top_level.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/pyproject.toml +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/requirements.txt +0 -0
- {dfs_sync-1.0.2 → dfs_sync-1.0.4}/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
|
获取路径类型
|
|
@@ -9,13 +9,22 @@
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import os
|
|
12
|
-
import sys
|
|
13
12
|
import time
|
|
14
13
|
import threading
|
|
15
14
|
from typing import Dict, Optional
|
|
16
15
|
from ..protocols.base import TransferProgress, TransferStatus
|
|
17
16
|
|
|
18
17
|
|
|
18
|
+
_ICONS = {
|
|
19
|
+
'upload': '↑ ',
|
|
20
|
+
'download': '↓ ',
|
|
21
|
+
'skip': '» ',
|
|
22
|
+
'ok': '✓ ',
|
|
23
|
+
'fail': '✗ ',
|
|
24
|
+
'other': '- ',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
19
28
|
class SimpleProgressDisplay:
|
|
20
29
|
"""简化的进度显示器"""
|
|
21
30
|
|
|
@@ -207,19 +216,19 @@ class SimpleProgressDisplay:
|
|
|
207
216
|
Returns:
|
|
208
217
|
格式化的进度行字符串
|
|
209
218
|
"""
|
|
210
|
-
#
|
|
219
|
+
# 状态图标
|
|
211
220
|
if progress.status == TransferStatus.UPLOADING:
|
|
212
|
-
icon =
|
|
221
|
+
icon = _ICONS['upload']
|
|
213
222
|
elif progress.status == TransferStatus.DOWNLOADING:
|
|
214
|
-
icon =
|
|
223
|
+
icon = _ICONS['download']
|
|
215
224
|
elif progress.status == TransferStatus.COMPLETED and progress.is_skipped:
|
|
216
|
-
icon =
|
|
225
|
+
icon = _ICONS['skip']
|
|
217
226
|
elif progress.status == TransferStatus.COMPLETED:
|
|
218
|
-
icon =
|
|
227
|
+
icon = _ICONS['ok']
|
|
219
228
|
elif progress.status == TransferStatus.FAILED:
|
|
220
|
-
icon =
|
|
229
|
+
icon = _ICONS['fail']
|
|
221
230
|
else:
|
|
222
|
-
icon =
|
|
231
|
+
icon = _ICONS['other']
|
|
223
232
|
|
|
224
233
|
# 分配任务序号(首次出现时);调用方已持有 _lock
|
|
225
234
|
file_path = progress.file_path
|
|
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
|