@@ -95,6 +95,17 @@ class WebdavUtil {
9595 }
9696 }
9797
98+ // 公共方法:拼接 WebDAV 文件 URL,自动去重斜杠和 /dav 前缀
99+ static String buildWebdavFileUrl (String webdavUrl, String remotePath) {
100+ // 去掉 remotePath 开头所有 / 和 dav/
101+ String path = remotePath;
102+ while (path.startsWith ('/' )) path = path.substring (1 );
103+ while (path.startsWith ('dav/' )) path = path.substring (4 );
104+ // 保证 webdavUrl 结尾只有一个斜杠
105+ String base = webdavUrl.endsWith ('/' ) ? webdavUrl : webdavUrl + '/' ;
106+ return base + path;
107+ }
108+
98109 static Future <bool > downloadFile ({
99110 required String webdavUrl,
100111 required String username,
@@ -103,7 +114,9 @@ class WebdavUtil {
103114 required String localPath,
104115 }) async {
105116 try {
106- final uri = Uri .parse ('$webdavUrl $remotePath ' );
117+ // 使用公共方法拼接 URL
118+ final url = buildWebdavFileUrl (webdavUrl, remotePath);
119+ final uri = Uri .parse (url);
107120 final auth = base64Encode (utf8.encode ('$username :$password ' ));
108121 print ('=== WebDAV 文件下载请求调试信息 ===' );
109122 print ('请求方法: GET' );
@@ -186,20 +199,13 @@ class WebdavUtil {
186199 final uri = Uri .parse ('$webdavUrl $remotePath ' );
187200 final auth = base64Encode (utf8.encode ('$username :$password ' ));
188201 final bytes = await file.readAsBytes ();
189- print ('=== WebDAV 文件上传请求调试信息 ===' );
190- print ('请求方法: PUT' );
191- print ('请求 URL: $uri ' );
192202 print ('上传文件: ${file .path } -> $remotePath ' );
193203 final response = await http.put (uri, headers: {'Authorization' : 'Basic $auth ' }, body: bytes);
194- print ('响应状态码: ${response .statusCode }' );
195204 if (response.statusCode == 201 || response.statusCode == 200 || response.statusCode == 204 ) {
196- print ('上传成功: $remotePath ' );
197- print ('=== WebDAV 文件上传请求调试信息结束 ===' );
198205 return true ;
199206 } else {
200207 print ('上传失败: $remotePath , 状态码: ${response .statusCode }' );
201208 print ('响应内容: ${response .body }' );
202- print ('=== WebDAV 文件上传请求调试信息结束 ===' );
203209 return false ;
204210 }
205211 } catch (e, stackTrace) {
@@ -275,7 +281,7 @@ class WebdavUtil {
275281 // 依次创建远程目录(父目录优先)
276282 for (final relativePath in localDirsList) {
277283 if (relativePath.isEmpty) continue ;
278- final remotePath = (remoteDir.endsWith ('/' ) ? remoteDir : remoteDir + ' /' ) + relativePath.replaceFirst ('/' , '' );
284+ final remotePath = (remoteDir.endsWith ('/' ) ? remoteDir : '$ remoteDir /' ) + relativePath.replaceFirst ('/' , '' );
279285 print ('[增量目录同步] 创建远程目录: $remotePath ' );
280286 final created = await createDirectory (
281287 webdavUrl: webdavUrl,
@@ -294,7 +300,7 @@ class WebdavUtil {
294300 List <File > filesToUpload = [];
295301 for (final file in allEntities.whereType <File >()) {
296302 final relativePath = file.path.substring (localDir.length).replaceAll ('\\ ' , '/' );
297- final remotePath = (remoteDir.endsWith ('/' ) ? remoteDir : remoteDir + ' /' ) + relativePath.replaceFirst ('/' , '' );
303+ final remotePath = (remoteDir.endsWith ('/' ) ? remoteDir : '$ remoteDir /' ) + relativePath.replaceFirst ('/' , '' );
298304 // 获取本地文件修改时间
299305 final localModified = await file.lastModified ();
300306 // 获取远程文件修改时间
@@ -321,7 +327,7 @@ class WebdavUtil {
321327 current++ ;
322328 if (onProgress != null ) onProgress (current, total, entity.path);
323329 final relativePath = entity.path.substring (localDir.length).replaceAll ('\\ ' , '/' );
324- final remotePath = (remoteDir.endsWith ('/' ) ? remoteDir : remoteDir + ' /' ) + relativePath.replaceFirst ('/' , '' );
330+ final remotePath = (remoteDir.endsWith ('/' ) ? remoteDir : '$ remoteDir /' ) + relativePath.replaceFirst ('/' , '' );
325331 final uploadSuccess = await uploadFile (
326332 webdavUrl: webdavUrl,
327333 username: username,
@@ -352,35 +358,42 @@ class WebdavUtil {
352358 void Function (int current, int total, String filePath)? onProgress,
353359 }) async {
354360 try {
355- final uri = Uri .parse (webdavUrl + (remoteDir.endsWith ('/' ) ? remoteDir : '$remoteDir /' ));
361+ print ('=== WebDAV 增量下载目录调试信息 ===' );
362+ print ('请求方法: PROPFIND' );
363+ print ('远程目录: $remoteDir ' );
364+ print ('本地目录: $localDir ' );
365+ final uriStr = webdavUrl + (remoteDir.endsWith ('/' ) ? remoteDir : '$remoteDir /' );
366+ print ('实际请求 URL: $uriStr ' );
367+ final uri = Uri .parse (uriStr);
356368 final auth = base64Encode (utf8.encode ('$username :$password ' ));
357369
358370 final request = http.Request ('PROPFIND' , uri)
359- ..headers.addAll ({'Authorization' : 'Basic $auth ' , 'Content-Type' : 'application/xml' , 'Depth' : '1' })
360- ..body = '''<?xml version="1.0" encoding="utf-8" ?>
361- <D:propfind xmlns:D="DAV:">
362- <D:prop>
363- <D:displayname/>
364- <D:getcontentlength/>
365- <D:getcontenttype/>
366- <D:resourcetype/>
367- <D:getlastmodified/>
368- </D:prop>
369- </D:propfind>''' ;
371+ ..headers.addAll ({'Authorization' : 'Basic $auth ' , 'Content-Type' : 'application/xml' , 'Depth' : 'infinity' })
372+ ..body = '''<?xml version="1.0" encoding="utf-8" ?>\n <D:propfind xmlns:D="DAV:">\n <D:prop>\n <D:displayname/>\n <D:getcontentlength/>\n <D:getcontenttype/>\n <D:resourcetype/>\n <D:getlastmodified/>\n </D:prop>\n </D:propfind>''' ;
370373
374+ print ('发送 PROPFIND 请求...' );
371375 final response = await http.Client ().send (request);
376+ print ('响应状态码: ${response .statusCode }' );
372377 if (response.statusCode != 207 ) {
373378 print ('WebDAV PROPFIND 请求失败: ${response .statusCode }' );
374379 return false ;
375380 }
376381
377382 final responseBody = await response.stream.bytesToString ();
383+ print ('响应体长度: ${responseBody .length } 字符' );
384+ print ('响应体内容(前500字符): ${responseBody .substring (0 , responseBody .length > 500 ? 500 : responseBody .length )}' );
385+ print ('=== WebDAV PROPFIND 请求调试信息结束 ===' );
378386 final files = _parseWebdavResponseWithModified (responseBody, remoteDir);
387+ print ('webdav解析到 ${files .length } 个文件/目录:' );
388+ for (final file in files) {
389+ print (' ${file ['isFile' ] ? '文件' : '目录' }: ${file ['name' ]} (路径: ${file ['path' ]})' );
390+ }
379391
380392 // 确保本地目录存在
381393 final localDirectory = Directory (localDir);
382394 if (! await localDirectory.exists ()) {
383395 await localDirectory.create (recursive: true );
396+ print ('本地目录不存在,已创建: $localDir ' );
384397 }
385398
386399 // 筛选需要下载的文件
@@ -391,19 +404,17 @@ class WebdavUtil {
391404 final localFile = File (localPath);
392405
393406 if (! await localFile.exists ()) {
394- // 本地文件不存在,需要下载
395407 filesToDownload.add (file);
396- print ('需要下载: ${file ['name' ]} (本地文件不存在)' );
408+ print ('[增量下载] 需要下载: ${file ['name' ]} (本地文件不存在)' );
397409 } else {
398- // 比较修改时间
399410 final localModified = await localFile.lastModified ();
400411 final remoteModified = file['lastModified' ] as DateTime ? ;
401412
402413 if (remoteModified != null && remoteModified.isAfter (localModified)) {
403414 filesToDownload.add (file);
404- print ('需要下载: ${file ['name' ]} (远程文件更新)' );
415+ print ('[增量下载] 需要下载: ${file ['name' ]} (远程文件更新, 本地: $ localModified , 远程: $ remoteModified )' );
405416 } else {
406- print ('跳过下载: ${file ['name' ]} (本地文件是最新的)' );
417+ print ('[增量下载] 跳过下载: ${file ['name' ]} (本地文件是最新的, 本地: $ localModified , 远程: $ remoteModified )' );
407418 }
408419 }
409420 }
@@ -415,6 +426,7 @@ class WebdavUtil {
415426
416427 for (final file in filesToDownload) {
417428 current++ ;
429+ print ('[增量下载] 开始下载: ${file ['name' ]} (路径: ${file ['path' ]})' );
418430 if (onProgress != null ) onProgress (current, total, file['path' ]);
419431 final success = await downloadFile (
420432 webdavUrl: webdavUrl,
@@ -424,8 +436,10 @@ class WebdavUtil {
424436 localPath: '$localDir /${file ['name' ]}' ,
425437 );
426438 if (! success) {
427- print ('下载文件失败: ${file ['path' ]}' );
439+ print ('[增量下载] 下载文件失败: ${file ['path' ]}' );
428440 return false ;
441+ } else {
442+ print ('[增量下载] 下载文件成功: ${file ['path' ]}' );
429443 }
430444 }
431445
@@ -446,7 +460,7 @@ class WebdavUtil {
446460 try {
447461 final uri = Uri .parse ('$webdavUrl $remotePath ' );
448462 final auth = base64Encode (utf8.encode ('$username :$password ' ));
449- final response = await http.Request ('MKCOL' , uri)
463+ final response = http.Request ('MKCOL' , uri)
450464 ..headers.addAll ({'Authorization' : 'Basic $auth ' });
451465 final streamedResponse = await http.Client ().send (response);
452466 // MKCOL: 201 Created 或 405 Method Not Allowed(已存在)视为成功
@@ -484,7 +498,7 @@ class WebdavUtil {
484498 // 去除域名和 remoteDir 前缀
485499 final uri = Uri .parse (href);
486500 return uri.path;
487- }).where ((path) => path != '/' && path != remoteDir && path != (remoteDir.endsWith ('/' ) ? remoteDir : remoteDir + ' /' )).toList ();
501+ }).where ((path) => path != '/' && path != remoteDir && path != (remoteDir.endsWith ('/' ) ? remoteDir : '$ remoteDir /' )).toList ();
488502 return dirs;
489503 }
490504
@@ -501,37 +515,34 @@ class WebdavUtil {
501515 return response.statusCode == 204 || response.statusCode == 200 || response.statusCode == 404 ;
502516 }
503517
504- // 解析 WebDAV PROPFIND 响应,包含修改时间
518+ // 解析 WebDAV PROPFIND 响应,包含修改时间(支持大小写标签)
505519 static List <Map <String , dynamic >> _parseWebdavResponseWithModified (String xmlResponse, String baseDir) {
520+ // 兼容 d: 和 D: 标签
521+ xmlResponse = xmlResponse.replaceAll ('<d:' , '<D:' ).replaceAll ('</d:' , '</D:' );
506522 final files = < Map <String , dynamic >> [];
507523 try {
508- final lines = xmlResponse.split ('\n ' );
509- String ? currentPath;
510- bool isCollection = false ;
511- DateTime ? lastModified;
512-
513- for (final line in lines) {
514- final trimmed = line.trim ();
515-
516- if (trimmed.contains ('<D:href>' ) && trimmed.contains ('</D:href>' )) {
517- final start = trimmed.indexOf ('<D:href>' ) + 8 ;
518- final end = trimmed.indexOf ('</D:href>' );
519- currentPath = trimmed.substring (start, end);
520- if (currentPath.startsWith ('/' )) {
521- currentPath = currentPath.substring (1 );
522- }
523- isCollection = false ;
524- lastModified = null ;
525- }
526-
527- if (trimmed.contains ('<D:collection/>' )) {
528- isCollection = true ;
529- }
530-
531- if (trimmed.contains ('<D:getlastmodified>' ) && trimmed.contains ('</D:getlastmodified>' )) {
532- final start = trimmed.indexOf ('<D:getlastmodified>' ) + 20 ;
533- final end = trimmed.indexOf ('</D:getlastmodified>' );
534- final dateStr = trimmed.substring (start, end);
524+ // 提取所有 <D:response>...</D:response>
525+ final responseMatches = RegExp (r'<D:response>([\s\S]*?)</D:response>' ).allMatches (xmlResponse);
526+ for (final match in responseMatches) {
527+ final response = match.group (1 )! ;
528+ // href
529+ final hrefMatch = RegExp (r'<D:href>(.*?)</D:href>' ).firstMatch (response);
530+ if (hrefMatch == null ) continue ;
531+ var currentPath = hrefMatch.group (1 )! ;
532+ if (currentPath.startsWith ('/' )) currentPath = currentPath.substring (1 );
533+ // 只过滤掉 baseDir 自身
534+ final baseDirPath = baseDir.replaceAll (RegExp (r'^/+|/+$' ), '' );
535+ if (currentPath == baseDirPath || currentPath == '$baseDirPath /' ) continue ;
536+ // 判断是否目录
537+ final isCollection = response.contains ('<D:collection/>' );
538+ // 文件名
539+ final fileName = currentPath.split ('/' ).last;
540+ if (fileName.isEmpty) continue ;
541+ // 修改时间
542+ DateTime ? lastModified;
543+ final lastModifiedMatch = RegExp (r'<D:getlastmodified>([^<]+)</D:getlastmodified>' ).firstMatch (response);
544+ if (lastModifiedMatch != null ) {
545+ final dateStr = lastModifiedMatch.group (1 )! ;
535546 try {
536547 lastModified = DateTime .parse (dateStr);
537548 } catch (e) {
@@ -542,23 +553,12 @@ class WebdavUtil {
542553 }
543554 }
544555 }
545-
546- if (trimmed.contains ('</D:response>' ) && currentPath != null ) {
547- if (currentPath != baseDir.replaceAll ('/' , '' ) &&
548- currentPath != baseDir.replaceFirst ('/' , '' ) &&
549- currentPath.isNotEmpty) {
550- final fileName = currentPath.split ('/' ).last;
551- if (fileName.isNotEmpty) {
552- files.add ({
553- 'name' : fileName,
554- 'path' : '/$currentPath ' ,
555- 'isFile' : ! isCollection,
556- 'lastModified' : lastModified,
557- });
558- }
559- }
560- currentPath = null ;
561- }
556+ files.add ({
557+ 'name' : fileName,
558+ 'path' : '/' + currentPath,
559+ 'isFile' : ! isCollection,
560+ 'lastModified' : lastModified,
561+ });
562562 }
563563 } catch (e) {
564564 print ('解析 WebDAV 响应异常: $e ' );
0 commit comments