Răsfoiți Sursa

支持处理只读文件/文件夹

龙腾猫跃 4 luni în urmă
părinte
comite
c9e1b15740
2 a modificat fișierele cu 39 adăugiri și 10 ștergeri
  1. 10 4
      Shared/Utils/DirectoryUtils.cs
  2. 29 6
      Shared/Utils/FileUtils.cs

+ 10 - 4
Shared/Utils/DirectoryUtils.cs

@@ -78,7 +78,7 @@ public static class DirectoryUtils {
         } else {
             // 实际的复制
             Logger.Trace($"复制文件夹:{sourceFolder} → {destFolder}");
-            foreach (var file in DirectoryUtils.GetFiles(sourceFolder)) FileUtils.Copy(file, file.Replace(sourceFolder, destFolder));
+            foreach (var file in DirectoryUtils.GetFiles(sourceFolder).ToList()) FileUtils.Copy(file, file.Replace(sourceFolder, destFolder));
         }
     }
 
@@ -103,8 +103,10 @@ public static class DirectoryUtils {
             // 同一磁盘剪切,直接调用 Move(这只修改文件夹名,效率更高)
             Logger.Trace($"剪切文件夹到同一磁盘:{sourceFolder} → {destFolder}");
             DirectoryUtils.Delete(destFolder); // Move 要求此前不存在对应文件夹
-            ResilientUtils.RetryOn<IOException>(() 
-                => Directory.Move(PathUtils.ForApi(sourceFolder), PathUtils.ForApi(destFolder)));
+            ResilientUtils.RetryOn<IOException>(() => {
+                FileUtils.SetReadOnly(sourceFolder, false);
+                Directory.Move(PathUtils.ForApi(sourceFolder), PathUtils.ForApi(destFolder));
+            });
         } else {
             // 不同磁盘,必须先复制再删除,这就是我们傻逼微软
             Logger.Trace($"剪切文件夹到不同磁盘:{sourceFolder} → {destFolder}");
@@ -142,10 +144,14 @@ public static class DirectoryUtils {
                 folder = PathUtils.ForApi(folder);
                 foreach (string filePath in DirectoryUtils.GetFiles(folder, true)) FileUtils.Delete(filePath); // 删除文件
                 foreach (string str in DirectoryUtils.GetDirectories(folder, true)) DeleteInternal(str); // 递归删除子文件夹
-                ResilientUtils.RetryOn<IOException>(() => Directory.Delete(folder, true)); // 最终删除文件夹
+                ResilientUtils.RetryOn<IOException>(() => {
+                    FileUtils.SetReadOnly(folder, false);
+                    Directory.Delete(folder, true);
+                }); // 最终删除文件夹
             } catch (DirectoryNotFoundException ex) { // #4549,也可能已被其他线程删除
                 if (DirectoryUtils.Exists(folder)) {
                     Logger.Warn(ex, $"该文件夹可能为孤立的符号链接,尝试直接删除({folder})");
+                    FileUtils.SetReadOnly(folder, false);
                     Directory.Delete(folder);
                 } else {
                     throw;

+ 29 - 6
Shared/Utils/FileUtils.cs

@@ -42,7 +42,7 @@ public static class FileUtils {
     /// </summary>
     public static string? TryReadAsString(string filePath, Encoding? encoding = null, Type? type = null) {
         try {
-            if (type != null && !FileUtils.Exists(filePath)) return null;
+            if (type == null && !FileUtils.Exists(filePath)) return null;
             return FileUtils.ReadAsBytes(filePath, type).GetString(encoding);
         } catch {
             return null;
@@ -155,6 +155,7 @@ public static class FileUtils {
             // 实际的复制
             DirectoryUtils.Create(PathUtils.RemoveLastPart(destFilePath));
             Logger.Trace($"复制文件:{sourceFilePath} → {destFilePath}");
+            if (FileUtils.Exists(destFilePath)) FileUtils.SetReadOnly(destFilePath, false);
             ResilientUtils.RetryOn<IOException>(() 
                 => File.Copy(PathUtils.ForApi(sourceFilePath), PathUtils.ForApi(destFilePath), true));
         }
@@ -175,6 +176,8 @@ public static class FileUtils {
             Logger.Trace($"剪切文件到自身,但大小写不同:{sourceFilePath} → {destFilePath}");
             ResilientUtils.RetryOn<IOException>(() => {
                 var temp = Path.Combine(PathUtils.RemoveLastPart(sourceFilePath), Path.GetRandomFileName());
+                FileUtils.SetReadOnly(sourceFilePath, false);
+                if (FileUtils.Exists(destFilePath)) FileUtils.SetReadOnly(destFilePath, false);
                 File.Move(PathUtils.ForApi(sourceFilePath), PathUtils.ForApi(temp));
                 File.Move(PathUtils.ForApi(temp), PathUtils.ForApi(destFilePath));
             });
@@ -183,8 +186,11 @@ public static class FileUtils {
             DirectoryUtils.Create(PathUtils.RemoveLastPart(destFilePath));
             FileUtils.Delete(destFilePath);
             Logger.Trace($"剪切文件:{sourceFilePath} → {destFilePath}");
-            ResilientUtils.RetryOn<IOException>(()
-                => File.Move(PathUtils.ForApi(sourceFilePath), PathUtils.ForApi(destFilePath)));
+            ResilientUtils.RetryOn<IOException>(() => {
+                FileUtils.SetReadOnly(sourceFilePath, false);
+                if (FileUtils.Exists(destFilePath)) FileUtils.SetReadOnly(destFilePath, false);
+                File.Move(PathUtils.ForApi(sourceFilePath), PathUtils.ForApi(destFilePath));
+            });
         }
     }
 
@@ -214,8 +220,10 @@ public static class FileUtils {
             }
         }
         // 永久删除
-        ResilientUtils.RetryOn<IOException>(()
-            => File.Delete(PathUtils.ForApi(filePath)));
+        ResilientUtils.RetryOn<IOException>(() => {
+            FileUtils.SetReadOnly(filePath, false);
+            File.Delete(PathUtils.ForApi(filePath));
+        });
         return false;
     }
 
@@ -248,7 +256,7 @@ public static class FileUtils {
             System.Runtime.ExceptionServices.ExceptionDispatchInfo? internalEx = null; // 捕获内部异常
             var thread = new Thread(() => { 
                 try { 
-                    ResilientUtils.RetryOn<IOException>(Run); 
+                    ResilientUtils.RetryOn<IOException, COMException>(Run); 
                 } catch (Exception ex) { 
                     internalEx = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(ex); 
                 } 
@@ -413,6 +421,21 @@ public static class FileUtils {
     public static FileInfo GetInfo(string path) 
         => new(PathUtils.ForApi(path));
 
+    /// <summary>
+    /// 设置文件或文件夹的只读属性。
+    /// 对文件夹使用时,只会设置文件夹本身的属性,不会递归设置其中的文件或子文件夹。
+    /// </summary>
+    public static void SetReadOnly(string path, bool readOnly) {
+        path = PathUtils.ForApi(path);
+        var attributes = File.GetAttributes(path);
+        var newAttributes = readOnly
+            ? attributes | FileAttributes.ReadOnly
+            : attributes & ~FileAttributes.ReadOnly;
+        if (attributes == newAttributes) return;
+        Logger.Trace($"{(readOnly ? "添加" : "移除")}只读属性:{path}(原属性:{attributes},新属性:{newAttributes})");
+        File.SetAttributes(path, newAttributes);
+    }
+
 }
 
 /// <summary>