diff --git a/FtpServer/Library/Connections/ControlConnection.cs b/FtpServer/Library/Connections/ControlConnection.cs index dc67213..efe3b34 100644 --- a/FtpServer/Library/Connections/ControlConnection.cs +++ b/FtpServer/Library/Connections/ControlConnection.cs @@ -128,6 +128,7 @@ private enum FtpReplyCode { CommandOkay = 200, SystemStatus = 211, + FileStatus = 213, CommandUnrecognized = 500, SyntaxErrorInParametersOrArguments = 501, NotImplemented = 502, @@ -273,7 +274,7 @@ await ReplyAsync( await ReplyAsync(FtpReplyCode.NameSystemType, "UNIX simulated by .NET Core"); return; case "FEAT": - await ReplyMultilineAsync(FtpReplyCode.SystemStatus, "Supports:\nUTF8"); + await ReplyMultilineAsync(FtpReplyCode.SystemStatus, "Supports:\nUTF8\nMFMT\nMDTM"); return; case "OPTS": if (parameter.ToUpperInvariant() == "UTF8 ON") @@ -388,6 +389,14 @@ await ReplyAsync( case "PROT": await CommandProtAsync(parameter); return; + case "MFMT": + var parameterSegs = parameter.Split(new[] { ' ' }, 2); + var dateTimeString = parameterSegs[0]; + await CommandMfmtAsync(parameterSegs[1], dateTimeString); + return; + case "MDTM": + await CommandMdtmAsync(parameter); + return; } await ReplyAsync(FtpReplyCode.CommandUnrecognized, "Can't recognize this command."); } @@ -595,12 +604,101 @@ await ReplyAsync( await ReplyAsync(FtpReplyCode.FileSpaceInsufficient, string.Format("Writing file denied: {0}", ex.Message)); return; } - + await dataConnection.DisconnectAsync(); await ReplyAsync(FtpReplyCode.SuccessClosingDataConnection, "File has been recieved"); return; } + private async Task CommandMfmtAsync(string path, string dateTimeString) + { + if (!authenticated) + { + await ReplyAsync(FtpReplyCode.NotLoggedIn, "You need to log in first"); + return; + } + if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(dateTimeString)) + { + await ReplyAsync( + FtpReplyCode.SyntaxErrorInParametersOrArguments, + "Syntax error, path or modification time is missing"); + return; + } + + try + { + var dateTime = DateTime.ParseExact( + dateTimeString, + "yyyyMMddHHmmss", + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal); + await fileProvider.SetFileModificationTimeAsync(path, dateTime); + + // FTP standards state the return should be the new datetime source from the filesystem, + // not what was provided in the params. This is because the target file system might not + // be as accurate the requested modifcation time was, eg. may not support seconds. + var resultingModificationTime = await fileProvider.GetFileModificationTimeAsync(path); + + await ReplyAsync(FtpReplyCode.FileStatus, string.Format("Modify={0}; {1}", resultingModificationTime.ToString("yyyyMMddHHmmss"), path)); + return; + } + catch (FormatException ex) + { + await ReplyAsync(FtpReplyCode.SyntaxErrorInParametersOrArguments, string.Format("Cannot parse modifiation time: {0}", ex.Message)); + return; + } + catch (FileBusyException ex) + { + await ReplyAsync(FtpReplyCode.FileBusy, string.Format("File temporarily unavailable: {0}", ex.Message)); + return; + } + catch (FileNoAccessException ex) + { + await ReplyAsync(FtpReplyCode.FileNoAccess, string.Format("File access denied: {0}", ex.Message)); + return; + } + } + + private async Task CommandMdtmAsync(string path) + { + if (!authenticated) + { + await ReplyAsync(FtpReplyCode.NotLoggedIn, "You need to log in first"); + return; + } + if (string.IsNullOrEmpty(path)) + { + await ReplyAsync( + FtpReplyCode.SyntaxErrorInParametersOrArguments, + "Syntax error, path is missing"); + return; + } + + try + { + var resultingModificationTime = await fileProvider.GetFileModificationTimeAsync(path); + + await ReplyAsync(FtpReplyCode.FileStatus, resultingModificationTime.ToString("yyyyMMddHHmmss")); + return; + } + catch (FormatException ex) + { + await ReplyAsync(FtpReplyCode.SyntaxErrorInParametersOrArguments, string.Format("Cannot parse modifiation time: {0}", ex.Message)); + return; + } + catch (FileBusyException ex) + { + await ReplyAsync(FtpReplyCode.FileBusy, string.Format("File temporarily unavailable: {0}", ex.Message)); + return; + } + catch (FileNoAccessException ex) + { + await ReplyAsync(FtpReplyCode.FileNoAccess, string.Format("File access denied: {0}", ex.Message)); + return; + } + + } + private async Task CommandRetrAsync(string parameter) { if (!authenticated) diff --git a/FtpServer/Library/File/IFileProvider.cs b/FtpServer/Library/File/IFileProvider.cs index af143b1..d419a77 100644 --- a/FtpServer/Library/File/IFileProvider.cs +++ b/FtpServer/Library/File/IFileProvider.cs @@ -84,6 +84,20 @@ public interface IFileProvider /// The operation failed but worth a retry. Task CreateFileForWriteAsync(string path); + /// + /// Sets the last modified time for a file + /// + /// Absolute or relative FTP path of the file. + /// Whether the updating the time succeeded or not. + Task SetFileModificationTimeAsync(string path, DateTime newTime); + + /// + /// Gets the last modified time for a file + /// + /// Absolute or relative FTP path of the file. + /// DateTime of last modification. + Task GetFileModificationTimeAsync(string path); + /// /// Gets the names of files and directories. /// diff --git a/FtpServer/Library/File/SimpleFileProvider.cs b/FtpServer/Library/File/SimpleFileProvider.cs index 01cd696..d04fdac 100644 --- a/FtpServer/Library/File/SimpleFileProvider.cs +++ b/FtpServer/Library/File/SimpleFileProvider.cs @@ -178,6 +178,20 @@ public async Task OpenFileForWriteAsync(string path) return System.IO.File.OpenWrite(localPath); } +#pragma warning disable CS1998 + public async Task SetFileModificationTimeAsync(string path, DateTime newTime) +#pragma warning restore CS1998 + { + throw new NotImplementedException(); + } + +#pragma warning disable CS1998 + public async Task GetFileModificationTimeAsync(string path) +#pragma warning restore CS1998 + { + throw new NotImplementedException(); + } + /// /// Creates a new file for writing. /// If the file already exists, replace it instead. diff --git a/UniversalFtpServer/Assets/LargeTile.scale-100.png b/UniversalFtpServer/Assets/LargeTile.scale-100.png index 70e6b61..263e099 100644 Binary files a/UniversalFtpServer/Assets/LargeTile.scale-100.png and b/UniversalFtpServer/Assets/LargeTile.scale-100.png differ diff --git a/UniversalFtpServer/Assets/LargeTile.scale-125.png b/UniversalFtpServer/Assets/LargeTile.scale-125.png index 836424f..c340a84 100644 Binary files a/UniversalFtpServer/Assets/LargeTile.scale-125.png and b/UniversalFtpServer/Assets/LargeTile.scale-125.png differ diff --git a/UniversalFtpServer/Assets/LargeTile.scale-150.png b/UniversalFtpServer/Assets/LargeTile.scale-150.png index 2ee7dc3..0f6d0a2 100644 Binary files a/UniversalFtpServer/Assets/LargeTile.scale-150.png and b/UniversalFtpServer/Assets/LargeTile.scale-150.png differ diff --git a/UniversalFtpServer/Assets/LargeTile.scale-200.png b/UniversalFtpServer/Assets/LargeTile.scale-200.png index f8d49e5..0936854 100644 Binary files a/UniversalFtpServer/Assets/LargeTile.scale-200.png and b/UniversalFtpServer/Assets/LargeTile.scale-200.png differ diff --git a/UniversalFtpServer/Assets/LargeTile.scale-400.png b/UniversalFtpServer/Assets/LargeTile.scale-400.png index 35bb072..a365f8c 100644 Binary files a/UniversalFtpServer/Assets/LargeTile.scale-400.png and b/UniversalFtpServer/Assets/LargeTile.scale-400.png differ diff --git a/UniversalFtpServer/Assets/SmallTile.scale-100.png b/UniversalFtpServer/Assets/SmallTile.scale-100.png index 3f8de4e..0e7dbf5 100644 Binary files a/UniversalFtpServer/Assets/SmallTile.scale-100.png and b/UniversalFtpServer/Assets/SmallTile.scale-100.png differ diff --git a/UniversalFtpServer/Assets/SmallTile.scale-125.png b/UniversalFtpServer/Assets/SmallTile.scale-125.png index 1d435d0..646d071 100644 Binary files a/UniversalFtpServer/Assets/SmallTile.scale-125.png and b/UniversalFtpServer/Assets/SmallTile.scale-125.png differ diff --git a/UniversalFtpServer/Assets/SmallTile.scale-150.png b/UniversalFtpServer/Assets/SmallTile.scale-150.png index 7191a82..27bcdc2 100644 Binary files a/UniversalFtpServer/Assets/SmallTile.scale-150.png and b/UniversalFtpServer/Assets/SmallTile.scale-150.png differ diff --git a/UniversalFtpServer/Assets/SmallTile.scale-200.png b/UniversalFtpServer/Assets/SmallTile.scale-200.png index 18d7e4d..6a1f2e3 100644 Binary files a/UniversalFtpServer/Assets/SmallTile.scale-200.png and b/UniversalFtpServer/Assets/SmallTile.scale-200.png differ diff --git a/UniversalFtpServer/Assets/SmallTile.scale-400.png b/UniversalFtpServer/Assets/SmallTile.scale-400.png index c873085..511a8f2 100644 Binary files a/UniversalFtpServer/Assets/SmallTile.scale-400.png and b/UniversalFtpServer/Assets/SmallTile.scale-400.png differ diff --git a/UniversalFtpServer/Assets/SplashScreen.scale-100.png b/UniversalFtpServer/Assets/SplashScreen.scale-100.png index 9b5a7cb..7a1dd7d 100644 Binary files a/UniversalFtpServer/Assets/SplashScreen.scale-100.png and b/UniversalFtpServer/Assets/SplashScreen.scale-100.png differ diff --git a/UniversalFtpServer/Assets/SplashScreen.scale-125.png b/UniversalFtpServer/Assets/SplashScreen.scale-125.png index 08fedc0..af0cc5d 100644 Binary files a/UniversalFtpServer/Assets/SplashScreen.scale-125.png and b/UniversalFtpServer/Assets/SplashScreen.scale-125.png differ diff --git a/UniversalFtpServer/Assets/SplashScreen.scale-150.png b/UniversalFtpServer/Assets/SplashScreen.scale-150.png index 83ce6e8..1fc15c2 100644 Binary files a/UniversalFtpServer/Assets/SplashScreen.scale-150.png and b/UniversalFtpServer/Assets/SplashScreen.scale-150.png differ diff --git a/UniversalFtpServer/Assets/SplashScreen.scale-200.png b/UniversalFtpServer/Assets/SplashScreen.scale-200.png index d9251fd..0f0b1dd 100644 Binary files a/UniversalFtpServer/Assets/SplashScreen.scale-200.png and b/UniversalFtpServer/Assets/SplashScreen.scale-200.png differ diff --git a/UniversalFtpServer/Assets/SplashScreen.scale-400.png b/UniversalFtpServer/Assets/SplashScreen.scale-400.png index 212fd00..39f0e1f 100644 Binary files a/UniversalFtpServer/Assets/SplashScreen.scale-400.png and b/UniversalFtpServer/Assets/SplashScreen.scale-400.png differ diff --git a/UniversalFtpServer/Assets/Square150x150Logo.scale-100.png b/UniversalFtpServer/Assets/Square150x150Logo.scale-100.png index b0df9d5..7b6f346 100644 Binary files a/UniversalFtpServer/Assets/Square150x150Logo.scale-100.png and b/UniversalFtpServer/Assets/Square150x150Logo.scale-100.png differ diff --git a/UniversalFtpServer/Assets/Square150x150Logo.scale-125.png b/UniversalFtpServer/Assets/Square150x150Logo.scale-125.png index 6d64e54..a5eeebb 100644 Binary files a/UniversalFtpServer/Assets/Square150x150Logo.scale-125.png and b/UniversalFtpServer/Assets/Square150x150Logo.scale-125.png differ diff --git a/UniversalFtpServer/Assets/Square150x150Logo.scale-150.png b/UniversalFtpServer/Assets/Square150x150Logo.scale-150.png index 386e7c2..c0276ce 100644 Binary files a/UniversalFtpServer/Assets/Square150x150Logo.scale-150.png and b/UniversalFtpServer/Assets/Square150x150Logo.scale-150.png differ diff --git a/UniversalFtpServer/Assets/Square150x150Logo.scale-200.png b/UniversalFtpServer/Assets/Square150x150Logo.scale-200.png index ca7f4dd..cb1266b 100644 Binary files a/UniversalFtpServer/Assets/Square150x150Logo.scale-200.png and b/UniversalFtpServer/Assets/Square150x150Logo.scale-200.png differ diff --git a/UniversalFtpServer/Assets/Square150x150Logo.scale-400.png b/UniversalFtpServer/Assets/Square150x150Logo.scale-400.png index 91644b9..122aee3 100644 Binary files a/UniversalFtpServer/Assets/Square150x150Logo.scale-400.png and b/UniversalFtpServer/Assets/Square150x150Logo.scale-400.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-256.png index dc1dfff..04d6059 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-256.png and b/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-256.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-32.png b/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-32.png index ff836ec..dba44a1 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-32.png and b/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-32.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-48.png b/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-48.png index 71637ca..fcc5ae4 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-48.png and b/UniversalFtpServer/Assets/Square44x44Logo.altform-unplated_targetsize-48.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.scale-100.png b/UniversalFtpServer/Assets/Square44x44Logo.scale-100.png index 658dc9b..5d2607d 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.scale-100.png and b/UniversalFtpServer/Assets/Square44x44Logo.scale-100.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.scale-125.png b/UniversalFtpServer/Assets/Square44x44Logo.scale-125.png index 46968d8..c8c3c87 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.scale-125.png and b/UniversalFtpServer/Assets/Square44x44Logo.scale-125.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.scale-150.png b/UniversalFtpServer/Assets/Square44x44Logo.scale-150.png index 2ea8149..27d85fc 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.scale-150.png and b/UniversalFtpServer/Assets/Square44x44Logo.scale-150.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.scale-200.png b/UniversalFtpServer/Assets/Square44x44Logo.scale-200.png index 6b0396b..132cd3a 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.scale-200.png and b/UniversalFtpServer/Assets/Square44x44Logo.scale-200.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.scale-400.png b/UniversalFtpServer/Assets/Square44x44Logo.scale-400.png index eeecfd1..e483afb 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.scale-400.png and b/UniversalFtpServer/Assets/Square44x44Logo.scale-400.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-16.png b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-16.png index 66aa83c..ee1ed17 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-16.png and b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-16.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24.png b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24.png index 55a936f..a6860f5 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24.png and b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png index ea73552..0edd90f 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png and b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-24_altform-unplated.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-256.png b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-256.png index 61752ea..fa79cdd 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-256.png and b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-256.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-32.png b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-32.png index 5c8868c..5fa7c52 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-32.png and b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-32.png differ diff --git a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-48.png b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-48.png index 0aaf379..947c9d4 100644 Binary files a/UniversalFtpServer/Assets/Square44x44Logo.targetsize-48.png and b/UniversalFtpServer/Assets/Square44x44Logo.targetsize-48.png differ diff --git a/UniversalFtpServer/Assets/StoreLogo.scale-100.png b/UniversalFtpServer/Assets/StoreLogo.scale-100.png index 8404b5b..73125e5 100644 Binary files a/UniversalFtpServer/Assets/StoreLogo.scale-100.png and b/UniversalFtpServer/Assets/StoreLogo.scale-100.png differ diff --git a/UniversalFtpServer/Assets/StoreLogo.scale-125.png b/UniversalFtpServer/Assets/StoreLogo.scale-125.png index 6945014..67cda58 100644 Binary files a/UniversalFtpServer/Assets/StoreLogo.scale-125.png and b/UniversalFtpServer/Assets/StoreLogo.scale-125.png differ diff --git a/UniversalFtpServer/Assets/StoreLogo.scale-150.png b/UniversalFtpServer/Assets/StoreLogo.scale-150.png index e0dd664..3efee59 100644 Binary files a/UniversalFtpServer/Assets/StoreLogo.scale-150.png and b/UniversalFtpServer/Assets/StoreLogo.scale-150.png differ diff --git a/UniversalFtpServer/Assets/StoreLogo.scale-200.png b/UniversalFtpServer/Assets/StoreLogo.scale-200.png index 4af878f..4e151dd 100644 Binary files a/UniversalFtpServer/Assets/StoreLogo.scale-200.png and b/UniversalFtpServer/Assets/StoreLogo.scale-200.png differ diff --git a/UniversalFtpServer/Assets/StoreLogo.scale-400.png b/UniversalFtpServer/Assets/StoreLogo.scale-400.png index ff568b9..206bd5c 100644 Binary files a/UniversalFtpServer/Assets/StoreLogo.scale-400.png and b/UniversalFtpServer/Assets/StoreLogo.scale-400.png differ diff --git a/UniversalFtpServer/Assets/Wide310x150Logo.scale-100.png b/UniversalFtpServer/Assets/Wide310x150Logo.scale-100.png index 8ca9ccd..da045d9 100644 Binary files a/UniversalFtpServer/Assets/Wide310x150Logo.scale-100.png and b/UniversalFtpServer/Assets/Wide310x150Logo.scale-100.png differ diff --git a/UniversalFtpServer/Assets/Wide310x150Logo.scale-125.png b/UniversalFtpServer/Assets/Wide310x150Logo.scale-125.png index 6c7091b..a87a288 100644 Binary files a/UniversalFtpServer/Assets/Wide310x150Logo.scale-125.png and b/UniversalFtpServer/Assets/Wide310x150Logo.scale-125.png differ diff --git a/UniversalFtpServer/Assets/Wide310x150Logo.scale-150.png b/UniversalFtpServer/Assets/Wide310x150Logo.scale-150.png index 6dda6d5..83f8e63 100644 Binary files a/UniversalFtpServer/Assets/Wide310x150Logo.scale-150.png and b/UniversalFtpServer/Assets/Wide310x150Logo.scale-150.png differ diff --git a/UniversalFtpServer/Assets/Wide310x150Logo.scale-200.png b/UniversalFtpServer/Assets/Wide310x150Logo.scale-200.png index 9b5a7cb..7a1dd7d 100644 Binary files a/UniversalFtpServer/Assets/Wide310x150Logo.scale-200.png and b/UniversalFtpServer/Assets/Wide310x150Logo.scale-200.png differ diff --git a/UniversalFtpServer/Assets/Wide310x150Logo.scale-400.png b/UniversalFtpServer/Assets/Wide310x150Logo.scale-400.png index d9251fd..0f0b1dd 100644 Binary files a/UniversalFtpServer/Assets/Wide310x150Logo.scale-400.png and b/UniversalFtpServer/Assets/Wide310x150Logo.scale-400.png differ diff --git a/UniversalFtpServer/PinvokeFilesystem.cs b/UniversalFtpServer/PinvokeFilesystem.cs index 0753c1a..4782bc8 100644 --- a/UniversalFtpServer/PinvokeFilesystem.cs +++ b/UniversalFtpServer/PinvokeFilesystem.cs @@ -157,6 +157,9 @@ static extern IntPtr FindFirstFileExFromApp( [DllImport("api-ms-win-core-file-l1-1-0.dll")] static extern bool FindClose(IntPtr hFindFile); + [DllImport("api-ms-win-core-file-l1-2-1.dll")] + static extern bool SetFileTime(IntPtr hTimeFile, IntPtr lpCreationTime, IntPtr lpLastAccessTime, ref long lpLastWriteTime); + public enum GET_FILEEX_INFO_LEVELS { GetFileExInfoStandard, @@ -198,6 +201,8 @@ public struct WIN32_FILE_ATTRIBUTE_DATA public const uint FILE_SHARE_WRITE = 0x00000002; public const uint FILE_SHARE_DELETE = 0x00000004; + public const uint FILE_WRITE_ATTRIBUTES = 0x00000100; + public const uint CREATE_ALWAYS = 2; public const uint CREATE_NEW = 1; public const uint OPEN_ALWAYS = 4; @@ -244,6 +249,45 @@ public static extern IntPtr CreateFileFromApp( IntPtr hTemplateFile ); + public static bool SetFileModificationTime(string path, DateTime newTime) + { + IntPtr hFile = CreateFileFromApp(path, PinvokeFilesystem.FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero, PinvokeFilesystem.OPEN_EXISTING, (uint)PinvokeFilesystem.File_Attributes.BackupSemantics, IntPtr.Zero); + + long dateTimeLong = newTime.ToFileTime(); + if (hFile.ToInt64() != -1) + { + SetFileTime(hFile, IntPtr.Zero, IntPtr.Zero, ref dateTimeLong); + } + + return true; + } + + public static DateTime GetFileModificationTime(string path) + { + WIN32_FIND_DATA findDataResult; + FINDEX_INFO_LEVELS findInfoLevel = FINDEX_INFO_LEVELS.FindExInfoBasic; + var dateModified = DateTime.MinValue; + + int additionalFlags = 0; + if (Environment.OSVersion.Version.Major >= 6) + { + findInfoLevel = FINDEX_INFO_LEVELS.FindExInfoBasic; + additionalFlags = FIND_FIRST_EX_LARGE_FETCH; + } + + IntPtr hFile = FindFirstFileExFromApp(path, findInfoLevel, out findDataResult, FINDEX_SEARCH_OPS.FindExSearchNameMatch, IntPtr.Zero, additionalFlags); + if (hFile.ToInt64() != -1) + { + long datemodifiedoffset = findDataResult.lastWriteTime.dwHighDateTime; + datemodifiedoffset = (datemodifiedoffset << 32); + datemodifiedoffset = datemodifiedoffset | (long)(uint)findDataResult.lastWriteTime.dwLowDateTime; + dateModified = System.DateTimeOffset.FromFileTime(datemodifiedoffset).ToUniversalTime().DateTime; + FindClose(hFile); + } + + return dateModified; + } + //function public static List GetItems(string path) { diff --git a/UniversalFtpServer/UwpFileProvider.cs b/UniversalFtpServer/UwpFileProvider.cs index b5bde1f..64633c4 100644 --- a/UniversalFtpServer/UwpFileProvider.cs +++ b/UniversalFtpServer/UwpFileProvider.cs @@ -26,6 +26,35 @@ public async Task CreateDirectoryAsync(string path) await RecursivelyCreateDirectoryAsync(fullPath); } + public async Task SetFileModificationTimeAsync(string path, DateTime newTime) + { + path = GetLocalVfsPath(path); + var pathexists = ItemExists(path); + if (pathexists) + { + await Task.Run(() => { PinvokeFilesystem.SetFileModificationTime(path, newTime); }); + return true; + } + else + { + throw new FileNotFoundException(); + } + } + + public async Task GetFileModificationTimeAsync(string path) + { + path = GetLocalVfsPath(path); + var pathexists = ItemExists(path); + if (pathexists) + { + return await Task.Run(() => {return PinvokeFilesystem.GetFileModificationTime(path); }); + } + else + { + throw new FileNotFoundException(); + } + } + public async Task CreateFileForWriteAsync(string path) { path = GetLocalVfsPath(path);