• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
Aucun tag

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

ファイル整理用ツールのPrism+WPFサンプル実装


Commit MetaInfo

Révision22910001f8acfa3229cd78f20d010bb49c38bb27 (tree)
l'heure2023-09-24 18:59:19
Auteuryoshy <yoshy.org.bitbucket@gz.j...>
Commiteryoshy

Message de Log

[UPD] ファイルシステム操作系のコピー処理時にジャンクションとシンボリックリンクを認識してそのものをコピーする機能を追加
[FIX] ファイルシステム操作系のディレクトリ削除処理を自前で行うように修正(処理中の対象ディレクトリがディレクトリがリパースポイントの場合は再帰で削除しない)

Change Summary

Modification

--- /dev/null
+++ b/FolderCategorizer2.04OuterEdge/Repository/OS/JunctionHelper.cs
@@ -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+}
--- a/FolderCategorizer2.04OuterEdge/Repository/OS/LocalFileSystemRepository.cs
+++ b/FolderCategorizer2.04OuterEdge/Repository/OS/LocalFileSystemRepository.cs
@@ -7,8 +7,10 @@ using Microsoft.VisualBasic.FileIO;
77 using NLog;
88 using System;
99 using System.Collections.Generic;
10+using System.ComponentModel;
1011 using System.IO;
1112 using System.Linq;
13+using System.Runtime.InteropServices;
1214 using VBFileSystem = Microsoft.VisualBasic.FileIO.FileSystem;
1315
1416 namespace FolderCategorizer2.OuterEdge.Repository.OS
@@ -109,140 +111,179 @@ namespace FolderCategorizer2.OuterEdge.Repository.OS
109111
110112 public void MoveFile(string targetPath, string sourceFullPath)
111113 {
112-#if false
113114 if (Directory.Exists(sourceFullPath))
114115 {
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);
131117 Directory.Move(sourceFullPath, targetPath);
132118 }
133119 else if (File.Exists(sourceFullPath))
134120 {
135- logger.Debug("File.Move({0}, {1})", sourceFullPath, targetPath);
121+ logger.Trace("File.Move({0}, {1})", sourceFullPath, targetPath);
136122 File.Move(sourceFullPath, targetPath);
137123 }
138124 else
139125 {
140126 throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です");
141127 }
142-#endif
143128 }
144129
145130 public void CopyFile(string targetPath, string sourceFullPath)
146131 {
147-#if false
148132 if (Directory.Exists(sourceFullPath))
149133 {
150- logger.Debug("FileSystem.CopyDirectory({0}, {1})", sourceFullPath, targetPath);
151- VBFileSystem.CopyDirectory(sourceFullPath, targetPath, UIOption.AllDialogs);
134+ CopyDirectoryInternal(targetPath, sourceFullPath);
152135 }
153136 else if (File.Exists(sourceFullPath))
154137 {
155- logger.Debug("FileSystem.CopyFile({0}, {1})", sourceFullPath, targetPath);
156- VBFileSystem.CopyFile(sourceFullPath, targetPath, UIOption.AllDialogs);
138+ CopyFileInternal(targetPath, sourceFullPath);
157139 }
158140 else
159141 {
160142 throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です");
161143 }
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)
164153 {
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);
167156 }
168- else if (File.Exists(sourceFullPath))
157+ else if ((file.Attributes & FileAttributes.ReparsePoint) != 0)
169158 {
170- logger.Debug("File.Copy({0}, {1})", sourceFullPath, targetPath);
171- File.Copy(sourceFullPath, targetPath);
159+ CreateFileSymbolicLink(targetPath, file, true);
172160 }
173161 else
174162 {
175- throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です");
163+ logger.Trace("FileInfo({0}).CopyTo({1})", sourceFullPath, targetPath);
164+ file.CopyTo(targetPath);
176165 }
177-#endif
178166 }
179167
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)
182177 {
183178 // Get information about the source directory
184179 DirectoryInfo dir = new(sourceFullPath);
185180
186181 // Check if the source directory exists
187182 if (!dir.Exists)
183+ {
188184 throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
185+ }
189186
190187 // Cache directories before we start copying
191188 DirectoryInfo[] dirs = dir.GetDirectories();
192189
193190 // Create the destination directory
194- logger.Debug("Directory.CreateDirectory({0})", targetPath);
191+ logger.Trace("Directory.CreateDirectory({0})", targetPath);
195192 Directory.CreateDirectory(targetPath);
196193
197194 // Get the files in the source directory and copy to the destination directory
198195 foreach (FileInfo file in dir.GetFiles())
199196 {
200197 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);
203199 }
204200
205201 // Recursively call this method
206202 foreach (DirectoryInfo subDir in dirs)
207203 {
208204 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+ }
210221 }
211222 }
212223
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+
213231 public void DeleteFile(string sourceFullPath)
214232 {
215-#if false
216233 if (Directory.Exists(sourceFullPath))
217234 {
218- logger.Debug("FileSystem.DeleteDirectory({0})", sourceFullPath);
219- VBFileSystem.DeleteDirectory(sourceFullPath, UIOption.AllDialogs, RecycleOption.SendToRecycleBin);
235+ logger.Trace("DeleteDirectoryInternal({0})", sourceFullPath);
236+ DeleteDirectoryInternal(sourceFullPath);
220237 }
221238 else if (File.Exists(sourceFullPath))
222239 {
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);
225242 }
226243 else
227244 {
228245 throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です");
229246 }
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)
232262 {
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;
235266 }
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())
237273 {
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();
240277 }
241- else
278+
279+ // Recursively call this method
280+ foreach (DirectoryInfo subDir in dirs)
242281 {
243- throw new FileNotFoundException($"{sourceFullPath}が存在しないか無効です");
282+ DeleteDirectoryInternal(subDir.FullName);
244283 }
245-#endif
284+
285+ logger.Trace("Directory.Delete({0})", sourceFullPath);
286+ dir.Delete();
246287 }
247288
248289 public void RenameFile(string sourceFullPath, string targetName)