Smb save servers, auth
This commit is contained in:
@@ -3,5 +3,6 @@ namespace FileTime.Core.Models
|
|||||||
public static class Constants
|
public static class Constants
|
||||||
{
|
{
|
||||||
public const char SeparatorChar = '/';
|
public const char SeparatorChar = '/';
|
||||||
|
public const string ContentProviderProtocol = "ctp://";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,26 @@ namespace FileTime.Core.Models
|
|||||||
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
|
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
|
||||||
|
|
||||||
Task RefreshAsync(CancellationToken token = default);
|
Task RefreshAsync(CancellationToken token = default);
|
||||||
Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false);
|
async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||||
|
{
|
||||||
|
var paths = path.Split(Constants.SeparatorChar);
|
||||||
|
|
||||||
|
var item = (await GetItems())?.FirstOrDefault(i => i.Name == paths[0]);
|
||||||
|
|
||||||
|
if (paths.Length == 1)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is IContainer container)
|
||||||
|
{
|
||||||
|
var result = await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
||||||
|
return result == null && acceptDeepestMatch ? this : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Task<IContainer> CreateContainer(string name);
|
Task<IContainer> CreateContainer(string name);
|
||||||
Task<IElement> CreateElement(string name);
|
Task<IElement> CreateElement(string name);
|
||||||
|
|
||||||
|
|||||||
12
src/Core/FileTime.Core/Persistence/PersistenceSettings.cs
Normal file
12
src/Core/FileTime.Core/Persistence/PersistenceSettings.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace FileTime.Core.Persistence
|
||||||
|
{
|
||||||
|
public class PersistenceSettings
|
||||||
|
{
|
||||||
|
public PersistenceSettings(string rootAppDataPath)
|
||||||
|
{
|
||||||
|
RootAppDataPath = rootAppDataPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string RootAppDataPath { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
using FileTime.Avalonia.Application;
|
using FileTime.Avalonia.Application;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.Models.Persistence
|
namespace FileTime.Avalonia.Models.Persistence
|
||||||
{
|
{
|
||||||
@@ -11,7 +13,8 @@ namespace FileTime.Avalonia.Models.Persistence
|
|||||||
|
|
||||||
public TabState(TabContainer tab)
|
public TabState(TabContainer tab)
|
||||||
{
|
{
|
||||||
Path = tab.CurrentLocation.Item.FullName;
|
var item = tab.CurrentLocation.Item;
|
||||||
|
Path = item is IContentProvider contentProvider ? Constants.ContentProviderProtocol + contentProvider.Name : item.FullName;
|
||||||
Number = tab.TabNumber;
|
Number = tab.TabNumber;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using FileTime.Core.Components;
|
|||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
using FileTime.Providers.Local;
|
using FileTime.Providers.Local;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.Services
|
namespace FileTime.Avalonia.Services
|
||||||
{
|
{
|
||||||
@@ -23,22 +24,26 @@ namespace FileTime.Avalonia.Services
|
|||||||
private readonly string _settingsPath;
|
private readonly string _settingsPath;
|
||||||
private readonly IEnumerable<IContentProvider> _contentProviders;
|
private readonly IEnumerable<IContentProvider> _contentProviders;
|
||||||
private readonly LocalContentProvider _localContentProvider;
|
private readonly LocalContentProvider _localContentProvider;
|
||||||
|
private readonly ILogger<StatePersistenceService> _logger;
|
||||||
|
|
||||||
public StatePersistenceService(
|
public StatePersistenceService(
|
||||||
AppState appState,
|
AppState appState,
|
||||||
ItemNameConverterService itemNameConverterService,
|
ItemNameConverterService itemNameConverterService,
|
||||||
IEnumerable<IContentProvider> contentProviders,
|
IEnumerable<IContentProvider> contentProviders,
|
||||||
LocalContentProvider localContentProvider)
|
LocalContentProvider localContentProvider,
|
||||||
|
ILogger<StatePersistenceService> logger)
|
||||||
{
|
{
|
||||||
_appState = appState;
|
_appState = appState;
|
||||||
_itemNameConverterService = itemNameConverterService;
|
_itemNameConverterService = itemNameConverterService;
|
||||||
_contentProviders = contentProviders;
|
_contentProviders = contentProviders;
|
||||||
_localContentProvider = localContentProvider;
|
_localContentProvider = localContentProvider;
|
||||||
|
_logger = logger;
|
||||||
_settingsPath = Path.Combine(Program.AppDataRoot, "savedState.json");
|
_settingsPath = Path.Combine(Program.AppDataRoot, "savedState.json");
|
||||||
|
|
||||||
_jsonOptions = new JsonSerializerOptions()
|
_jsonOptions = new JsonSerializerOptions()
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true
|
PropertyNameCaseInsensitive = true,
|
||||||
|
WriteIndented = true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +60,10 @@ namespace FileTime.Avalonia.Services
|
|||||||
await RestoreTabs(state.TabStates);
|
await RestoreTabs(state.TabStates);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Unkown exception while restoring app state.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SaveStates()
|
public void SaveStates()
|
||||||
@@ -96,10 +104,19 @@ namespace FileTime.Avalonia.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
foreach (var tab in tabStates.Tabs)
|
foreach (var tab in tabStates.Tabs)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (tab.Path == null) continue;
|
if (tab.Path == null) continue;
|
||||||
|
|
||||||
IItem? pathItem = null;
|
IItem? pathItem = null;
|
||||||
|
if (tab.Path.StartsWith(Constants.ContentProviderProtocol))
|
||||||
|
{
|
||||||
|
var contentProviderName = tab.Path.Substring(Constants.ContentProviderProtocol.Length);
|
||||||
|
pathItem = _contentProviders.FirstOrDefault(c => c.Name == contentProviderName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
foreach (var contentProvider in _contentProviders)
|
foreach (var contentProvider in _contentProviders)
|
||||||
{
|
{
|
||||||
if (contentProvider.CanHandlePath(tab.Path))
|
if (contentProvider.CanHandlePath(tab.Path))
|
||||||
@@ -108,6 +125,7 @@ namespace FileTime.Avalonia.Services
|
|||||||
if (pathItem != null) break;
|
if (pathItem != null) break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var container = pathItem switch
|
var container = pathItem switch
|
||||||
{
|
{
|
||||||
@@ -125,6 +143,11 @@ namespace FileTime.Avalonia.Services
|
|||||||
await newTabContainer.Init(tab.Number);
|
await newTabContainer.Init(tab.Number);
|
||||||
_appState.Tabs.Add(newTabContainer);
|
_appState.Tabs.Add(newTabContainer);
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Unkown exception while restoring tab. {0}", JsonSerializer.Serialize(tab, _jsonOptions));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_appState.Tabs.FirstOrDefault(t => t.TabNumber == tabStates.ActiveTabNumber) is TabContainer tabContainer)
|
if (_appState.Tabs.FirstOrDefault(t => t.TabNumber == tabStates.ActiveTabNumber) is TabContainer tabContainer)
|
||||||
{
|
{
|
||||||
@@ -133,7 +156,10 @@ namespace FileTime.Avalonia.Services
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch { }
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Unkown exception while restoring tabs.");
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using FileTime.Avalonia.Services;
|
|||||||
using FileTime.Avalonia.ViewModels;
|
using FileTime.Avalonia.ViewModels;
|
||||||
using FileTime.Core.Command;
|
using FileTime.Core.Command;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
|
using FileTime.Core.Persistence;
|
||||||
|
using FileTime.Providers.Smb;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -31,6 +33,8 @@ namespace FileTime.Avalonia
|
|||||||
.AddSingleton<KeyboardConfigurationService>()
|
.AddSingleton<KeyboardConfigurationService>()
|
||||||
.AddSingleton<KeyInputHandlerService>()
|
.AddSingleton<KeyInputHandlerService>()
|
||||||
.AddSingleton<DialogService>()
|
.AddSingleton<DialogService>()
|
||||||
|
.AddSingleton(new PersistenceSettings(Program.AppDataRoot))
|
||||||
|
.AddSmbServices()
|
||||||
.AddSingleton<IIconProvider, MaterialIconProvider>();
|
.AddSingleton<IIconProvider, MaterialIconProvider>();
|
||||||
|
|
||||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
|||||||
@@ -67,12 +67,16 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
|
|
||||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
||||||
|
|
||||||
[Obsolete($"This property is for databinding only, use {nameof(GetContainers)} method instead.")]
|
/*[Obsolete($"This property is for databinding only, use {nameof(GetContainers)} method instead.")]
|
||||||
public ObservableCollection<ContainerViewModel> Containers
|
public ObservableCollection<ContainerViewModel> Containers
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!_isInitialized) Task.Run(Refresh).Wait();
|
if (!_isInitialized) Task.Run(Refresh).Wait();
|
||||||
|
}
|
||||||
|
catch(Exception e) { }
|
||||||
return _containers;
|
return _containers;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -89,8 +93,12 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
public ObservableCollection<ElementViewModel> Elements
|
public ObservableCollection<ElementViewModel> Elements
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!_isInitialized) Task.Run(Refresh).Wait();
|
if (!_isInitialized) Task.Run(Refresh).Wait();
|
||||||
|
}
|
||||||
|
catch(Exception e) { }
|
||||||
return _elements;
|
return _elements;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -106,8 +114,12 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
public ObservableCollection<IItemViewModel> Items
|
public ObservableCollection<IItemViewModel> Items
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!_isInitialized) Task.Run(Refresh).Wait();
|
if (!_isInitialized) Task.Run(Refresh).Wait();
|
||||||
|
}
|
||||||
|
catch(Exception e) { }
|
||||||
return _items;
|
return _items;
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
@@ -118,6 +130,55 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
OnPropertyChanged(nameof(Items));
|
OnPropertyChanged(nameof(Items));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public Task Containers => GetContainers();
|
||||||
|
public Task Elements => GetElements();
|
||||||
|
public Task Items => GetItems();
|
||||||
|
|
||||||
|
public async Task<ObservableCollection<ContainerViewModel>> GetContainers(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||||
|
return _containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ObservableCollection<ElementViewModel>> GetElements(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||||
|
return _elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ObservableCollection<IItemViewModel>> GetItems(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetContainers(ObservableCollection<ContainerViewModel> value)
|
||||||
|
{
|
||||||
|
if (value != _containers)
|
||||||
|
{
|
||||||
|
_containers = value;
|
||||||
|
OnPropertyChanged(nameof(Containers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetElements(ObservableCollection<ElementViewModel> value)
|
||||||
|
{
|
||||||
|
if (value != _elements)
|
||||||
|
{
|
||||||
|
_elements = value;
|
||||||
|
OnPropertyChanged(nameof(Elements));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetItems(ObservableCollection<IItemViewModel> value)
|
||||||
|
{
|
||||||
|
if (value != _items)
|
||||||
|
{
|
||||||
|
_items = value;
|
||||||
|
OnPropertyChanged(nameof(Items));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContainerViewModel(INewItemProcessor newItemProcessor, ContainerViewModel? parent, IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
public ContainerViewModel(INewItemProcessor newItemProcessor, ContainerViewModel? parent, IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||||
@@ -211,14 +272,14 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Containers = new ObservableCollection<ContainerViewModel>(newContainers);
|
SetContainers(new ObservableCollection<ContainerViewModel>(newContainers));
|
||||||
Elements = new ObservableCollection<ElementViewModel>(newElements);
|
SetElements(new ObservableCollection<ElementViewModel>(newElements));
|
||||||
Items = new ObservableCollection<IItemViewModel>(newContainers.Cast<IItemViewModel>().Concat(newElements));
|
SetItems(new ObservableCollection<IItemViewModel>(newContainers.Cast<IItemViewModel>().Concat(newElements)));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < Items.Count; i++)
|
for (var i = 0; i < _items.Count; i++)
|
||||||
{
|
{
|
||||||
Items[i].IsAlternative = i % 2 == 1;
|
_items[i].IsAlternative = i % 2 == 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -293,7 +354,7 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(unloadEvents)
|
if (unloadEvents)
|
||||||
{
|
{
|
||||||
Container.Refreshed.Remove(Container_Refreshed);
|
Container.Refreshed.Remove(Container_Refreshed);
|
||||||
}
|
}
|
||||||
@@ -303,24 +364,6 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
_items.Clear();
|
_items.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ObservableCollection<ContainerViewModel>> GetContainers(CancellationToken token = default)
|
|
||||||
{
|
|
||||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
|
||||||
return _containers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ObservableCollection<ElementViewModel>> GetElements(CancellationToken token = default)
|
|
||||||
{
|
|
||||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
|
||||||
return _elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<ObservableCollection<IItemViewModel>> GetItems(CancellationToken token = default)
|
|
||||||
{
|
|
||||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
|
||||||
return _items;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Dispose()
|
private void Dispose()
|
||||||
{
|
{
|
||||||
if (!_disposed)
|
if (!_disposed)
|
||||||
|
|||||||
@@ -34,7 +34,6 @@
|
|||||||
<Rectangle Fill="#01000000"/>
|
<Rectangle Fill="#01000000"/>
|
||||||
|
|
||||||
<StackPanel Margin="20,10" Orientation="Horizontal">
|
<StackPanel Margin="20,10" Orientation="Horizontal">
|
||||||
<!--TextBlock Text="{Binding AppState.SelectedTab.TabNumber,StringFormat=({0})}" /-->
|
|
||||||
<local:PathPresenter DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}"/>
|
<local:PathPresenter DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}"/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Text="{Binding AppState.SelectedTab.SelectedItem.Item.Name}" Foreground="{StaticResource AccentForegroundBrush}" />
|
Text="{Binding AppState.SelectedTab.SelectedItem.Item.Name}" Foreground="{StaticResource AccentForegroundBrush}" />
|
||||||
@@ -222,9 +221,10 @@
|
|||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<ListBox
|
<ListBox
|
||||||
|
x:CompileBindings="False"
|
||||||
Classes="ContentListView"
|
Classes="ContentListView"
|
||||||
IsEnabled="False"
|
IsEnabled="False"
|
||||||
Items="{Binding AppState.SelectedTab.Parent.Items}">
|
Items="{Binding AppState.SelectedTab.Parent.Items^}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<local:ItemView ShowAttributes="False"/>
|
<local:ItemView ShowAttributes="False"/>
|
||||||
@@ -244,8 +244,9 @@
|
|||||||
<Grid Grid.Column="2">
|
<Grid Grid.Column="2">
|
||||||
<ListBox
|
<ListBox
|
||||||
x:Name="CurrentItems"
|
x:Name="CurrentItems"
|
||||||
|
x:CompileBindings="False"
|
||||||
IsTabStop="True"
|
IsTabStop="True"
|
||||||
Items="{Binding AppState.SelectedTab.CurrentLocation.Items}"
|
Items="{Binding AppState.SelectedTab.CurrentLocation.Items^}"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||||
SelectedItem="{Binding AppState.SelectedTab.SelectedItem, Mode=TwoWay}"
|
SelectedItem="{Binding AppState.SelectedTab.SelectedItem, Mode=TwoWay}"
|
||||||
@@ -258,12 +259,13 @@
|
|||||||
</ListBox>
|
</ListBox>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
x:CompileBindings="False"
|
||||||
x:Name="CurrentEmpty"
|
x:Name="CurrentEmpty"
|
||||||
Margin="10"
|
Margin="10"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
FontWeight="Bold"
|
FontWeight="Bold"
|
||||||
Foreground="{DynamicResource ErrorBrush}"
|
Foreground="{DynamicResource ErrorBrush}"
|
||||||
IsVisible="{Binding AppState.SelectedTab.CurrentLocation.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
IsVisible="{Binding AppState.SelectedTab.CurrentLocation.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||||
Empty
|
Empty
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -282,8 +284,9 @@
|
|||||||
Classes="ContentListView"
|
Classes="ContentListView"
|
||||||
IsEnabled="False"
|
IsEnabled="False"
|
||||||
x:Name="ChildItems"
|
x:Name="ChildItems"
|
||||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
x:CompileBindings="False"
|
||||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
Items="{Binding AppState.SelectedTab.ChildContainer.Items^}"
|
||||||
|
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<local:ItemView/>
|
<local:ItemView/>
|
||||||
@@ -291,7 +294,9 @@
|
|||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
</ListBox>
|
</ListBox>
|
||||||
|
|
||||||
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
<Grid
|
||||||
|
x:CompileBindings="False"
|
||||||
|
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Name="ChildEmpty"
|
x:Name="ChildEmpty"
|
||||||
|
|||||||
@@ -124,7 +124,8 @@ namespace FileTime.Providers.Local
|
|||||||
|
|
||||||
if (item is IContainer container)
|
if (item is IContainer container)
|
||||||
{
|
{
|
||||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
var result = await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
||||||
|
return result == null && acceptDeepestMatch ? this : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -5,9 +5,10 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\Core\FileTime.Core\FileTime.Core.csproj"/>
|
<ProjectReference Include="..\..\Core\FileTime.Core\FileTime.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SMBLibrary" Version="1.4.8"/>
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||||
|
<PackageReference Include="SMBLibrary" Version="1.4.8" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using FileTime.Core.Persistence;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Smb.Persistence
|
||||||
|
{
|
||||||
|
public class PersistenceService
|
||||||
|
{
|
||||||
|
private const string smbFolderName = "smb";
|
||||||
|
private const string serverFileName = "servers.json";
|
||||||
|
private readonly PersistenceSettings _persistenceSettings;
|
||||||
|
private readonly JsonSerializerOptions _jsonOptions;
|
||||||
|
private static readonly byte[] _encryptionKey = {
|
||||||
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
|
0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16
|
||||||
|
};
|
||||||
|
private readonly ILogger<PersistenceService> _logger;
|
||||||
|
|
||||||
|
public PersistenceService(PersistenceSettings persistenceSettings, ILogger<PersistenceService> logger)
|
||||||
|
{
|
||||||
|
_persistenceSettings = persistenceSettings;
|
||||||
|
_logger = logger;
|
||||||
|
|
||||||
|
_jsonOptions = new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true,
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
public async Task SaveServers(IEnumerable<Smb.SmbServer> servers)
|
||||||
|
{
|
||||||
|
ServersPersistenceRoot root;
|
||||||
|
string? encodedIV = null;
|
||||||
|
|
||||||
|
using (Aes aes = Aes.Create())
|
||||||
|
{
|
||||||
|
aes.Key = _encryptionKey;
|
||||||
|
|
||||||
|
encodedIV = Convert.ToBase64String(aes.IV);
|
||||||
|
|
||||||
|
root = new ServersPersistenceRoot()
|
||||||
|
{
|
||||||
|
Key = encodedIV,
|
||||||
|
Servers = servers.Select(s => SaveServer(s, aes)).ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var smbDirectory = new DirectoryInfo(Path.Combine(_persistenceSettings.RootAppDataPath, smbFolderName));
|
||||||
|
if (!smbDirectory.Exists) smbDirectory.Create();
|
||||||
|
|
||||||
|
var serversPath = Path.Combine(_persistenceSettings.RootAppDataPath, smbFolderName, serverFileName);
|
||||||
|
|
||||||
|
using var stream = File.Create(serversPath);
|
||||||
|
await JsonSerializer.SerializeAsync(stream, root, _jsonOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<SmbServer>> LoadServers()
|
||||||
|
{
|
||||||
|
var serverFilePath = Path.Combine(_persistenceSettings.RootAppDataPath, smbFolderName, serverFileName);
|
||||||
|
var servers = new List<SmbServer>();
|
||||||
|
|
||||||
|
if (!new FileInfo(serverFilePath).Exists) return servers;
|
||||||
|
|
||||||
|
using var stream = File.OpenRead(serverFilePath);
|
||||||
|
var serversRoot = await JsonSerializer.DeserializeAsync<ServersPersistenceRoot>(stream);
|
||||||
|
|
||||||
|
if (serversRoot == null) return servers;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(serversRoot.Key))
|
||||||
|
{
|
||||||
|
var iv = Convert.FromBase64String(serversRoot.Key);
|
||||||
|
|
||||||
|
using Aes aes = Aes.Create();
|
||||||
|
foreach (var server in serversRoot.Servers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(server.Password)) continue;
|
||||||
|
|
||||||
|
using var memoryStream = new MemoryStream();
|
||||||
|
memoryStream.Write(Convert.FromBase64String(server.Password));
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
using CryptoStream cryptoStream = new(
|
||||||
|
memoryStream,
|
||||||
|
aes.CreateDecryptor(_encryptionKey, iv),
|
||||||
|
CryptoStreamMode.Read);
|
||||||
|
using StreamReader decryptReader = new(cryptoStream);
|
||||||
|
server.Password = await decryptReader.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
catch(Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Unkown error while decrypting password for {0}", server.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
servers.AddRange(serversRoot.Servers);
|
||||||
|
|
||||||
|
return servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SmbServer SaveServer(Smb.SmbServer server, Aes aes)
|
||||||
|
{
|
||||||
|
var encryptedPassword = "";
|
||||||
|
using (var memoryStream = new MemoryStream())
|
||||||
|
{
|
||||||
|
using CryptoStream cryptoStream = new(
|
||||||
|
memoryStream,
|
||||||
|
aes.CreateEncryptor(),
|
||||||
|
CryptoStreamMode.Write);
|
||||||
|
using StreamWriter encryptWriter = new(cryptoStream);
|
||||||
|
{
|
||||||
|
encryptWriter.Write(server.Password);
|
||||||
|
encryptWriter.Flush();
|
||||||
|
cryptoStream.FlushFinalBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
var a = memoryStream.ToArray();
|
||||||
|
encryptedPassword = Convert.ToBase64String(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SmbServer()
|
||||||
|
{
|
||||||
|
Path = server.Name,
|
||||||
|
Name = server.Name,
|
||||||
|
UserName = server.Username,
|
||||||
|
Password = encryptedPassword
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FileTime.Providers.Smb.Persistence
|
||||||
|
{
|
||||||
|
public class ServersPersistenceRoot
|
||||||
|
{
|
||||||
|
public string Key { get; set; }
|
||||||
|
public List<SmbServer> Servers { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace FileTime.Providers.Smb.Persistence
|
||||||
|
{
|
||||||
|
public class SmbServer
|
||||||
|
{
|
||||||
|
public string Path { get; set; }
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string? UserName { get; set; }
|
||||||
|
public string? Password { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ namespace FileTime.Providers.Smb
|
|||||||
private readonly Func<Task<ISMBClient>> _getSmbClient;
|
private readonly Func<Task<ISMBClient>> _getSmbClient;
|
||||||
private readonly Action _disposeClient;
|
private readonly Action _disposeClient;
|
||||||
private bool _isRunning;
|
private bool _isRunning;
|
||||||
private readonly object _lock = new object();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
public SmbClientContext(Func<Task<ISMBClient>> getSmbClient, Action disposeClient)
|
public SmbClientContext(Func<Task<ISMBClient>> getSmbClient, Action disposeClient)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,17 +3,23 @@ using AsyncEvent;
|
|||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace FileTime.Providers.Smb
|
namespace FileTime.Providers.Smb
|
||||||
{
|
{
|
||||||
public class SmbContentProvider : IContentProvider
|
public class SmbContentProvider : IContentProvider
|
||||||
{
|
{
|
||||||
|
private readonly object _initializationGuard = new object();
|
||||||
|
private bool _initialized;
|
||||||
|
private bool _initializing;
|
||||||
private IContainer? _parent;
|
private IContainer? _parent;
|
||||||
private readonly IInputInterface _inputInterface;
|
private readonly IInputInterface _inputInterface;
|
||||||
private readonly List<IContainer> _rootContainers;
|
private readonly List<IContainer> _rootContainers;
|
||||||
private readonly IReadOnlyList<IContainer> _rootContainersReadOnly;
|
private readonly IReadOnlyList<IContainer> _rootContainersReadOnly;
|
||||||
private IReadOnlyList<IItem>? _items;
|
private IReadOnlyList<IItem> _items;
|
||||||
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
private readonly IReadOnlyList<IElement> _elements = new List<IElement>().AsReadOnly();
|
||||||
|
private readonly Persistence.PersistenceService _persistenceService;
|
||||||
|
private readonly ILogger<SmbContentProvider> _logger;
|
||||||
|
|
||||||
public string Name { get; } = "smb";
|
public string Name { get; } = "smb";
|
||||||
|
|
||||||
@@ -33,12 +39,14 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
public bool IsDestroyed => false;
|
public bool IsDestroyed => false;
|
||||||
|
|
||||||
public SmbContentProvider(IInputInterface inputInterface)
|
public SmbContentProvider(IInputInterface inputInterface, Persistence.PersistenceService persistenceService, ILogger<SmbContentProvider> logger)
|
||||||
{
|
{
|
||||||
_rootContainers = new List<IContainer>();
|
_rootContainers = new List<IContainer>();
|
||||||
_items = new List<IItem>();
|
_items = new List<IItem>();
|
||||||
_rootContainersReadOnly = _rootContainers.AsReadOnly();
|
_rootContainersReadOnly = _rootContainers.AsReadOnly();
|
||||||
_inputInterface = inputInterface;
|
_inputInterface = inputInterface;
|
||||||
|
_persistenceService = persistenceService;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IContainer> CreateContainer(string name)
|
public async Task<IContainer> CreateContainer(string name)
|
||||||
@@ -55,6 +63,8 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
await RefreshAsync();
|
await RefreshAsync();
|
||||||
|
|
||||||
|
await SaveServers();
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +84,7 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
var pathParts = path.TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
var pathParts = path.TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||||
|
|
||||||
var rootContainer = _rootContainers.Find(c => c.Name == pathParts[0]);
|
var rootContainer = (await GetContainers())?.FirstOrDefault(c => c.Name == pathParts[0]);
|
||||||
|
|
||||||
if (rootContainer == null)
|
if (rootContainer == null)
|
||||||
{
|
{
|
||||||
@@ -98,9 +108,19 @@ namespace FileTime.Providers.Smb
|
|||||||
public void SetParent(IContainer container) => _parent = container;
|
public void SetParent(IContainer container) => _parent = container;
|
||||||
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default) => Task.FromResult(_rootContainersReadOnly);
|
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default) => Task.FromResult(_rootContainersReadOnly);
|
||||||
|
|
||||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
|
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_rootContainersReadOnly);
|
{
|
||||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
await Init();
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||||
|
{
|
||||||
|
await Init();
|
||||||
|
return _rootContainersReadOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IElement>?)_elements);
|
||||||
|
|
||||||
public Task Rename(string newName) => throw new NotSupportedException();
|
public Task Rename(string newName) => throw new NotSupportedException();
|
||||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||||
@@ -108,5 +128,54 @@ namespace FileTime.Providers.Smb
|
|||||||
public void Destroy() { }
|
public void Destroy() { }
|
||||||
|
|
||||||
public void Unload() { }
|
public void Unload() { }
|
||||||
|
|
||||||
|
public async Task SaveServers()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _persistenceService.SaveServers(_rootContainers.OfType<SmbServer>());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Unkown error while saving smb server states.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Init()
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
lock (_initializationGuard)
|
||||||
|
{
|
||||||
|
if (!_initializing)
|
||||||
|
{
|
||||||
|
_initializing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Task.Delay(1);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_initialized) return;
|
||||||
|
if (_items.Count > 0) return;
|
||||||
|
_initialized = true;
|
||||||
|
|
||||||
|
var servers = await _persistenceService.LoadServers();
|
||||||
|
foreach (var server in servers)
|
||||||
|
{
|
||||||
|
var smbServer = new SmbServer(server.Path, this, _inputInterface, server.UserName, server.Password);
|
||||||
|
_rootContainers.Add(smbServer);
|
||||||
|
}
|
||||||
|
_items = _rootContainers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock (_initializationGuard)
|
||||||
|
{
|
||||||
|
_initializing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,25 +55,6 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||||
|
|
||||||
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
|
||||||
{
|
|
||||||
var paths = path.Split(Constants.SeparatorChar);
|
|
||||||
|
|
||||||
var item = (await GetItems())?.FirstOrDefault(i => i.Name == paths[0]);
|
|
||||||
|
|
||||||
if (paths.Length == 1)
|
|
||||||
{
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is IContainer container)
|
|
||||||
{
|
|
||||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IContainer? GetParent() => _parent;
|
public IContainer? GetParent() => _parent;
|
||||||
|
|
||||||
public Task<bool> IsExists(string name)
|
public Task<bool> IsExists(string name)
|
||||||
|
|||||||
@@ -10,17 +10,18 @@ namespace FileTime.Providers.Smb
|
|||||||
{
|
{
|
||||||
public class SmbServer : IContainer
|
public class SmbServer : IContainer
|
||||||
{
|
{
|
||||||
private string? _username;
|
private bool _reenterCredentials;
|
||||||
private string? _password;
|
|
||||||
|
|
||||||
private IReadOnlyList<IContainer>? _shares;
|
private IReadOnlyList<IContainer>? _shares;
|
||||||
private IReadOnlyList<IItem>? _items;
|
private IReadOnlyList<IItem>? _items;
|
||||||
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
||||||
private ISMBClient? _client;
|
private ISMBClient? _client;
|
||||||
private readonly object _clientGuard = new object();
|
private readonly object _clientGuard = new();
|
||||||
private bool _refreshingClient;
|
private bool _refreshingClient;
|
||||||
private readonly IInputInterface _inputInterface;
|
private readonly IInputInterface _inputInterface;
|
||||||
private readonly SmbClientContext _smbClientContext;
|
private readonly SmbClientContext _smbClientContext;
|
||||||
|
public string? Username { get; private set; }
|
||||||
|
public string? Password { get; private set; }
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
|
||||||
@@ -42,10 +43,12 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
public bool IsDestroyed => false;
|
public bool IsDestroyed => false;
|
||||||
|
|
||||||
public SmbServer(string path, SmbContentProvider contentProvider, IInputInterface inputInterface)
|
public SmbServer(string path, SmbContentProvider contentProvider, IInputInterface inputInterface, string? username = null, string? password = null)
|
||||||
{
|
{
|
||||||
_inputInterface = inputInterface;
|
_inputInterface = inputInterface;
|
||||||
_smbClientContext = new SmbClientContext(GetSmbClient, DisposeSmbClient);
|
_smbClientContext = new SmbClientContext(GetSmbClient, DisposeSmbClient);
|
||||||
|
Username = username;
|
||||||
|
Password = password;
|
||||||
|
|
||||||
Provider = contentProvider;
|
Provider = contentProvider;
|
||||||
FullName = Name = path;
|
FullName = Name = path;
|
||||||
@@ -53,12 +56,12 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (_shares == null) await RefreshAsync();
|
if (_shares == null) await RefreshAsync(token);
|
||||||
return _shares;
|
return _shares;
|
||||||
}
|
}
|
||||||
public async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
public async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (_shares == null) await RefreshAsync();
|
if (_shares == null) await RefreshAsync(token);
|
||||||
return _shares;
|
return _shares;
|
||||||
}
|
}
|
||||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||||
@@ -81,9 +84,24 @@ namespace FileTime.Providers.Smb
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
var paths = path.Split(Constants.SeparatorChar);
|
||||||
|
|
||||||
|
var item = (await GetItems())!.FirstOrDefault(i => i.Name == paths[0]);
|
||||||
|
|
||||||
|
if (paths.Length == 1)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is IContainer container)
|
||||||
|
{
|
||||||
|
var result = await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
||||||
|
return result == null && acceptDeepestMatch ? this : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IContainer? GetParent() => Provider;
|
public IContainer? GetParent() => Provider;
|
||||||
@@ -152,30 +170,32 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
if (connected)
|
if (connected)
|
||||||
{
|
{
|
||||||
if (_username == null && _password == null)
|
if (_reenterCredentials || Username == null || Password == null)
|
||||||
{
|
{
|
||||||
var inputs = await _inputInterface.ReadInputs(
|
var inputs = await _inputInterface.ReadInputs(
|
||||||
new InputElement[]
|
new InputElement[]
|
||||||
{
|
{
|
||||||
new InputElement($"Username for '{Name}'", InputType.Text),
|
new InputElement($"Username for '{Name}'", InputType.Text, Username ?? ""),
|
||||||
new InputElement($"Password for '{Name}'", InputType.Password)
|
new InputElement($"Password for '{Name}'", InputType.Password, Password ?? "")
|
||||||
});
|
});
|
||||||
|
|
||||||
_username = inputs[0];
|
Username = inputs[0];
|
||||||
_password = inputs[1];
|
Password = inputs[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.Login(string.Empty, _username, _password) != NTStatus.STATUS_SUCCESS)
|
if (client.Login(string.Empty, Username, Password) != NTStatus.STATUS_SUCCESS)
|
||||||
{
|
{
|
||||||
_username = null;
|
_reenterCredentials = true;
|
||||||
_password = null;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
_reenterCredentials = false;
|
||||||
lock (_clientGuard)
|
lock (_clientGuard)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await Provider.SaveServers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace FileTime.Providers.Smb
|
|||||||
private IReadOnlyList<IItem>? _items;
|
private IReadOnlyList<IItem>? _items;
|
||||||
private IReadOnlyList<IContainer>? _containers;
|
private IReadOnlyList<IContainer>? _containers;
|
||||||
private IReadOnlyList<IElement>? _elements;
|
private IReadOnlyList<IElement>? _elements;
|
||||||
private SmbClientContext _smbClientContext;
|
private readonly SmbClientContext _smbClientContext;
|
||||||
private readonly IContainer? _parent;
|
private readonly IContainer? _parent;
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
@@ -45,17 +45,17 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (_items == null) await RefreshAsync();
|
if (_items == null) await RefreshAsync(token);
|
||||||
return _items;
|
return _items;
|
||||||
}
|
}
|
||||||
public async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
public async Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (_containers == null) await RefreshAsync();
|
if (_containers == null) await RefreshAsync(token);
|
||||||
return _containers;
|
return _containers;
|
||||||
}
|
}
|
||||||
public async Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
public async Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||||
{
|
{
|
||||||
if (_elements == null) await RefreshAsync();
|
if (_elements == null) await RefreshAsync(token);
|
||||||
return _elements;
|
return _elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,25 +74,6 @@ namespace FileTime.Providers.Smb
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IItem?> GetByPath(string path, bool acceptDeepestMatch = false)
|
|
||||||
{
|
|
||||||
var paths = path.Split(Constants.SeparatorChar);
|
|
||||||
|
|
||||||
var item = (await GetItems())?.FirstOrDefault(i => i.Name == paths[0]);
|
|
||||||
|
|
||||||
if (paths.Length == 1)
|
|
||||||
{
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is IContainer container)
|
|
||||||
{
|
|
||||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IContainer? GetParent() => _parent;
|
public IContainer? GetParent() => _parent;
|
||||||
|
|
||||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||||
|
|||||||
14
src/Providers/FileTime.Providers.Smb/Startup.cs
Normal file
14
src/Providers/FileTime.Providers.Smb/Startup.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using FileTime.Providers.Smb.Persistence;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Smb
|
||||||
|
{
|
||||||
|
public static class Startup
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddSmbServices(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
return serviceCollection
|
||||||
|
.AddSingleton<PersistenceService>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user