diff --git a/Common/Common.csproj b/Common/Common.csproj index 9e57fd0..cb8f71e 100644 --- a/Common/Common.csproj +++ b/Common/Common.csproj @@ -10,7 +10,7 @@ AnyCPU;x64 - - + + \ No newline at end of file diff --git a/Windows/Common/Core/Common.Windows.Core.csproj b/Windows/Common/Core/Common.Windows.Core.csproj index 2ddd7f1..d805f48 100644 --- a/Windows/Common/Core/Common.Windows.Core.csproj +++ b/Windows/Common/Core/Common.Windows.Core.csproj @@ -1,6 +1,6 @@ - net6.0-windows10.0.19041.0;net48 + net7.0-windows10.0.19041.0;net48 Contains functionality common for all Windows Virtual Drive samples. IT Hit LTD. IT Hit User File System @@ -20,8 +20,8 @@ - - + + \ No newline at end of file diff --git a/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj b/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj index 432caab..5241897 100644 --- a/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj +++ b/Windows/Common/VirtualDrive/Common.Windows.VirtualDrive.csproj @@ -1,6 +1,6 @@ - net6.0-windows10.0.19041.0;net48 + net7.0-windows10.0.19041.0;net48 Contains functionality common for all Windows Virtual Drive samples. IT Hit LTD. IT Hit User File System @@ -13,7 +13,7 @@ - + diff --git a/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj b/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj index efe9ac1..3418223 100644 --- a/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj +++ b/Windows/VirtualDrive/VirtualDrive.ShellExtension/VirtualDrive.ShellExtension.csproj @@ -1,6 +1,6 @@ - net6.0-windows10.0.19041.0 + net7.0-windows10.0.19041.0 True x64 @@ -19,7 +19,7 @@ - + diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj b/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj index 3218022..15b79aa 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj +++ b/Windows/VirtualDrive/VirtualDrive/VirtualDrive.csproj @@ -1,7 +1,7 @@ Exe - net6.0-windows10.0.19041.0 + net7.0-windows10.0.19041.0 IT Hit LTD. IT Hit LTD. Virtual Drive @@ -40,7 +40,7 @@ This is an advanced project with ETags support, Microsoft Office documents editi - + diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs index 47a4ffa..471ce09 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFile.cs @@ -90,11 +90,11 @@ public async Task ValidateDataAsync(long offset, long length, IValidateDataOpera } /// - public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return; + if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return null; // Send the ETag to the server as part of the update to ensure // the file in the remote storge is not modified since last read. @@ -129,15 +129,36 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, } // Update remote storage file metadata. - remoteStorageItem.Attributes = fileMetadata.Attributes & ~FileAttributes.ReadOnly; - remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; - remoteStorageItem.Attributes = fileMetadata.Attributes; + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value & ~FileAttributes.ReadOnly; + } + + if (fileBasicInfo.CreationTime.HasValue) + { + remoteStorageItem.CreationTimeUtc = fileBasicInfo.CreationTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastWriteTime.HasValue) + { + remoteStorageItem.LastWriteTimeUtc = fileBasicInfo.LastWriteTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastAccessTime.HasValue) + { + remoteStorageItem.LastAccessTimeUtc = fileBasicInfo.LastAccessTime.Value.UtcDateTime; + } + + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value; + } // Save ETag received from your remote storage in persistent placeholder properties. // string newEtag = ... // placeholder.SetETag(newEtag); + + return null; } } } diff --git a/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs b/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs index fdbcd4a..ea28b09 100644 --- a/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs +++ b/Windows/VirtualDrive/VirtualDrive/VirtualFolder.cs @@ -168,19 +168,40 @@ public async Task> EnumerateChildrenAsync } /// - public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return; + if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return null; DirectoryInfo remoteStorageItem = new DirectoryInfo(remoteStoragePath); // Update remote storage folder metadata. - remoteStorageItem.Attributes = folderMetadata.Attributes & ~FileAttributes.ReadOnly; - remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; - remoteStorageItem.Attributes = folderMetadata.Attributes; + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value & ~FileAttributes.ReadOnly; + } + + if (fileBasicInfo.CreationTime.HasValue) + { + remoteStorageItem.CreationTimeUtc = fileBasicInfo.CreationTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastWriteTime.HasValue) + { + remoteStorageItem.LastWriteTimeUtc = fileBasicInfo.LastWriteTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastAccessTime.HasValue) + { + remoteStorageItem.LastAccessTimeUtc = fileBasicInfo.LastAccessTime.Value.UtcDateTime; + } + + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value; + } + + return null; } diff --git a/Windows/VirtualDrive/VirtualDrive/appsettings.json b/Windows/VirtualDrive/VirtualDrive/appsettings.json index 15853cb..b9041b0 100644 --- a/Windows/VirtualDrive/VirtualDrive/appsettings.json +++ b/Windows/VirtualDrive/VirtualDrive/appsettings.json @@ -23,7 +23,7 @@ // Your virtual file system will be mounted under this path. // Make sure to delete the all plceholders created by previous version of the software under the sync root. - "UserFileSystemRootPath": "%USERPROFILE%\\VirtualDriveV7\\", + "UserFileSystemRootPath": "%USERPROFILE%\\VirtualDriveV73\\", // Full synchronization interval in milliseconds. diff --git a/Windows/VirtualFileSystem/RemoteStorageMonitor.cs b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs index 7725593..31a17f4 100644 --- a/Windows/VirtualFileSystem/RemoteStorageMonitor.cs +++ b/Windows/VirtualFileSystem/RemoteStorageMonitor.cs @@ -317,7 +317,7 @@ internal static bool IsModified(string userFileSystemPath, string remoteStorageP FileInfo fiUserFileSystem = new FileInfo(userFileSystemPath); FileInfo fiRemoteStorage = new FileInfo(remoteStoragePath); - // This check is to prevent circular calls. In you real app you wouuld not send notifications to the client that generated the event. + // This check is to prevent circular calls. In your real app you would not send notifications to the client that generated the event. if (fiUserFileSystem.LastWriteTimeUtc >= fiRemoteStorage.LastWriteTimeUtc) { return false; @@ -328,7 +328,7 @@ internal static bool IsModified(string userFileSystemPath, string remoteStorageP if (fiUserFileSystem.Length == fiRemoteStorage.Length) { // Verify that the file is not offline, - // therwise the file will be hydrated when the file stream is opened. + // otherwise the file will be hydrated when the file stream is opened. if (fiUserFileSystem.Attributes.HasFlag(System.IO.FileAttributes.Offline) || fiUserFileSystem.Attributes.HasFlag(System.IO.FileAttributes.Offline)) { diff --git a/Windows/VirtualFileSystem/VirtualFile.cs b/Windows/VirtualFileSystem/VirtualFile.cs index 30b0dc9..997cc49 100644 --- a/Windows/VirtualFileSystem/VirtualFile.cs +++ b/Windows/VirtualFileSystem/VirtualFile.cs @@ -83,11 +83,11 @@ public async Task ValidateDataAsync(long offset, long length, IValidateDataOpera } /// - public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return; + if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return null; FileInfo remoteStorageItem = new FileInfo(remoteStoragePath); @@ -102,11 +102,32 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, } // Update remote storage file metadata. - remoteStorageItem.Attributes = fileMetadata.Attributes & ~FileAttributes.ReadOnly; - remoteStorageItem.CreationTimeUtc = fileMetadata.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; - remoteStorageItem.Attributes = fileMetadata.Attributes; + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value & ~FileAttributes.ReadOnly; + } + + if (fileBasicInfo.CreationTime.HasValue) + { + remoteStorageItem.CreationTimeUtc = fileBasicInfo.CreationTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastWriteTime.HasValue) + { + remoteStorageItem.LastWriteTimeUtc = fileBasicInfo.LastWriteTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastAccessTime.HasValue) + { + remoteStorageItem.LastAccessTimeUtc = fileBasicInfo.LastAccessTime.Value.UtcDateTime; + } + + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value; + } + + return null; } } } diff --git a/Windows/VirtualFileSystem/VirtualFileSystem.csproj b/Windows/VirtualFileSystem/VirtualFileSystem.csproj index f565ca6..68cdefd 100644 --- a/Windows/VirtualFileSystem/VirtualFileSystem.csproj +++ b/Windows/VirtualFileSystem/VirtualFileSystem.csproj @@ -1,7 +1,7 @@  Exe - net6.0-windows10.0.19041.0;net48 + net7.0-windows10.0.19041.0;net48 IT Hit LTD. IT Hit LTD. Virtual File System diff --git a/Windows/VirtualFileSystem/VirtualFolder.cs b/Windows/VirtualFileSystem/VirtualFolder.cs index 9c06b43..dbd4a0b 100644 --- a/Windows/VirtualFileSystem/VirtualFolder.cs +++ b/Windows/VirtualFileSystem/VirtualFolder.cs @@ -141,19 +141,40 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo } /// - public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); - if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return; + if (!Mapping.TryGetRemoteStoragePathById(RemoteStorageItemId, out string remoteStoragePath)) return null; DirectoryInfo remoteStorageItem = new DirectoryInfo(remoteStoragePath); // Update remote storage folder metadata. - remoteStorageItem.Attributes = folderMetadata.Attributes & ~FileAttributes.ReadOnly; - remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; - remoteStorageItem.Attributes = folderMetadata.Attributes; + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value & ~FileAttributes.ReadOnly; + } + + if (fileBasicInfo.CreationTime.HasValue) + { + remoteStorageItem.CreationTimeUtc = fileBasicInfo.CreationTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastWriteTime.HasValue) + { + remoteStorageItem.LastWriteTimeUtc = fileBasicInfo.LastWriteTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastAccessTime.HasValue) + { + remoteStorageItem.LastAccessTimeUtc = fileBasicInfo.LastAccessTime.Value.UtcDateTime; + } + + if (fileBasicInfo.Attributes.HasValue) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value; + } + + return null; } } diff --git a/Windows/VirtualFileSystem/appsettings.json b/Windows/VirtualFileSystem/appsettings.json index 5e6b7ee..5149ffe 100644 --- a/Windows/VirtualFileSystem/appsettings.json +++ b/Windows/VirtualFileSystem/appsettings.json @@ -23,7 +23,7 @@ // Your virtual file system will be mounted under this path. // Make sure to delete the all plceholders created by previous version of the software under the sync root. - "UserFileSystemRootPath": "%USERPROFILE%\\VFSv7\\" + "UserFileSystemRootPath": "%USERPROFILE%\\VFSv73\\" // To test performance: diff --git a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj index fa211cc..1b70623 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj +++ b/Windows/WebDAVDrive/WebDAVDrive.ShellExtension/WebDAVDrive.ShellExtension.csproj @@ -1,6 +1,6 @@ - net6.0-windows10.0.19041.0 + net7.0-windows10.0.19041.0 True x64 @@ -12,7 +12,7 @@ IT HIT LTD. - + diff --git a/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj b/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj index 4153509..478a4a1 100644 --- a/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj +++ b/Windows/WebDAVDrive/WebDAVDrive.UI/WebDAVDrive.UI.csproj @@ -1,7 +1,7 @@  - net6.0-windows10.0.19041.0 + net7.0-windows10.0.19041.0 true True IT Hit LTD. diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs index aaba42e..0d1fbc6 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFile.cs @@ -97,7 +97,7 @@ public async Task ValidateDataAsync(long offset, long length, IValidateDataOpera } /// - public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); @@ -144,6 +144,8 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, inSyncResultContext.SetInSync = false; } } + + return null; } } } diff --git a/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs b/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs index 844cf1e..a8028b8 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs +++ b/Windows/WebDAVDrive/WebDAVDrive/VirtualFolder.cs @@ -144,10 +144,11 @@ public async Task> EnumerateChildrenAsync } /// - public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { // Typically we can not change any folder metadata on a WebDAV server, just logging the call. Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", UserFileSystemPath, default, operationContext); + return null; } /// diff --git a/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj b/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj index 91c9e18..2093b13 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj +++ b/Windows/WebDAVDrive/WebDAVDrive/WebDAVDrive.csproj @@ -1,7 +1,7 @@ Exe - net6.0-windows10.0.19041.0 + net7.0-windows10.0.19041.0 IT Hit LTD. IT Hit LTD. WebDAV Drive @@ -39,7 +39,7 @@ - + diff --git a/Windows/WebDAVDrive/WebDAVDrive/appsettings.json b/Windows/WebDAVDrive/WebDAVDrive/appsettings.json index 14a3664..5535720 100644 --- a/Windows/WebDAVDrive/WebDAVDrive/appsettings.json +++ b/Windows/WebDAVDrive/WebDAVDrive/appsettings.json @@ -40,7 +40,7 @@ //Your virtual file system will be mounted under this path. - "UserFileSystemRootPath": "%USERPROFILE%\\DAVv7\\", + "UserFileSystemRootPath": "%USERPROFILE%\\DAVv73\\", // Automatic lock timout in milliseconds. Automatic lock will be extended (refreshed) when this period is about to expire. diff --git a/macOS/Common/Core/Common.Core.csproj b/macOS/Common/Core/Common.Core.csproj index 7ee3cfe..850c48e 100644 --- a/macOS/Common/Core/Common.Core.csproj +++ b/macOS/Common/Core/Common.Core.csproj @@ -19,6 +19,6 @@ None - + diff --git a/macOS/Common/Core/ConsoleLogger.cs b/macOS/Common/Core/ConsoleLogger.cs index f0ed7f4..62124f2 100644 --- a/macOS/Common/Core/ConsoleLogger.cs +++ b/macOS/Common/Core/ConsoleLogger.cs @@ -39,7 +39,6 @@ public void LogError(IEngine sender, EngineErrorEventArgs e) { LogError(e.Message, sourcePath: e.SourcePath, targetPath: e.TargetPath, ex: e.Exception, operationContext: e.OperationContext, callerLineNumber: e.CallerLineNumber, callerMemberName: e.CallerMemberName, callerFilePath: e.CallerFilePath); - //WriteLog(sender, e, log4net.Core.Level.Error); } public void LogMessage(IEngine sender, EngineMessageEventArgs e) @@ -61,7 +60,7 @@ public void LogError(string message, string sourcePath = null, string targetPath public void LogMessage(string message, string sourcePath = null, string targetPath = null, IOperationContext operationContext = null, [CallerLineNumber] int callerLineNumber = 0, [CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null) { - LogWithLevel(string.Empty, $"\n{DateTimeOffset.Now} [{Thread.CurrentThread.ManagedThreadId,2}] {ComponentName,-26}{message,-45} {sourcePath,-80} {targetPath}"); + LogWithLevel(string.Empty, $"\n{ComponentName,-26}{message,-45} {sourcePath,-80} {targetPath}"); } private void LogError(string str, Exception ex) @@ -86,7 +85,7 @@ public ILogger CreateLogger(string componentName) public void LogDebug(string message, string sourcePath = null, string targetPath = null, IOperationContext operationContext = null, [CallerLineNumber] int callerLineNumber = 0, [CallerMemberName] string callerMemberName = null, [CallerFilePath] string callerFilePath = null) { - LogDebug($"\n{DateTimeOffset.Now} [{Thread.CurrentThread.ManagedThreadId,2}] {ComponentName,-26}{message,-45} {sourcePath,-80} {targetPath}"); + LogDebug($"\n{ComponentName,-26}{message,-45} {sourcePath,-80} {targetPath}"); } diff --git a/macOS/UserFileSystemSamplesMac.sln b/macOS/UserFileSystemSamplesMac.sln index 797b007..aa722c5 100644 --- a/macOS/UserFileSystemSamplesMac.sln +++ b/macOS/UserFileSystemSamplesMac.sln @@ -21,12 +21,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebDAVMacApp", "WebDAVDrive EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebDAVFileProviderExtension", "WebDAVDrive\WebDAVFileProviderExtension\WebDAVFileProviderExtension.csproj", "{2EAE2CFB-2220-4599-A462-20973AA1E4E3}" EndProject -Project("{9344BDBB-3E7F-41FC-A0DD-8665D75EE146}") = "FileProviderExtension", "VirtualFileSystem\FileProviderExtension\FileProviderExtension.csproj", "{B8E37561-0C38-432E-ACC2-34D5530F350E}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileProviderExtension", "VirtualFileSystem\FileProviderExtension\FileProviderExtension.csproj", "{B8E37561-0C38-432E-ACC2-34D5530F350E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebDAVFileProviderUIExtension", "WebDAVDrive\WebDAVFileProviderUIExtension\WebDAVFileProviderUIExtension.csproj", "{5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {E16FE844-A1C6-4C2E-A920-D0C0B8A1A47D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU @@ -37,6 +41,10 @@ Global {07FF7DBA-27A0-417C-89F6-27E04E51F8DD}.Debug|Any CPU.Build.0 = Debug|Any CPU {07FF7DBA-27A0-417C-89F6-27E04E51F8DD}.Release|Any CPU.ActiveCfg = Release|Any CPU {07FF7DBA-27A0-417C-89F6-27E04E51F8DD}.Release|Any CPU.Build.0 = Release|Any CPU + {07FF7DBA-27A0-417C-89F6-27E04E51F8DD}.Debug|x64.ActiveCfg = Debug|Any CPU + {07FF7DBA-27A0-417C-89F6-27E04E51F8DD}.Debug|x64.Build.0 = Debug|Any CPU + {07FF7DBA-27A0-417C-89F6-27E04E51F8DD}.Release|x64.ActiveCfg = Release|Any CPU + {07FF7DBA-27A0-417C-89F6-27E04E51F8DD}.Release|x64.Build.0 = Release|Any CPU {FC3C471C-1B0E-4CAD-A084-408525EA86A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC3C471C-1B0E-4CAD-A084-408525EA86A7}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC3C471C-1B0E-4CAD-A084-408525EA86A7}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -45,6 +53,10 @@ Global {867899D4-8931-44FE-A554-122C2D65616C}.Debug|Any CPU.Build.0 = Debug|Any CPU {867899D4-8931-44FE-A554-122C2D65616C}.Release|Any CPU.ActiveCfg = Release|Any CPU {867899D4-8931-44FE-A554-122C2D65616C}.Release|Any CPU.Build.0 = Release|Any CPU + {867899D4-8931-44FE-A554-122C2D65616C}.Debug|x64.ActiveCfg = Debug|Any CPU + {867899D4-8931-44FE-A554-122C2D65616C}.Debug|x64.Build.0 = Debug|Any CPU + {867899D4-8931-44FE-A554-122C2D65616C}.Release|x64.ActiveCfg = Release|Any CPU + {867899D4-8931-44FE-A554-122C2D65616C}.Release|x64.Build.0 = Release|Any CPU {427510AC-F886-4521-AC23-E74E5665B309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {427510AC-F886-4521-AC23-E74E5665B309}.Debug|Any CPU.Build.0 = Debug|Any CPU {427510AC-F886-4521-AC23-E74E5665B309}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -53,6 +65,10 @@ Global {B1EBA44C-F695-440C-A7B7-8D05CA72EF3A}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1EBA44C-F695-440C-A7B7-8D05CA72EF3A}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1EBA44C-F695-440C-A7B7-8D05CA72EF3A}.Release|Any CPU.Build.0 = Release|Any CPU + {B1EBA44C-F695-440C-A7B7-8D05CA72EF3A}.Debug|x64.ActiveCfg = Debug|Any CPU + {B1EBA44C-F695-440C-A7B7-8D05CA72EF3A}.Debug|x64.Build.0 = Debug|Any CPU + {B1EBA44C-F695-440C-A7B7-8D05CA72EF3A}.Release|x64.ActiveCfg = Release|Any CPU + {B1EBA44C-F695-440C-A7B7-8D05CA72EF3A}.Release|x64.Build.0 = Release|Any CPU {B23C34D8-3EF8-4627-9425-30243E1A4223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B23C34D8-3EF8-4627-9425-30243E1A4223}.Debug|Any CPU.Build.0 = Debug|Any CPU {B23C34D8-3EF8-4627-9425-30243E1A4223}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -65,18 +81,42 @@ Global {DF0B99D0-BA4D-4F24-8759-0BA9905F377E}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF0B99D0-BA4D-4F24-8759-0BA9905F377E}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF0B99D0-BA4D-4F24-8759-0BA9905F377E}.Release|Any CPU.Build.0 = Release|Any CPU + {DF0B99D0-BA4D-4F24-8759-0BA9905F377E}.Debug|x64.ActiveCfg = Debug|Any CPU + {DF0B99D0-BA4D-4F24-8759-0BA9905F377E}.Debug|x64.Build.0 = Debug|Any CPU + {DF0B99D0-BA4D-4F24-8759-0BA9905F377E}.Release|x64.ActiveCfg = Release|Any CPU + {DF0B99D0-BA4D-4F24-8759-0BA9905F377E}.Release|x64.Build.0 = Release|Any CPU {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Release|Any CPU.Build.0 = Release|Any CPU + {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Debug|x64.ActiveCfg = Debug|Any CPU + {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Debug|x64.Build.0 = Debug|Any CPU + {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Release|x64.ActiveCfg = Release|Any CPU + {E9CFD15E-6885-41BA-AAC5-1AC95247940B}.Release|x64.Build.0 = Release|Any CPU {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Release|Any CPU.ActiveCfg = Release|Any CPU {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Release|Any CPU.Build.0 = Release|Any CPU + {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Debug|x64.ActiveCfg = Debug|Any CPU + {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Debug|x64.Build.0 = Debug|Any CPU + {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Release|x64.ActiveCfg = Release|Any CPU + {2EAE2CFB-2220-4599-A462-20973AA1E4E3}.Release|x64.Build.0 = Release|Any CPU {B8E37561-0C38-432E-ACC2-34D5530F350E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B8E37561-0C38-432E-ACC2-34D5530F350E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8E37561-0C38-432E-ACC2-34D5530F350E}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8E37561-0C38-432E-ACC2-34D5530F350E}.Release|Any CPU.Build.0 = Release|Any CPU + {B8E37561-0C38-432E-ACC2-34D5530F350E}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8E37561-0C38-432E-ACC2-34D5530F350E}.Debug|x64.Build.0 = Debug|Any CPU + {B8E37561-0C38-432E-ACC2-34D5530F350E}.Release|x64.ActiveCfg = Release|Any CPU + {B8E37561-0C38-432E-ACC2-34D5530F350E}.Release|x64.Build.0 = Release|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Release|Any CPU.Build.0 = Release|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Debug|x64.ActiveCfg = Debug|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Debug|x64.Build.0 = Debug|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Release|x64.ActiveCfg = Release|Any CPU + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -98,5 +138,6 @@ Global {E9CFD15E-6885-41BA-AAC5-1AC95247940B} = {34CB985B-1D15-4E17-8FA5-3FAFB4D26DA9} {2EAE2CFB-2220-4599-A462-20973AA1E4E3} = {34CB985B-1D15-4E17-8FA5-3FAFB4D26DA9} {B8E37561-0C38-432E-ACC2-34D5530F350E} = {036FC7F3-0FEC-497D-867F-3F67A465B6B8} + {5839C7B6-0F26-4E6A-8B26-FC9C1B8B4957} = {34CB985B-1D15-4E17-8FA5-3FAFB4D26DA9} EndGlobalSection EndGlobal diff --git a/macOS/VirtualFileSystem/FileProviderExtension/FileProviderExtension.csproj b/macOS/VirtualFileSystem/FileProviderExtension/FileProviderExtension.csproj index 5176b6f..b7e0e0a 100644 --- a/macOS/VirtualFileSystem/FileProviderExtension/FileProviderExtension.csproj +++ b/macOS/VirtualFileSystem/FileProviderExtension/FileProviderExtension.csproj @@ -74,9 +74,9 @@ - - - + + + diff --git a/macOS/VirtualFileSystem/FileProviderExtension/VirtualFile.cs b/macOS/VirtualFileSystem/FileProviderExtension/VirtualFile.cs index 6cf887d..4756cd7 100644 --- a/macOS/VirtualFileSystem/FileProviderExtension/VirtualFile.cs +++ b/macOS/VirtualFileSystem/FileProviderExtension/VirtualFile.cs @@ -34,7 +34,7 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferDa } /// - public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", RemoteStoragePath); @@ -49,11 +49,29 @@ public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, remoteStorageStream.SetLength(content.Length); } - remoteStorageItem.LastWriteTimeUtc = fileMetadata.LastWriteTime.UtcDateTime; + if (fileBasicInfo.LastWriteTime != null) + { + remoteStorageItem.LastWriteTimeUtc = fileBasicInfo.LastWriteTime.Value.UtcDateTime; + } + } + + // Update remote storage file metadata. + if (fileBasicInfo.Attributes != null) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value; + } + + if (fileBasicInfo.CreationTime != null) + { + remoteStorageItem.CreationTimeUtc = fileBasicInfo.CreationTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastAccessTime != null) + { + remoteStorageItem.LastAccessTimeUtc = fileBasicInfo.LastAccessTime.Value.UtcDateTime; } - // Update remote storage file metadata. - remoteStorageItem.LastAccessTimeUtc = fileMetadata.LastAccessTime.UtcDateTime; + return await GetMetadataAsync() as IFileMetadata; } } } diff --git a/macOS/VirtualFileSystem/FileProviderExtension/VirtualFileSystemItem.cs b/macOS/VirtualFileSystem/FileProviderExtension/VirtualFileSystemItem.cs index f3db8e2..bb87cdc 100644 --- a/macOS/VirtualFileSystem/FileProviderExtension/VirtualFileSystemItem.cs +++ b/macOS/VirtualFileSystem/FileProviderExtension/VirtualFileSystemItem.cs @@ -74,7 +74,7 @@ public async Task DeleteAsync(IOperationContext operationContext = null, IConfir } /// - public async Task GetMetadataAsync() + public async Task GetMetadataAsync(IResultContext resultContext = null) { // Return IFileMetadata for a file, IFolderMetadata for a folder. diff --git a/macOS/VirtualFileSystem/FileProviderExtension/VirtualFolder.cs b/macOS/VirtualFileSystem/FileProviderExtension/VirtualFolder.cs index 74f6cf8..a25a78c 100644 --- a/macOS/VirtualFileSystem/FileProviderExtension/VirtualFolder.cs +++ b/macOS/VirtualFileSystem/FileProviderExtension/VirtualFolder.cs @@ -90,18 +90,34 @@ public async Task GetChildrenAsync(string pattern, IOperationContext operationCo /// - public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", RemoteStoragePath); DirectoryInfo remoteStorageItem = new DirectoryInfo(RemoteStoragePath); // Update remote storage folder metadata. - remoteStorageItem.Attributes = folderMetadata.Attributes; - remoteStorageItem.CreationTimeUtc = folderMetadata.CreationTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; - remoteStorageItem.LastAccessTimeUtc = folderMetadata.LastAccessTime.UtcDateTime; - remoteStorageItem.LastWriteTimeUtc = folderMetadata.LastWriteTime.UtcDateTime; + if (fileBasicInfo.Attributes != null) + { + remoteStorageItem.Attributes = fileBasicInfo.Attributes.Value; + } + + if (fileBasicInfo.CreationTime != null) + { + remoteStorageItem.CreationTimeUtc = fileBasicInfo.CreationTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastWriteTime != null) + { + remoteStorageItem.LastWriteTimeUtc = fileBasicInfo.LastWriteTime.Value.UtcDateTime; + } + + if (fileBasicInfo.LastAccessTime != null) + { + remoteStorageItem.LastAccessTimeUtc = fileBasicInfo.LastAccessTime.Value.UtcDateTime; + } + + return await GetMetadataAsync() as IFolderMetadata; } } diff --git a/macOS/VirtualFileSystem/VirtualFileSystemMacApp/VirtualFileSystemMacApp.csproj b/macOS/VirtualFileSystem/VirtualFileSystemMacApp/VirtualFileSystemMacApp.csproj index d710a02..ad042b2 100644 --- a/macOS/VirtualFileSystem/VirtualFileSystemMacApp/VirtualFileSystemMacApp.csproj +++ b/macOS/VirtualFileSystem/VirtualFileSystemMacApp/VirtualFileSystemMacApp.csproj @@ -135,7 +135,7 @@ - - + + diff --git a/macOS/WebDAVDrive/WebDAVCommon/SecureStorage.cs b/macOS/WebDAVDrive/WebDAVCommon/SecureStorage.cs new file mode 100644 index 0000000..5ac6ea6 --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVCommon/SecureStorage.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Text.Json; +using CoreWlan; +using Security; + +namespace WebDAVCommon +{ + public class SecureStorage + { + public const string ExtensionIdentifier = "com.webdav.vfs.app"; + public const string ExtensionDisplayName = "IT Hit WebDAV Drive"; + + private const string AppGroupId = "65S3A9JQ35.group.com.webdav.vfs"; + private const string InternalSettingFile = "data.out"; + + /// + /// Writes value to storage. + /// + /// Key. + /// Value. + /// + /// + public async Task SetAsync(string key, string value) + { + if (string.IsNullOrWhiteSpace(key)) + { + throw new ArgumentNullException(nameof(key)); + } + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + // Because secure storage requires provisioning profile, in case of the development mode, we store credentials in external file. + string userDataPath = Path.Combine(GetSharedContainerPath(), InternalSettingFile); + Dictionary userData = File.Exists(userDataPath) ? JsonSerializer.Deserialize>(await File.ReadAllTextAsync(userDataPath)) : new Dictionary(); + + if (userData.ContainsKey(key)) + { + userData[key] = value; + } + else + { + userData.Add(key, value); + } + + await File.WriteAllTextAsync(userDataPath, JsonSerializer.Serialize(userData)); + } + + /// + /// Returns value by key. + /// + /// Key. + /// + public async Task GetAsync(string key) + { + string userDataPath = Path.Combine(GetSharedContainerPath(), InternalSettingFile); + if (File.Exists(userDataPath)) + { + Dictionary userData = JsonSerializer.Deserialize>(await File.ReadAllTextAsync(userDataPath)); + + if (userData.ContainsKey(key)) + { + return userData[key]; + } + else + { + return null; + } + } + else + { + return null; + } + } + + + public string GetSharedContainerPath() + { + return NSFileManager.DefaultManager.GetContainerUrl(AppGroupId)?.Path; + } + + /// + /// Triggers log-in button in file manager. + /// + public async Task RequireAuthenticationAsync() + { + await SetAsync("LoginType", "RequireAuthentication"); + } + } +} + diff --git a/macOS/WebDAVDrive/WebDAVCommon/WebDAVCommon.csproj b/macOS/WebDAVDrive/WebDAVCommon/WebDAVCommon.csproj index b42dc88..41da35e 100644 --- a/macOS/WebDAVDrive/WebDAVCommon/WebDAVCommon.csproj +++ b/macOS/WebDAVDrive/WebDAVCommon/WebDAVCommon.csproj @@ -45,4 +45,7 @@ + + + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualEngine.cs b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualEngine.cs index ab8d9be..33684cc 100644 --- a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualEngine.cs +++ b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualEngine.cs @@ -1,13 +1,17 @@ using System; using System.IO; +using System.Net; +using System.Net.Mail; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using AuthenticationServices; using Common.Core; using FileProvider; using Foundation; using ITHit.FileSystem; using ITHit.FileSystem.Mac; +using ITHit.FileSystem.Mac.Contexts; using ITHit.WebDAV.Client; using WebDAVCommon; @@ -21,6 +25,11 @@ public class VirtualEngine : EngineMac /// public WebDavSession WebDavSession; + /// + /// Secure Storage. + /// + public SecureStorage SecureStorage; + /// /// Automatic lock timout in milliseconds. /// @@ -51,9 +60,10 @@ public VirtualEngine(NSFileProviderDomain domain) Error += consolelogger.LogError; Message += consolelogger.LogMessage; Debug += consolelogger.LogDebug; - - WebDavSession = new WebDavSession(AppGroupSettings.Settings.Value.WebDAVClientLicense); - WebDavSession.CustomHeaders.Add("InstanceId", Environment.MachineName); + + SecureStorage = new SecureStorage(); + + InitWebDavSession(); AutoLock = AppGroupSettings.Settings.Value.AutoLock; AutoLockTimoutMs = AppGroupSettings.Settings.Value.AutoLockTimoutMs; @@ -65,6 +75,25 @@ public VirtualEngine(NSFileProviderDomain domain) Logger.LogMessage("Engine started."); } + /// + /// Initializes WebDAV session. + /// + internal void InitWebDavSession() + { + if(WebDavSession != null) + { + WebDavSession.Dispose(); + } + WebDavSession = new WebDavSession(AppGroupSettings.Settings.Value.WebDAVClientLicense); + WebDavSession.CustomHeaders.Add("InstanceId", Environment.MachineName); + + string loginType = SecureStorage.GetAsync("LoginType").Result; + if (!string.IsNullOrEmpty(loginType) && loginType.Equals("UserNamePassword")) + { + WebDavSession.Credentials = new NetworkCredential(SecureStorage.GetAsync("UserName").Result, SecureStorage.GetAsync("Password").Result); + } + } + /// public override async Task GetFileSystemItemAsync(byte[] remoteStorageItemId, FileSystemItemType itemType, IContext context, ILogger logger = null) { @@ -105,7 +134,29 @@ public override async Task GetMenuCommandAsync(Guid menuGuid, IOpe /// public async Task GetRootStorageItemIdAsync() { - return (await new VirtualFolder(Encoding.UTF8.GetBytes(AppGroupSettings.Settings.Value.WebDAVServerUrl), this, Logger).GetMetadataAsync()).RemoteStorageItemId; + Logger.LogMessage($"{nameof(VirtualEngine)}.{nameof(GetRootStorageItemIdAsync)}()"); + try + { + return (await new VirtualFolder(Encoding.UTF8.GetBytes(AppGroupSettings.Settings.Value.WebDAVServerUrl), this, Logger).GetMetadataAsync())?.RemoteStorageItemId; + } + catch (ITHit.WebDAV.Client.Exceptions.WebDavHttpException httpException) + { + Logger.LogError($"{nameof(VirtualEngine)}.{nameof(GetRootStorageItemIdAsync)}()", ex: httpException); + switch (httpException.Status.Code) + { + // Challenge-responce auth: Basic, Digest, NTLM or Kerberos + case 401: + // Set login type to display sing in button in Finder. + await SecureStorage.RequireAuthenticationAsync(); + return null; + } + return null; + } + catch (Exception ex) + { + Logger.LogError($"{nameof(VirtualEngine)}.{nameof(GetRootStorageItemIdAsync)}()", ex: ex); + return null; + } } @@ -120,5 +171,12 @@ protected override void Stop() { Logger.LogMessage($"{nameof(IEngine)}.{nameof(Stop)}()"); } + + public override async Task IsAuthenticatedAsync() + { + Logger.LogMessage($"{nameof(IEngine)}.{nameof(IsAuthenticatedAsync)}()"); + string loginType = await SecureStorage.GetAsync("LoginType"); + return string.IsNullOrEmpty(loginType) || loginType != "RequireAuthentication"; + } } } diff --git a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFile.cs b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFile.cs index 1cb630d..f9e4cb4 100644 --- a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFile.cs +++ b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFile.cs @@ -1,6 +1,7 @@ using FileProviderExtension.Extensions; using ITHit.FileSystem; using ITHit.FileSystem.Mac; +using WebDAVCommon; using Client = ITHit.WebDAV.Client; namespace WebDAVFileProviderExtension @@ -40,13 +41,17 @@ public async Task ReadAsync(Stream output, long offset, long length, ITransferDa // Operation was canceled. Logger.LogMessage($"{nameof(ReadAsync)}({offset}, {length}) canceled", RemoteStorageUriById.AbsoluteUri, default); } + catch (Client.Exceptions.WebDavHttpException httpException) + { + HandleWebExceptions(httpException, resultContext); + } } } } /// - public async Task WriteAsync(IFileMetadata fileMetadata, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, Stream content = null, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(IFile)}.{nameof(WriteAsync)}()", RemoteStorageUriById.AbsoluteUri, default, operationContext); @@ -75,20 +80,26 @@ await Engine.WebDavSession.UploadAsync(RemoteStorageUriById, async (outputStream content.Position = 0; // Setting position to 0 is required in case of retry. await content.CopyToAsync(outputStream); }, null, content.Length, 0, -1, lockTokens, eTag, null, cancellationToken); + + + // macOS requires last modification date on the server to match the client. If date does not match, the file will be redownloaded. + // Here we use property name indetical to Microsoft Windows Explorer for max interability. + if (fileBasicInfo.LastWriteTime.HasValue) + { + Client.IFile file = (await Engine.WebDavSession.GetFileAsync(RemoteStorageUriById.AbsoluteUri, null, cancellationToken)).WebDavResponse; + Client.Property[] propsToAddAndUpdate = new Client.Property[1]; + propsToAddAndUpdate[0] = new Client.Property(new Client.PropertyName("Win32LastModifiedTime", "urn:schemas-microsoft-com:"), fileBasicInfo.LastWriteTime.ToString()); + + await file.UpdatePropertiesAsync(propsToAddAndUpdate, null, lockTokens?.FirstOrDefault()?.LockToken); + } } catch (Client.Exceptions.PreconditionFailedException) { Logger.LogMessage($"Conflict. The item is modified.", RemoteStorageUriById.AbsoluteUri, default, operationContext); - } - - // macOS requires last modification date on the server to match the client. If date does not match, the file will be redownloaded. - // Here we use property name indetical to Microsoft Windows Explorer for max interability. - Client.IFile file = (await Engine.WebDavSession.GetFileAsync(RemoteStorageUriById.AbsoluteUri, null, cancellationToken)).WebDavResponse; - Client.Property[] propsToAddAndUpdate = new Client.Property[1]; - propsToAddAndUpdate[0] = new Client.Property(new Client.PropertyName("Win32LastModifiedTime", "urn:schemas-microsoft-com:"), fileMetadata.LastWriteTime.ToString()); - - await file.UpdatePropertiesAsync(propsToAddAndUpdate, null, lockTokens?.FirstOrDefault()?.LockToken); + } } + + return await GetMetadataAsync() as IFileMetadata; } } } diff --git a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFileSystemItem.cs b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFileSystemItem.cs index d3ac1ea..8f882a1 100644 --- a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFileSystemItem.cs +++ b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFileSystemItem.cs @@ -1,3 +1,5 @@ +using System.Net; +using System.Net.Mail; using System.Text; using System.Threading; using ITHit.FileSystem; @@ -73,7 +75,7 @@ public async Task DeleteAsync(IOperationContext operationContext = null, IConfir } /// - public async Task GetMetadataAsync() + public async Task GetMetadataAsync(IResultContext resultContext = null) { Logger.LogMessage($"{nameof(IFileSystemItem)}.{nameof(GetMetadataAsync)}()", RemoteStorageUriById.AbsoluteUri); @@ -87,12 +89,16 @@ public async Task DeleteAsync(IOperationContext operationContext = null, IConfir // Return IFileMetadata for a file, IFolderMetadata for a folder. item = (await Engine.WebDavSession.GetItemAsync(RemoteStorageUriById, propNames)).WebDavResponse; } - catch (ITHit.WebDAV.Client.Exceptions.NotFoundException e) + catch (Client.Exceptions.NotFoundException e) { Logger.LogError($"{nameof(IFileSystemItem)}.{nameof(GetMetadataAsync)}()", RemoteStorageUriById.AbsoluteUri, ex: e); item = null; } + catch (Client.Exceptions.WebDavHttpException httpException) + { + HandleWebExceptions(httpException, resultContext); + } return item != null ? Mapping.GetUserFileSystemItemMetadata(item) : null; } @@ -163,12 +169,37 @@ public Task> GetPropertiesAsync(IOperati throw new NotImplementedException(); } + protected async void HandleWebExceptions(Client.Exceptions.WebDavHttpException webDavHttpException, IResultContext resultContext) + { + switch (webDavHttpException.Status.Code) + { + // Challenge-responce auth: Basic, Digest, NTLM or Kerberos + case 401: + + if (Engine.WebDavSession.Credentials == null || !(Engine.WebDavSession.Credentials is NetworkCredential) || + (Engine.WebDavSession.Credentials as NetworkCredential).UserName != await Engine.SecureStorage.GetAsync("UserName")) + { + // Set login type to display sing in button in Finder. + await Engine.SecureStorage.RequireAuthenticationAsync(); + if (resultContext != null) + { + resultContext.ReportStatus(CloudFileStatus.STATUS_CLOUD_FILE_AUTHENTICATION_FAILED); + } + } + else + { + // Reset WebDavSession. + Engine.InitWebDavSession(); + } + break; + } + } + public async Task LockAsync(LockMode lockMode, IOperationContext operationContext = null, CancellationToken cancellationToken = default) { Logger.LogMessage($"{nameof(ILock)}.{nameof(LockAsync)}()", RemoteStorageUriById.AbsoluteUri, default, operationContext); // Call your remote storage here to lock the item. - // Save the lock token and other lock info received from the remote storage on the client. // Supply the lock-token as part of each remote storage update in IFile.WriteAsync() method. // Note that the actual lock timout applied by the server may be different from the one requested. diff --git a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFolder.cs b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFolder.cs index 4590184..20ef6b5 100644 --- a/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFolder.cs +++ b/macOS/WebDAVDrive/WebDAVFileProviderExtension/VirtualFolder.cs @@ -9,6 +9,8 @@ using ITHit.FileSystem.Synchronization; using System.Text; +using ITHit.WebDAV.Client.Exceptions; +using WebDAVCommon; namespace WebDAVFileProviderExtension { @@ -37,7 +39,8 @@ public async Task CreateFileAsync(IFileMetadata fileMetadata, Stream? co // Create a new file in the remote storage. long contentLength = content != null ? content.Length : 0; - Client.IWebDavResponse response = await Engine.WebDavSession.UploadAsync(newFileUri, async (outputStream) => { + Client.IWebDavResponse response = await Engine.WebDavSession.UploadAsync(newFileUri, async (outputStream) => + { if (content != null) { // Setting position to 0 is required in case of retry. @@ -67,31 +70,40 @@ public async Task CreateFolderAsync(IFolderMetadata folderMetadata, IInS /// public async Task GetChildrenAsync(string pattern, IOperationContext operationContext, IFolderListingResultContext resultContext, CancellationToken cancellationToken) { - Logger.LogMessage($"{nameof(IFolder)}.{nameof(GetChildrenAsync)}({pattern})", RemoteStorageUriById.AbsoluteUri); + try + { + Logger.LogMessage($"{nameof(IFolder)}.{nameof(GetChildrenAsync)}({pattern})", RemoteStorageUriById.AbsoluteUri); - Client.PropertyName[] propNames = new Client.PropertyName[2]; - propNames[0] = new Client.PropertyName("resource-id", "DAV:"); - propNames[1] = new Client.PropertyName("parent-resource-id", "DAV:"); + Client.PropertyName[] propNames = new Client.PropertyName[2]; + propNames[0] = new Client.PropertyName("resource-id", "DAV:"); + propNames[1] = new Client.PropertyName("parent-resource-id", "DAV:"); + + IList remoteStorageChildren = (await Engine.WebDavSession.GetChildrenAsync(RemoteStorageUriById, false, propNames, null, cancellationToken)).WebDavResponse; - IList remoteStorageChildren = (await Engine.WebDavSession.GetChildrenAsync(RemoteStorageUriById, false, propNames, null, cancellationToken)).WebDavResponse; + List userFileSystemChildren = new List(); + foreach (Client.IHierarchyItem remoteStorageItem in remoteStorageChildren) + { + IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); + userFileSystemChildren.Add(itemInfo); + } - List userFileSystemChildren = new List(); - foreach (Client.IHierarchyItem remoteStorageItem in remoteStorageChildren) + // To signal that the children enumeration is completed + // always call ReturnChildren(), even if the folder is empty. + await resultContext.ReturnChildrenAsync(userFileSystemChildren.ToArray(), userFileSystemChildren.Count); + } + catch (WebDavHttpException httpException) { - IFileSystemItemMetadata itemInfo = Mapping.GetUserFileSystemItemMetadata(remoteStorageItem); - userFileSystemChildren.Add(itemInfo); + HandleWebExceptions(httpException, resultContext); } - - // To signal that the children enumeration is completed - // always call ReturnChildren(), even if the folder is empty. - await resultContext.ReturnChildrenAsync(userFileSystemChildren.ToArray(), userFileSystemChildren.Count); } /// - public async Task WriteAsync(IFolderMetadata folderMetadata, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) + public async Task WriteAsync(IFileSystemBasicInfo fileBasicInfo, IOperationContext operationContext = null, IInSyncResultContext inSyncResultContext = null, CancellationToken cancellationToken = default) { // Typically we can not change any folder metadata on a WebDAV server, just logging the call. Logger.LogMessage($"{nameof(IFolder)}.{nameof(WriteAsync)}()", RemoteStorageUriById.AbsoluteUri, default, operationContext); + + return await GetMetadataAsync() as IFolderMetadata; } /// @@ -128,5 +140,5 @@ public async Task GetChangesAsync(string syncToken, bool deep, long? l return changes; } } - + } diff --git a/macOS/WebDAVDrive/WebDAVFileProviderExtension/WebDAVFileProviderExtension.csproj b/macOS/WebDAVDrive/WebDAVFileProviderExtension/WebDAVFileProviderExtension.csproj index c77e3d1..fea13a9 100644 --- a/macOS/WebDAVDrive/WebDAVFileProviderExtension/WebDAVFileProviderExtension.csproj +++ b/macOS/WebDAVDrive/WebDAVFileProviderExtension/WebDAVFileProviderExtension.csproj @@ -76,11 +76,11 @@ - + - - + + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Entitlements.plist b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Entitlements.plist new file mode 100644 index 0000000..a62467a --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Entitlements.plist @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.application-groups + + 65S3A9JQ35.group.com.webdav.vfs + + + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Extension/NSViewExtensions.cs b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Extension/NSViewExtensions.cs new file mode 100644 index 0000000..963237c --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Extension/NSViewExtensions.cs @@ -0,0 +1,43 @@ +namespace WebDAVFileProviderUIExtension.Extension +{ + public static class NSViewExtensions + { + public static TView SetBackgroundColor(this TView view, NSColor color) + where TView : NSView + { + return view.SetBackgroundColor(color.CGColor); + } + + public static TView SetBackgroundColor(this TView view, CGColor color) + where TView : NSView + { + view.EnsureWantsLayer(); + if (view.Layer != null) + { + view.Layer.BackgroundColor = color; + } + + return view; + } + + public static TView EnsureWantsLayer(this TView view) + where TView : NSView + { + view.WantsLayer = true; + return view; + } + + public static NSButton SetColoredTitle( + this NSButton button, + string text, + NSColor color) + { + button.Title = text; + + button.AttributedTitle = text.CreateAttributedString(color); + + return button; + } + } +} + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Extension/StringExtensions.cs b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Extension/StringExtensions.cs new file mode 100644 index 0000000..f600e73 --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Extension/StringExtensions.cs @@ -0,0 +1,16 @@ +namespace WebDAVFileProviderUIExtension.Extension +{ + public static class StringExtensions + { + public static NSMutableAttributedString CreateAttributedString( + this string text, + NSColor color) + { + var attributedString = new NSMutableAttributedString(text); + var range = new NSRange(0, text.Length); + attributedString.AddAttribute(NSStringAttributeKey.ForegroundColor, color, range); + return attributedString; + } + } +} + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/FPUIActionExtension.cs b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/FPUIActionExtension.cs new file mode 100644 index 0000000..87200a1 --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/FPUIActionExtension.cs @@ -0,0 +1,31 @@ +using Common.Core; +using FileProvider; +using ITHit.FileSystem.Mac; +using WebDAVCommon; +using WebDAVFileProviderUIExtension.ViewControllers; + +namespace WebDAVFileProviderUIExtension; + +[Register("FPUIActionExtension")] +public class FPUIActionExtension : FPUIActionExtensionViewControllerMac +{ + public FPUIActionExtension() : base(NSFileProviderManager.FromDomain(new NSFileProviderDomain(SecureStorage.ExtensionIdentifier, SecureStorage.ExtensionDisplayName)), + new ConsoleLogger(typeof(FPUIActionExtension).Name)) + { } + + /// + public override async Task GetMenuCommandAsync(Guid menuGuid, IEnumerable remoteStorageItemIds, IMacFPUIActionExtensionContext context) + { + Logger.LogMessage($"{nameof(FPUIActionExtension)}.{nameof(GetMenuCommandAsync)}()"); + + return new ContectMenuUIViewController(context); + } + + /// + public override async Task RequireAuthenticationAsync(IMacFPUIActionExtensionContext context) + { + Logger.LogMessage($"{nameof(FPUIActionExtension)}.{nameof(RequireAuthenticationAsync)}()"); + + return new AuthViewController(context); + } +} diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Info.plist b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Info.plist new file mode 100644 index 0000000..ce2548b --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Info.plist @@ -0,0 +1,57 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + WebDAVFileProviderUIExtension + CFBundleIdentifier + com.webdav.vfs.app.extension.ui + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + WebDAVFileProviderUIExtension + CFBundlePackageType + XPC! + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + 10.14 + LSUIElement + + NSAppTransportSecurity + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + NSExtension + + NSExtensionFileProviderActions + + + NSExtensionFileProviderActionActivationRule + TRUEPREDICATE + NSExtensionFileProviderActionIdentifier + 18d0e0cb-5df7-432c-a06f-297c06b5ec6d + NSExtensionFileProviderActionName + Action UI + + + NSExtensionPointIdentifier + com.apple.fileprovider-actionsui + NSExtensionPrincipalClass + FPUIActionExtension + NSExtensionFileProviderDocumentGroup + 65S3A9JQ35.group.com.webdav.vfs + + + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Resources/LaunchScreen.xib b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Resources/LaunchScreen.xib new file mode 100644 index 0000000..772defd --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/Resources/LaunchScreen.xib @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/ViewControllers/AuthViewController.cs b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/ViewControllers/AuthViewController.cs new file mode 100644 index 0000000..445a7a2 --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/ViewControllers/AuthViewController.cs @@ -0,0 +1,135 @@ +using Common.Core; +using ITHit.FileSystem.Mac; +using ObjCRuntime; +using WebDAVCommon; +using WebDAVFileProviderUIExtension.Extension; + +namespace WebDAVFileProviderUIExtension.ViewControllers +{ + public class AuthViewController : NSViewController + { + private ConsoleLogger logger = new ConsoleLogger(typeof(AuthViewController).Name); + private SecureStorage secureStorage = new SecureStorage(); + + private NSTextField loginTextField = new() + { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + private NSSecureTextField passwordTextField = new() + { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + private NSButton authenticationButton = new() + { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + private IMacFPUIActionExtensionContext extensionContext; + + public AuthViewController(IMacFPUIActionExtensionContext extensionContext) : base(nameof(AuthViewController), null) + { + this.extensionContext = extensionContext; + } + + protected AuthViewController(NativeHandle handle) : base(handle) + { + // This constructor is required if the view controller is loaded from a xib or a storyboard. + // Do not put any initialization here, use ViewDidLoad instead. + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (loginTextField != null) + { + loginTextField.Dispose(); + loginTextField = null; + } + + if (passwordTextField != null) + { + passwordTextField.Dispose(); + passwordTextField = null; + } + + if (authenticationButton != null) + { + authenticationButton.Dispose(); + authenticationButton = null; + } + } + + base.Dispose(disposing); + } + + public override void LoadView() + { + NSView view = new NSView() + { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + authenticationButton.Activated += OnAuthenticationButtonActivated; + + loginTextField.SetBackgroundColor(NSColor.White); + passwordTextField.SetBackgroundColor(NSColor.White); + authenticationButton.SetBackgroundColor(NSColor.White); + + loginTextField.Cell.PlaceholderAttributedString = "Login".CreateAttributedString(NSColor.Gray); + passwordTextField.Cell.PlaceholderAttributedString = "Password".CreateAttributedString(NSColor.Gray); + authenticationButton.SetColoredTitle("Authenticate", NSColor.Black); + + loginTextField.TextColor = NSColor.Black; + passwordTextField.TextColor = NSColor.Black; + + + view.AddSubview(loginTextField); + view.AddSubview(passwordTextField); + view.AddSubview(authenticationButton); + + View = view; + + const int padding = 20; + + NSLayoutConstraint.ActivateConstraints(new[] { + loginTextField.HeightAnchor.ConstraintEqualTo(25f), + loginTextField.LeadingAnchor.ConstraintEqualTo(view.LeadingAnchor, padding), + loginTextField.TopAnchor.ConstraintEqualTo(view.TopAnchor, padding), + loginTextField.TrailingAnchor.ConstraintEqualTo(view.TrailingAnchor, -padding), + + passwordTextField.HeightAnchor.ConstraintEqualTo(25f), + passwordTextField.LeadingAnchor.ConstraintEqualTo(view.LeadingAnchor, padding), + passwordTextField.TopAnchor.ConstraintEqualTo(loginTextField.BottomAnchor, padding), + passwordTextField.TrailingAnchor.ConstraintEqualTo(view.TrailingAnchor, -padding), + + authenticationButton.HeightAnchor.ConstraintEqualTo(25f), + authenticationButton.CenterXAnchor.ConstraintEqualTo(view.CenterXAnchor), + authenticationButton.LeadingAnchor.ConstraintGreaterThanOrEqualTo(view.LeadingAnchor, padding * 3), + authenticationButton.TrailingAnchor.ConstraintGreaterThanOrEqualTo(view.TrailingAnchor, -padding), + authenticationButton.TopAnchor.ConstraintEqualTo(passwordTextField.BottomAnchor, padding * 3), + authenticationButton.BottomAnchor.ConstraintEqualTo(view.BottomAnchor, -padding) + }); + } + + private void OnAuthenticationButtonActivated(object? sender, EventArgs e) + { + try + { + logger.LogDebug($"OnAuthenticationButtonActivated: send signal to file provider."); + + secureStorage.SetAsync("LoginType", "UserNamePassword").Wait(); + secureStorage.SetAsync("UserName", loginTextField.StringValue).Wait(); + secureStorage.SetAsync("Password", passwordTextField.StringValue).Wait(); + extensionContext.CompleteRequest(); + } + catch(Exception ex) + { + logger.LogError($"OnAuthenticationButtonActivated: {ex.Message}", ex: ex); + } + } + } +} + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/ViewControllers/ContectMenuUIViewController.cs b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/ViewControllers/ContectMenuUIViewController.cs new file mode 100644 index 0000000..3765408 --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/ViewControllers/ContectMenuUIViewController.cs @@ -0,0 +1,109 @@ +using Common.Core; +using ITHit.FileSystem.Mac; +using ObjCRuntime; +using WebDAVCommon; +using WebDAVFileProviderUIExtension.Extension; + +namespace WebDAVFileProviderUIExtension.ViewControllers +{ + public class ContectMenuUIViewController : NSViewController + { + private ConsoleLogger logger = new ConsoleLogger(typeof(AuthViewController).Name); + + private NSTextField loginTextField = new() + { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + private NSButton closeButton = new() + { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + private IMacFPUIActionExtensionContext extensionContext; + + public ContectMenuUIViewController(IMacFPUIActionExtensionContext extensionContext) : base(nameof(AuthViewController), null) + { + this.extensionContext = extensionContext; + } + + protected ContectMenuUIViewController(NativeHandle handle) : base(handle) + { + // This constructor is required if the view controller is loaded from a xib or a storyboard. + // Do not put any initialization here, use ViewDidLoad instead. + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (loginTextField != null) + { + loginTextField.Dispose(); + loginTextField = null; + } + + + if (closeButton != null) + { + closeButton.Dispose(); + closeButton = null; + } + } + + base.Dispose(disposing); + } + + public override void LoadView() + { + NSView view = new NSView() + { + TranslatesAutoresizingMaskIntoConstraints = false + }; + + closeButton.Activated += OnCloseButtonActivated; + + loginTextField.SetBackgroundColor(NSColor.White); + closeButton.SetBackgroundColor(NSColor.White); + + loginTextField.Cell.PlaceholderAttributedString = "Custom Textbox".CreateAttributedString(NSColor.Gray); + closeButton.SetColoredTitle("Close", NSColor.Black); + + loginTextField.TextColor = NSColor.Black; + + view.AddSubview(loginTextField); + view.AddSubview(closeButton); + + View = view; + + const int padding = 20; + + NSLayoutConstraint.ActivateConstraints(new[] { + loginTextField.HeightAnchor.ConstraintEqualTo(25f), + loginTextField.LeadingAnchor.ConstraintEqualTo(view.LeadingAnchor, padding), + loginTextField.TopAnchor.ConstraintEqualTo(view.TopAnchor, padding), + loginTextField.TrailingAnchor.ConstraintEqualTo(view.TrailingAnchor, -padding), + + closeButton.HeightAnchor.ConstraintEqualTo(25f), + closeButton.CenterXAnchor.ConstraintEqualTo(view.CenterXAnchor), + closeButton.LeadingAnchor.ConstraintGreaterThanOrEqualTo(view.LeadingAnchor, padding * 3), + closeButton.TrailingAnchor.ConstraintGreaterThanOrEqualTo(view.TrailingAnchor, -padding), + closeButton.BottomAnchor.ConstraintEqualTo(view.BottomAnchor, -padding) + }); + } + + private void OnCloseButtonActivated(object? sender, EventArgs e) + { + try + { + logger.LogDebug($"OnCloseButtonActivated: send signal to file provider."); + extensionContext.CompleteRequest(); + } + catch(Exception ex) + { + logger.LogError($"OnCloseButtonActivated: {ex.Message}", ex: ex); + } + } + } +} + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/WebDAVFileProviderUIExtension.csproj b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/WebDAVFileProviderUIExtension.csproj new file mode 100644 index 0000000..0b89efe --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/WebDAVFileProviderUIExtension.csproj @@ -0,0 +1,95 @@ + + + net7.0-macos + Library + enable + true + 10.14 + True + false + false + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + false + false + true + true + + Full + None + + + + AfterBuild + bash ${ProjectDir}/sign.sh ${TargetDir} + + + AfterBuild + codesign --force --sign - -o runtime --entitlements ${ProjectDir}/Entitlements.plist --timestamp --generate-entitlement-der ${TargetDir}/WebDAVFileProviderUIExtension.appex + + + + + + pdbonly + true + bin\Release + + prompt + 4 + False + true + false + true + true + true + Entitlements.plist + None + + None + 10.0 + true + + + + AfterBuild + bash ${ProjectDir}/sign_release.sh ${TargetDir} + + + AfterBuild + codesign --force --sign "Mac Developer" -o runtime --entitlements ${ProjectDir}/Entitlements.plist --timestamp --generate-entitlement-der ${TargetDir}/WebDAVFileProviderUIExtension.appex + + + + + + + + + + + + $([MSBuild]::MakeRelative ('$(MSBuildProjectDirectory)', '%(Identity)')) + + + $([MSBuild]::MakeRelative ('$(MSBuildProjectDirectory)', '%(Identity)')) + + + + + + + + + + + + diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/sign.sh b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/sign.sh new file mode 100644 index 0000000..80f4b45 --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/sign.sh @@ -0,0 +1 @@ +find $1/WebDAVFileProviderUIExtension.appex -iname '*.dylib' | while read libfile ; do codesign --force --sign - -o runtime --timestamp "${libfile}" ; done ; \ No newline at end of file diff --git a/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/sign_release.sh b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/sign_release.sh new file mode 100644 index 0000000..21b22bc --- /dev/null +++ b/macOS/WebDAVDrive/WebDAVFileProviderUIExtension/sign_release.sh @@ -0,0 +1 @@ +find $1/WebDAVFileProviderUIExtension.appex -iname '*.dylib' | while read libfile ; do codesign --force --sign "Mac Developer" -o runtime --timestamp "${libfile}" ; done ; \ No newline at end of file diff --git a/macOS/WebDAVDrive/WebDAVMacApp/AppDelegate.cs b/macOS/WebDAVDrive/WebDAVMacApp/AppDelegate.cs index aea7a25..c8278a2 100644 --- a/macOS/WebDAVDrive/WebDAVMacApp/AppDelegate.cs +++ b/macOS/WebDAVDrive/WebDAVMacApp/AppDelegate.cs @@ -13,8 +13,6 @@ public class AppDelegate : NSApplicationDelegate { private ILogger Logger = new ConsoleLogger("WebDavFileProviderHostApp"); private RemoteStorageMonitor RemoteStorageMonitor = new RemoteStorageMonitor(AppGroupSettings.Settings.Value.WebSocketServerUrl, new ConsoleLogger(typeof(RemoteStorageMonitor).Name)); - private string ExtensionIdentifier = "com.webdav.vfs.app"; - private string ExtensionDisplayName = "IT Hit WebDAV Drive"; private NSMenuItem InstallMenuItem = new NSMenuItem("Install WebDAV FS Extension"); private NSMenuItem UninstallMenuItem = new NSMenuItem("Uninstall WebDAV FS Extension"); private NSStatusItem StatusItem; @@ -27,7 +25,7 @@ public override void DidFinishLaunching(NSNotification notification) { NSMenu menu = new NSMenu(); - Task taskIsExtensionRegistered = Task.Run(async () => await Common.Core.Registrar.IsRegisteredAsync(ExtensionIdentifier)); + Task taskIsExtensionRegistered = Task.Run(async () => await Common.Core.Registrar.IsRegisteredAsync(SecureStorage.ExtensionIdentifier)); bool isExtensionRegistered = taskIsExtensionRegistered.Result; if (isExtensionRegistered) { @@ -35,7 +33,7 @@ public override void DidFinishLaunching(NSNotification notification) Task.Run(async () => { RemoteStorageMonitor.ServerNotifications = new ServerNotifications( - NSFileProviderManager.FromDomain(new NSFileProviderDomain(ExtensionIdentifier, ExtensionDisplayName)), + NSFileProviderManager.FromDomain(new NSFileProviderDomain(SecureStorage.ExtensionIdentifier, SecureStorage.ExtensionDisplayName)), RemoteStorageMonitor.Logger); await RemoteStorageMonitor.StartAsync(); }).Wait(); @@ -45,6 +43,7 @@ public override void DidFinishLaunching(NSNotification notification) InstallMenuItem.Activated += Install; } + NSMenuItem logoutMenuItem = new NSMenuItem("Log out", (_, _) => { }); NSMenuItem exitMenuItem = new NSMenuItem("Quit", (a, b) => { NSApplication.SharedApplication.Terminate(this); }); menu.AddItem(InstallMenuItem); @@ -71,7 +70,7 @@ private void Install(object? sender, EventArgs e) Process.Start("open", AppGroupSettings.Settings.Value.WebDAVServerUrl); Task.Run(async () => { - NSFileProviderDomain domain = await Common.Core.Registrar.RegisterAsync(ExtensionIdentifier, ExtensionDisplayName, Logger); + NSFileProviderDomain domain = await Common.Core.Registrar.RegisterAsync(SecureStorage.ExtensionIdentifier, SecureStorage.ExtensionDisplayName, Logger); RemoteStorageMonitor.ServerNotifications = new ServerNotifications(NSFileProviderManager.FromDomain(domain), RemoteStorageMonitor.Logger); await RemoteStorageMonitor.StartAsync(); }).Wait(); @@ -86,7 +85,7 @@ private void Uninstall(object? sender, EventArgs e) Task.Run(async () => { await RemoteStorageMonitor.StopAsync(); - await Common.Core.Registrar.UnregisterAsync(ExtensionIdentifier, Logger); + await Common.Core.Registrar.UnregisterAsync(SecureStorage.ExtensionIdentifier, Logger); }).Wait(); InstallMenuItem.Activated += Install; diff --git a/macOS/WebDAVDrive/WebDAVMacApp/WebDAVMacApp.csproj b/macOS/WebDAVDrive/WebDAVMacApp/WebDAVMacApp.csproj index 0910ba9..3694a56 100644 --- a/macOS/WebDAVDrive/WebDAVMacApp/WebDAVMacApp.csproj +++ b/macOS/WebDAVDrive/WebDAVMacApp/WebDAVMacApp.csproj @@ -28,6 +28,10 @@ AfterBuild cp -rf "${ProjectDir}/../WebDAVFileProviderExtension/bin/Debug/WebDAVFileProviderExtension.appex" "${TargetDir}/WebDAV Drive.app/Contents/PlugIns" + + AfterBuild + cp -rf "${ProjectDir}/../WebDAVFileProviderUIExtension/bin/Debug/WebDAVFileProviderUIExtension.appex" "${TargetDir}/WebDAV Drive.app/Contents/PlugIns" + AfterBuild codesign --force --sign - --entitlements ${ProjectDir}/Entitlements.plist --timestamp --generate-entitlement-der "${TargetDir}/WebDAV Drive.app" @@ -137,15 +141,16 @@ - + - + + - - + +