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);