ファイル整理用ツールのPrism+WPFサンプル実装
Révision | 22910001f8acfa3229cd78f20d010bb49c38bb27 (tree) |
---|---|
l'heure | 2023-09-24 18:59:19 |
Auteur | yoshy <yoshy.org.bitbucket@gz.j...> |
Commiter | yoshy |
[UPD] ファイルシステム操作系のコピー処理時にジャンクションとシンボリックリンクを認識してそのものをコピーする機能を追加
[FIX] ファイルシステム操作系のディレクトリ削除処理を自前で行うように修正(処理中の対象ディレクトリがディレクトリがリパースポイントの場合は再帰で削除しない)
@@ -0,0 +1,258 @@ | ||
1 | +using Microsoft.Win32.SafeHandles; | |
2 | +using NLog; | |
3 | +using System; | |
4 | +using System.ComponentModel; | |
5 | +using System.IO; | |
6 | +using System.Runtime.InteropServices; | |
7 | +using System.Text; | |
8 | + | |
9 | +namespace FolderCategorizer2.OuterEdge.Repository.OS | |
10 | +{ | |
11 | + /// <see href="https://stackoverflow.com/questions/1400549/in-net-how-do-i-create-a-junction-in-ntfs-as-opposed-to-a-symlink"/> | |
12 | + internal static class JunctionHelper | |
13 | + { | |
14 | + private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); | |
15 | + | |
16 | + /// <summary> | |
17 | + /// Command to set the reparse point data block. | |
18 | + /// </summary> | |
19 | + private const uint FSCTL_SET_REPARSE_POINT = 0x000900A4; | |
20 | + | |
21 | + /// <summary> | |
22 | + /// Command to get the reparse point data block. | |
23 | + /// </summary> | |
24 | + private const uint FSCTL_GET_REPARSE_POINT = 0x000900A8; | |
25 | + | |
26 | + /// <summary> | |
27 | + /// Command to delete the reparse point data base. | |
28 | + /// </summary> | |
29 | + private const uint FSCTL_DELETE_REPARSE_POINT = 0x000900AC; | |
30 | + | |
31 | + /// <summary> | |
32 | + /// Reparse point tag used to identify mount points and junction points. | |
33 | + /// </summary> | |
34 | + private const uint IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003; | |
35 | + | |
36 | + /// <summary> | |
37 | + /// This prefix indicates to NTFS that the path is to be treated as a non-interpreted | |
38 | + /// path in the virtual file system. | |
39 | + /// </summary> | |
40 | + private const string NonInterpretedPathPrefix = @"\??\"; | |
41 | + | |
42 | + [Flags] | |
43 | + private enum Win32FileAccess : uint | |
44 | + { | |
45 | + GenericRead = 0x80000000U, | |
46 | + GenericWrite = 0x40000000U, | |
47 | + GenericExecute = 0x20000000U, | |
48 | + GenericAll = 0x10000000U | |
49 | + } | |
50 | + | |
51 | + [Flags] | |
52 | + private enum Win32FileAttribute : uint | |
53 | + { | |
54 | + AttributeReadOnly = 0x1U, | |
55 | + AttributeHidden = 0x2U, | |
56 | + AttributeSystem = 0x4U, | |
57 | + AttributeDirectory = 0x10U, | |
58 | + AttributeArchive = 0x20U, | |
59 | + AttributeDevice = 0x40U, | |
60 | + AttributeNormal = 0x80U, | |
61 | + AttributeTemporary = 0x100U, | |
62 | + AttributeSparseFile = 0x200U, | |
63 | + AttributeReparsePoint = 0x400U, | |
64 | + AttributeCompressed = 0x800U, | |
65 | + AttributeOffline = 0x1000U, | |
66 | + AttributeNotContentIndexed = 0x2000U, | |
67 | + AttributeEncrypted = 0x4000U, | |
68 | + AttributeIntegrityStream = 0x8000U, | |
69 | + AttributeVirtual = 0x10000U, | |
70 | + AttributeNoScrubData = 0x20000U, | |
71 | + AttributeEA = 0x40000U, | |
72 | + AttributeRecallOnOpen = 0x40000U, | |
73 | + AttributePinned = 0x80000U, | |
74 | + AttributeUnpinned = 0x100000U, | |
75 | + AttributeRecallOnDataAccess = 0x400000U, | |
76 | + FlagOpenNoRecall = 0x100000U, | |
77 | + /// <summary> | |
78 | + /// Normal reparse point processing will not occur; CreateFile will attempt to open the reparse point. When a file is opened, a file handle is returned, | |
79 | + /// whether or not the filter that controls the reparse point is operational. | |
80 | + /// <br />This flag cannot be used with the <see cref="FileMode.Create"/> flag. | |
81 | + /// <br />If the file is not a reparse point, then this flag is ignored. | |
82 | + /// </summary> | |
83 | + FlagOpenReparsePoint = 0x200000U, | |
84 | + FlagSessionAware = 0x800000U, | |
85 | + FlagPosixSemantics = 0x1000000U, | |
86 | + /// <summary> | |
87 | + /// You must set this flag to obtain a handle to a directory. A directory handle can be passed to some functions instead of a file handle. | |
88 | + /// </summary> | |
89 | + FlagBackupSemantics = 0x2000000U, | |
90 | + FlagDeleteOnClose = 0x4000000U, | |
91 | + FlagSequentialScan = 0x8000000U, | |
92 | + FlagRandomAccess = 0x10000000U, | |
93 | + FlagNoBuffering = 0x20000000U, | |
94 | + FlagOverlapped = 0x40000000U, | |
95 | + FlagWriteThrough = 0x80000000U | |
96 | + } | |
97 | + | |
98 | + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] | |
99 | + private static extern SafeFileHandle CreateFile(string lpFileName, Win32FileAccess dwDesiredAccess, | |
100 | + FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition, | |
101 | + Win32FileAttribute dwFlagsAndAttributes, IntPtr hTemplateFile); | |
102 | + | |
103 | + // Because the tag we're using is IO_REPARSE_TAG_MOUNT_POINT, we use the MountPointReparseBuffer struct in the DUMMYUNIONNAME union. | |
104 | + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
105 | + private struct ReparseDataBuffer | |
106 | + { | |
107 | + /// <summary>Reparse point tag. Must be a Microsoft reparse point tag.</summary> | |
108 | + public uint ReparseTag; | |
109 | + /// <summary>Size, in bytes, of the reparse data in the buffer that <see cref="PathBuffer"/> points to.</summary> | |
110 | + public ushort ReparseDataLength; | |
111 | + /// <summary>Reserved; do not use.</summary> | |
112 | + private ushort Reserved; | |
113 | + /// <summary>Offset, in bytes, of the substitute name string in the <see cref="PathBuffer"/> array.</summary> | |
114 | + public ushort SubstituteNameOffset; | |
115 | + /// <summary>Length, in bytes, of the substitute name string. If this string is null-terminated, <see cref="SubstituteNameLength"/> does not include space for the null character.</summary> | |
116 | + public ushort SubstituteNameLength; | |
117 | + /// <summary>Offset, in bytes, of the print name string in the <see cref="PathBuffer"/> array.</summary> | |
118 | + public ushort PrintNameOffset; | |
119 | + /// <summary>Length, in bytes, of the print name string. If this string is null-terminated, <see cref="PrintNameLength"/> does not include space for the null character.</summary> | |
120 | + public ushort PrintNameLength; | |
121 | + /// <summary> | |
122 | + /// A buffer containing the unicode-encoded path string. The path string contains the substitute name | |
123 | + /// string and print name string. The substitute name and print name strings can appear in any order. | |
124 | + /// </summary> | |
125 | + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8184)] | |
126 | + internal string PathBuffer; | |
127 | + // with <MarshalAs(UnmanagedType.ByValArray, SizeConst:=16368)> Public PathBuffer As Byte() | |
128 | + // 16368 is the amount of bytes. since a unicode string uses 2 bytes per character, constrain to 16368/2 = 8184 characters. | |
129 | + } | |
130 | + | |
131 | + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "DeviceIoControl")] | |
132 | + private static extern bool DeviceIoControlSet(SafeFileHandle hDevice, uint dwIoControlCode, | |
133 | + [In] ReparseDataBuffer lpInBuffer, uint nInBufferSize, | |
134 | + IntPtr lpOutBuffer, uint nOutBufferSize, | |
135 | + uint lpBytesReturned, IntPtr lpOverlapped); | |
136 | + | |
137 | + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "DeviceIoControl")] | |
138 | + private static extern bool DeviceIoControlGet(SafeFileHandle hDevice, uint dwIoControlCode, | |
139 | + IntPtr lpInBuffer, uint nInBufferSize, | |
140 | + [In, Out] ref ReparseDataBuffer lpOutBuffer, uint nOutBufferSize, | |
141 | + [Out] out uint lpBytesReturned, IntPtr lpOverlapped); | |
142 | + | |
143 | + /// <summary> | |
144 | + /// Creates a junction point from the specified directory to the specified target directory. | |
145 | + /// </summary> | |
146 | + /// <remarks> | |
147 | + /// Only works on NTFS. | |
148 | + /// </remarks> | |
149 | + /// <param name="path">The junction point path</param> | |
150 | + /// <param name="targetDir">The target directory</param> | |
151 | + /// <param name="overwrite">If true overwrites an existing reparse point or empty directory</param> | |
152 | + /// <exception cref="IOException">Thrown when the junction point could not be created or when | |
153 | + /// an existing directory was found and <paramref name="overwrite" /> if false</exception> | |
154 | + public static void Create(string path, string targetDir, bool overwrite = false) | |
155 | + { | |
156 | + if (Directory.Exists(path)) | |
157 | + { | |
158 | + if (!overwrite) | |
159 | + throw new IOException("Directory already exists and overwrite parameter is false."); | |
160 | + } | |
161 | + else | |
162 | + { | |
163 | + Directory.CreateDirectory(path); | |
164 | + } | |
165 | + | |
166 | + targetDir = NonInterpretedPathPrefix + Path.GetFullPath(targetDir); | |
167 | + | |
168 | + using SafeFileHandle reparsePointHandle = OpenReparsePoint(path, Win32FileAccess.GenericWrite); | |
169 | + | |
170 | + if (reparsePointHandle.IsInvalid || Marshal.GetLastWin32Error() != 0) | |
171 | + { | |
172 | + throw new IOException("Unable to open reparse point.", new Win32Exception(Marshal.GetLastWin32Error())); | |
173 | + } | |
174 | + | |
175 | + //// unicode string is 2 bytes per character, so *2 to get byte length | |
176 | + //ushort byteLength = (ushort)(targetDir.Length * 2); | |
177 | + ushort byteLength = (ushort)Encoding.Unicode.GetByteCount(targetDir); | |
178 | + var reparseDataBuffer = new ReparseDataBuffer() | |
179 | + { | |
180 | + ReparseTag = IO_REPARSE_TAG_MOUNT_POINT, | |
181 | + ReparseDataLength = (ushort)(byteLength + 12u), | |
182 | + SubstituteNameOffset = 0, | |
183 | + SubstituteNameLength = byteLength, | |
184 | + PrintNameOffset = (ushort)(byteLength + 2u), | |
185 | + PrintNameLength = 0, | |
186 | + PathBuffer = targetDir | |
187 | + }; | |
188 | + | |
189 | + bool result = DeviceIoControlSet(reparsePointHandle, FSCTL_SET_REPARSE_POINT, reparseDataBuffer, (uint)(byteLength + 20), IntPtr.Zero, 0u, 0u, IntPtr.Zero); | |
190 | + if (!result) | |
191 | + throw new IOException("Unable to create junction point.", new Win32Exception(Marshal.GetLastWin32Error())); | |
192 | + } | |
193 | + | |
194 | + /// <summary> | |
195 | + /// Determines whether the specified path exists and refers to a junction point. | |
196 | + /// </summary> | |
197 | + /// <param name="path">The junction point path</param> | |
198 | + /// <returns>True if the specified path represents a junction point</returns> | |
199 | + /// <exception cref="IOException">Thrown if the specified path is invalid | |
200 | + /// or some other error occurs</exception> | |
201 | + public static bool Exists(string path) | |
202 | + { | |
203 | + if (!Directory.Exists(path)) | |
204 | + return false; | |
205 | + | |
206 | + using (SafeFileHandle handle = OpenReparsePoint(path, Win32FileAccess.GenericRead)) | |
207 | + { | |
208 | + return InternalGetTarget(handle) != null; | |
209 | + } | |
210 | + } | |
211 | + | |
212 | + /// <summary> | |
213 | + /// Gets the target of the specified junction point. | |
214 | + /// </summary> | |
215 | + /// <remarks> | |
216 | + /// Only works on NTFS. | |
217 | + /// </remarks> | |
218 | + /// <param name="path">The junction point path</param> | |
219 | + /// <returns>The target of the junction point, or null when the specified path does not | |
220 | + /// exist, is invalid, is not a junction point, or some other error occurs</returns> | |
221 | + public static string GetTarget(string path) | |
222 | + { | |
223 | + using (SafeFileHandle handle = OpenReparsePoint(path, Win32FileAccess.GenericRead)) | |
224 | + { | |
225 | + return InternalGetTarget(handle); | |
226 | + } | |
227 | + } | |
228 | + | |
229 | + private static string InternalGetTarget(SafeFileHandle hReparsePoint) | |
230 | + { | |
231 | + var reparseDataBuffer = new ReparseDataBuffer(); | |
232 | + uint outBufferSize = 8200; | |
233 | + uint bytesReturned; | |
234 | + | |
235 | + bool result = DeviceIoControlGet(hReparsePoint, FSCTL_GET_REPARSE_POINT, IntPtr.Zero, 0u, ref reparseDataBuffer, outBufferSize, out bytesReturned, IntPtr.Zero); | |
236 | + if (!result) | |
237 | + throw new IOException("Unable to get information about junction point.", new Win32Exception(Marshal.GetLastWin32Error())); | |
238 | + | |
239 | + if (reparseDataBuffer.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT) | |
240 | + return null; | |
241 | + | |
242 | + string targetDir = reparseDataBuffer.PathBuffer; | |
243 | + | |
244 | + if (targetDir.StartsWith(NonInterpretedPathPrefix)) | |
245 | + targetDir = targetDir.Substring(NonInterpretedPathPrefix.Length); | |
246 | + | |
247 | + return targetDir; | |
248 | + } | |
249 | + | |
250 | + private static SafeFileHandle OpenReparsePoint(string junctionPath, Win32FileAccess desiredFileAccess) | |
251 | + { | |
252 | + return CreateFile(junctionPath, desiredFileAccess, | |
253 | + FileShare.Read | FileShare.Write | FileShare.Delete, IntPtr.Zero, FileMode.Open, | |
254 | + Win32FileAttribute.FlagBackupSemantics | Win32FileAttribute.FlagOpenReparsePoint, IntPtr.Zero); | |
255 | + } | |
256 | + | |
257 | + } | |
258 | +} |
@@ -7,8 +7,10 @@ using Microsoft.VisualBasic.FileIO; | ||
7 | 7 | using NLog; |
8 | 8 | using System; |
9 | 9 | using System.Collections.Generic; |
10 | +using System.ComponentModel; | |
10 | 11 | using System.IO; |
11 | 12 | using System.Linq; |
13 | +using System.Runtime.InteropServices; | |
12 | 14 | using VBFileSystem = Microsoft.VisualBasic.FileIO.FileSystem; |
13 | 15 | |
14 | 16 | namespace FolderCategorizer2.OuterEdge.Repository.OS |
@@ -109,140 +111,179 @@ namespace FolderCategorizer2.OuterEdge.Repository.OS | ||
109 | 111 | |
110 | 112 | public void MoveFile(string targetPath, string sourceFullPath) |
111 | 113 | { |
112 | -#if false | |
113 | 114 | if (Directory.Exists(sourceFullPath)) |
114 | 115 | { |
115 | - logger.Debug("FileSystem.MoveDirectory({0}, {1})", sourceFullPath, targetPath); | |
116 | - VBFileSystem.MoveDirectory(sourceFullPath, targetPath, UIOption.AllDialogs); | |
117 | - } | |
118 | - else if (File.Exists(sourceFullPath)) | |
119 | - { | |
120 | - logger.Debug("FileSystem.MoveFile({0}, {1})", sourceFullPath, targetPath); | |
121 | - VBFileSystem.MoveFile(sourceFullPath, targetPath, UIOption.AllDialogs); | |
122 | - } | |
123 | - else | |
124 | - { | |
125 | - throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です"); | |
126 | - } | |
127 | -#else | |
128 | - if (Directory.Exists(sourceFullPath)) | |
129 | - { | |
130 | - logger.Debug("Directory.Move({0}, {1})", sourceFullPath, targetPath); | |
116 | + logger.Trace("Directory.Move({0}, {1})", sourceFullPath, targetPath); | |
131 | 117 | Directory.Move(sourceFullPath, targetPath); |
132 | 118 | } |
133 | 119 | else if (File.Exists(sourceFullPath)) |
134 | 120 | { |
135 | - logger.Debug("File.Move({0}, {1})", sourceFullPath, targetPath); | |
121 | + logger.Trace("File.Move({0}, {1})", sourceFullPath, targetPath); | |
136 | 122 | File.Move(sourceFullPath, targetPath); |
137 | 123 | } |
138 | 124 | else |
139 | 125 | { |
140 | 126 | throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です"); |
141 | 127 | } |
142 | -#endif | |
143 | 128 | } |
144 | 129 | |
145 | 130 | public void CopyFile(string targetPath, string sourceFullPath) |
146 | 131 | { |
147 | -#if false | |
148 | 132 | if (Directory.Exists(sourceFullPath)) |
149 | 133 | { |
150 | - logger.Debug("FileSystem.CopyDirectory({0}, {1})", sourceFullPath, targetPath); | |
151 | - VBFileSystem.CopyDirectory(sourceFullPath, targetPath, UIOption.AllDialogs); | |
134 | + CopyDirectoryInternal(targetPath, sourceFullPath); | |
152 | 135 | } |
153 | 136 | else if (File.Exists(sourceFullPath)) |
154 | 137 | { |
155 | - logger.Debug("FileSystem.CopyFile({0}, {1})", sourceFullPath, targetPath); | |
156 | - VBFileSystem.CopyFile(sourceFullPath, targetPath, UIOption.AllDialogs); | |
138 | + CopyFileInternal(targetPath, sourceFullPath); | |
157 | 139 | } |
158 | 140 | else |
159 | 141 | { |
160 | 142 | throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です"); |
161 | 143 | } |
162 | -#else | |
163 | - if (Directory.Exists(sourceFullPath)) | |
144 | + } | |
145 | + | |
146 | + private static void CopyFileInternal(string targetPath, string sourceFullPath) | |
147 | + { | |
148 | + FileInfo file = new(sourceFullPath); | |
149 | + | |
150 | + string junctionTarget = JunctionHelper.GetTarget(sourceFullPath); | |
151 | + | |
152 | + if (junctionTarget != null) | |
164 | 153 | { |
165 | - //logger.Debug("Directory.Copy({0}, {1})", sourceFullPath, targetPath); | |
166 | - CopyDirectory(targetPath, sourceFullPath); | |
154 | + logger.Trace("JunctionHelper.Create({0}, {1})", targetPath, junctionTarget); | |
155 | + JunctionHelper.Create(targetPath, junctionTarget); | |
167 | 156 | } |
168 | - else if (File.Exists(sourceFullPath)) | |
157 | + else if ((file.Attributes & FileAttributes.ReparsePoint) != 0) | |
169 | 158 | { |
170 | - logger.Debug("File.Copy({0}, {1})", sourceFullPath, targetPath); | |
171 | - File.Copy(sourceFullPath, targetPath); | |
159 | + CreateFileSymbolicLink(targetPath, file, true); | |
172 | 160 | } |
173 | 161 | else |
174 | 162 | { |
175 | - throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です"); | |
163 | + logger.Trace("FileInfo({0}).CopyTo({1})", sourceFullPath, targetPath); | |
164 | + file.CopyTo(targetPath); | |
176 | 165 | } |
177 | -#endif | |
178 | 166 | } |
179 | 167 | |
180 | - /// <see cref="https://learn.microsoft.com/ja-jp/dotnet/standard/io/how-to-copy-directories"/> | |
181 | - private void CopyDirectory(string targetPath, string sourceFullPath) | |
168 | + private static void CreateFileSymbolicLink(string targetPath, FileInfo file, bool bFinalTarget) | |
169 | + { | |
170 | + string linkTarget = bFinalTarget ? file.ResolveLinkTarget(true).FullName : file.LinkTarget; | |
171 | + logger.Trace("File.CreateSymbolicLink({0}, {1})", targetPath, linkTarget); | |
172 | + File.CreateSymbolicLink(targetPath, linkTarget); | |
173 | + } | |
174 | + | |
175 | + /// <see href="https://learn.microsoft.com/ja-jp/dotnet/standard/io/how-to-copy-directories"/> | |
176 | + private void CopyDirectoryInternal(string targetPath, string sourceFullPath) | |
182 | 177 | { |
183 | 178 | // Get information about the source directory |
184 | 179 | DirectoryInfo dir = new(sourceFullPath); |
185 | 180 | |
186 | 181 | // Check if the source directory exists |
187 | 182 | if (!dir.Exists) |
183 | + { | |
188 | 184 | throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); |
185 | + } | |
189 | 186 | |
190 | 187 | // Cache directories before we start copying |
191 | 188 | DirectoryInfo[] dirs = dir.GetDirectories(); |
192 | 189 | |
193 | 190 | // Create the destination directory |
194 | - logger.Debug("Directory.CreateDirectory({0})", targetPath); | |
191 | + logger.Trace("Directory.CreateDirectory({0})", targetPath); | |
195 | 192 | Directory.CreateDirectory(targetPath); |
196 | 193 | |
197 | 194 | // Get the files in the source directory and copy to the destination directory |
198 | 195 | foreach (FileInfo file in dir.GetFiles()) |
199 | 196 | { |
200 | 197 | string targetFilePath = Path.Combine(targetPath, file.Name); |
201 | - logger.Debug("FileInfo({0}).CopyTo({1})", file.FullName, targetFilePath); | |
202 | - file.CopyTo(targetFilePath); | |
198 | + CopyFileInternal(targetFilePath, file.FullName); | |
203 | 199 | } |
204 | 200 | |
205 | 201 | // Recursively call this method |
206 | 202 | foreach (DirectoryInfo subDir in dirs) |
207 | 203 | { |
208 | 204 | string newDestinationDir = Path.Combine(targetPath, subDir.Name); |
209 | - CopyDirectory(newDestinationDir, subDir.FullName); | |
205 | + | |
206 | + string junctionTarget = JunctionHelper.GetTarget(subDir.FullName); | |
207 | + | |
208 | + if (junctionTarget != null) | |
209 | + { | |
210 | + logger.Trace("JunctionHelper.Create({0}, {1})", newDestinationDir, junctionTarget); | |
211 | + JunctionHelper.Create(newDestinationDir, junctionTarget); | |
212 | + } | |
213 | + else if ((subDir.Attributes & FileAttributes.ReparsePoint) != 0) | |
214 | + { | |
215 | + CreateDirectorySymbolicLink(newDestinationDir, subDir, true); | |
216 | + } | |
217 | + else | |
218 | + { | |
219 | + CopyDirectoryInternal(newDestinationDir, subDir.FullName); | |
220 | + } | |
210 | 221 | } |
211 | 222 | } |
212 | 223 | |
224 | + private static void CreateDirectorySymbolicLink(string targetPath, DirectoryInfo dir, bool bFinalTarget) | |
225 | + { | |
226 | + string linkTarget = bFinalTarget ? dir.ResolveLinkTarget(true).FullName : dir.LinkTarget; | |
227 | + logger.Trace("Directory.CreateSymbolicLink({0}, {1})", targetPath, linkTarget); | |
228 | + Directory.CreateSymbolicLink(targetPath, linkTarget); | |
229 | + } | |
230 | + | |
213 | 231 | public void DeleteFile(string sourceFullPath) |
214 | 232 | { |
215 | -#if false | |
216 | 233 | if (Directory.Exists(sourceFullPath)) |
217 | 234 | { |
218 | - logger.Debug("FileSystem.DeleteDirectory({0})", sourceFullPath); | |
219 | - VBFileSystem.DeleteDirectory(sourceFullPath, UIOption.AllDialogs, RecycleOption.SendToRecycleBin); | |
235 | + logger.Trace("DeleteDirectoryInternal({0})", sourceFullPath); | |
236 | + DeleteDirectoryInternal(sourceFullPath); | |
220 | 237 | } |
221 | 238 | else if (File.Exists(sourceFullPath)) |
222 | 239 | { |
223 | - logger.Debug("FileSystem.DeleteFile({0})", sourceFullPath); | |
224 | - VBFileSystem.DeleteFile(sourceFullPath, UIOption.AllDialogs, RecycleOption.SendToRecycleBin); | |
240 | + logger.Trace("File.Delete({0})", sourceFullPath); | |
241 | + File.Delete(sourceFullPath); | |
225 | 242 | } |
226 | 243 | else |
227 | 244 | { |
228 | 245 | throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です"); |
229 | 246 | } |
230 | -#else | |
231 | - if (Directory.Exists(sourceFullPath)) | |
247 | + } | |
248 | + | |
249 | + /// <see href="https://qiita.com/mima_ita/items/f84e187d60b63074927f"/> | |
250 | + private void DeleteDirectoryInternal(string sourceFullPath) | |
251 | + { | |
252 | + // Get information about the source directory | |
253 | + DirectoryInfo dir = new(sourceFullPath); | |
254 | + | |
255 | + // Check if the source directory exists | |
256 | + if (!dir.Exists) | |
257 | + { | |
258 | + throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}"); | |
259 | + } | |
260 | + | |
261 | + if ((dir.Attributes & FileAttributes.ReparsePoint) != 0) | |
232 | 262 | { |
233 | - logger.Debug("Directory.Delete({0})", sourceFullPath); | |
234 | - Directory.Delete(sourceFullPath, true); | |
263 | + logger.Trace("Directory.Delete(<JUNCTION/SYMLINKD> {0})", sourceFullPath); | |
264 | + dir.Delete(); | |
265 | + return; | |
235 | 266 | } |
236 | - else if (File.Exists(sourceFullPath)) | |
267 | + | |
268 | + // Cache directories before we start deleting | |
269 | + DirectoryInfo[] dirs = dir.GetDirectories(); | |
270 | + | |
271 | + // Get the files in the source directory and delete them | |
272 | + foreach (FileInfo file in dir.GetFiles()) | |
237 | 273 | { |
238 | - logger.Debug("File.Delete({0})", sourceFullPath); | |
239 | - File.Delete(sourceFullPath); | |
274 | + logger.Trace("File({0}).Delete()", file.FullName); | |
275 | + File.SetAttributes(file.FullName, FileAttributes.Normal); | |
276 | + file.Delete(); | |
240 | 277 | } |
241 | - else | |
278 | + | |
279 | + // Recursively call this method | |
280 | + foreach (DirectoryInfo subDir in dirs) | |
242 | 281 | { |
243 | - throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です"); | |
282 | + DeleteDirectoryInternal(subDir.FullName); | |
244 | 283 | } |
245 | -#endif | |
284 | + | |
285 | + logger.Trace("Directory.Delete({0})", sourceFullPath); | |
286 | + dir.Delete(); | |
246 | 287 | } |
247 | 288 | |
248 | 289 | public void RenameFile(string sourceFullPath, string targetName) |