Skip to content
This repository has been archived by the owner on Sep 19, 2023. It is now read-only.

Add MFMT and MDTM features to allow safe syncing with Rclone + New icons #1

Merged
merged 2 commits into from
Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 100 additions & 2 deletions FtpServer/Library/Connections/ControlConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ private enum FtpReplyCode
{
CommandOkay = 200,
SystemStatus = 211,
FileStatus = 213,
CommandUnrecognized = 500,
SyntaxErrorInParametersOrArguments = 501,
NotImplemented = 502,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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.");
}
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions FtpServer/Library/File/IFileProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,20 @@ public interface IFileProvider
/// <exception cref="FileBusyException">The operation failed but worth a retry.</exception>
Task<Stream> CreateFileForWriteAsync(string path);

/// <summary>
/// Sets the last modified time for a file
/// </summary>
/// <param name="path">Absolute or relative FTP path of the file.</param>
/// <returns>Whether the updating the time succeeded or not.</returns>
Task<bool> SetFileModificationTimeAsync(string path, DateTime newTime);

/// <summary>
/// Gets the last modified time for a file
/// </summary>
/// <param name="path">Absolute or relative FTP path of the file.</param>
/// <returns>DateTime of last modification.</returns>
Task<DateTime> GetFileModificationTimeAsync(string path);

/// <summary>
/// Gets the names of files and directories.
/// </summary>
Expand Down
14 changes: 14 additions & 0 deletions FtpServer/Library/File/SimpleFileProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,20 @@ public async Task<Stream> OpenFileForWriteAsync(string path)
return System.IO.File.OpenWrite(localPath);
}

#pragma warning disable CS1998
public async Task<bool> SetFileModificationTimeAsync(string path, DateTime newTime)
#pragma warning restore CS1998
{
throw new NotImplementedException();
}

#pragma warning disable CS1998
public async Task<DateTime> GetFileModificationTimeAsync(string path)
#pragma warning restore CS1998
{
throw new NotImplementedException();
}

/// <summary>
/// Creates a new file for writing.
/// If the file already exists, replace it instead.
Expand Down
Binary file modified UniversalFtpServer/Assets/LargeTile.scale-100.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/LargeTile.scale-125.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/LargeTile.scale-150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/LargeTile.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/LargeTile.scale-400.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SmallTile.scale-100.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SmallTile.scale-125.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SmallTile.scale-150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SmallTile.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SmallTile.scale-400.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SplashScreen.scale-100.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SplashScreen.scale-125.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SplashScreen.scale-150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SplashScreen.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/SplashScreen.scale-400.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square150x150Logo.scale-100.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square150x150Logo.scale-125.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square150x150Logo.scale-150.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square150x150Logo.scale-200.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square150x150Logo.scale-400.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.scale-100.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.scale-125.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.scale-150.png
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.scale-200.png
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.scale-400.png
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.targetsize-16.png
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.targetsize-24.png
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.targetsize-256.png
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.targetsize-32.png
Binary file modified UniversalFtpServer/Assets/Square44x44Logo.targetsize-48.png
Binary file modified UniversalFtpServer/Assets/StoreLogo.scale-100.png
Binary file modified UniversalFtpServer/Assets/StoreLogo.scale-125.png
Binary file modified UniversalFtpServer/Assets/StoreLogo.scale-150.png
Binary file modified UniversalFtpServer/Assets/StoreLogo.scale-200.png
Binary file modified UniversalFtpServer/Assets/StoreLogo.scale-400.png
Binary file modified UniversalFtpServer/Assets/Wide310x150Logo.scale-100.png
Binary file modified UniversalFtpServer/Assets/Wide310x150Logo.scale-125.png
Binary file modified UniversalFtpServer/Assets/Wide310x150Logo.scale-150.png
Binary file modified UniversalFtpServer/Assets/Wide310x150Logo.scale-200.png
Binary file modified UniversalFtpServer/Assets/Wide310x150Logo.scale-400.png
44 changes: 44 additions & 0 deletions UniversalFtpServer/PinvokeFilesystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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<MonitoredFolderItem> GetItems(string path)
{
Expand Down
29 changes: 29 additions & 0 deletions UniversalFtpServer/UwpFileProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,35 @@ public async Task CreateDirectoryAsync(string path)
await RecursivelyCreateDirectoryAsync(fullPath);
}

public async Task<bool> 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<DateTime> 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<Stream> CreateFileForWriteAsync(string path)
{
path = GetLocalVfsPath(path);
Expand Down