Smb write, delete, fixes, error handling

This commit is contained in:
2022-02-16 12:14:19 +01:00
parent f809f0a640
commit 6f2a3d0dc4
21 changed files with 395 additions and 72 deletions

View File

@@ -169,8 +169,16 @@ namespace FileTime.ConsoleUI.App
{ {
IList<AbsolutePath>? itemsToDelete = null; IList<AbsolutePath>? itemsToDelete = null;
var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.ResolveAsync()).ToList(); var currentSelectedItems = new List<IItem>();
var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem(); foreach (var item in await _tabStates[_selectedTab!].GetCurrentMarkedItems())
{
var resolvedItem = await item.ResolveAsync();
if (resolvedItem != null) currentSelectedItems.Add(resolvedItem);
}
IItem? currentSelectedItem = null;
if (_selectedTab != null) currentSelectedItem = await _selectedTab.GetCurrentSelectedItem();
if (currentSelectedItems.Count > 0) if (currentSelectedItems.Count > 0)
{ {
var delete = true; var delete = true;

View File

@@ -60,9 +60,9 @@ namespace FileTime.Core.Command
var newDiffs = new List<Difference>(); var newDiffs = new List<Difference>();
_copyOperation = (_, to, _, _) => _copyOperation = async (_, to, _, _) =>
{ {
var target = to.GetParent().ResolveAsync(); var target = await to.GetParent().ResolveAsync();
newDiffs.Add(new Difference( newDiffs.Add(new Difference(
target is IElement target is IElement
? DifferenceItemType.Element ? DifferenceItemType.Element
@@ -70,8 +70,6 @@ namespace FileTime.Core.Command
DifferenceActionType.Create, DifferenceActionType.Create,
to to
)); ));
return Task.CompletedTask;
}; };
_createContainer = async (IContainer target, string name) => _createContainer = async (IContainer target, string name) =>
@@ -188,6 +186,7 @@ namespace FileTime.Core.Command
for (var i = 0; targetNameExists; i++) for (var i = 0; targetNameExists; i++)
{ {
targetName = element.Name + (i == 0 ? "_" : $"_{i}"); targetName = element.Name + (i == 0 ? "_" : $"_{i}");
targetNameExists = await target.IsExistsAsync(targetName);
} }
} }
else if (transportMode == Command.TransportMode.Skip && targetNameExists) else if (transportMode == Command.TransportMode.Skip && targetNameExists)

View File

@@ -43,7 +43,7 @@ namespace FileTime.Core.Command
public async Task<CanCommandRun> CanRun(PointInTime startPoint) public async Task<CanCommandRun> CanRun(PointInTime startPoint)
{ {
var resolvedContainer = Container.ResolveAsync(); var resolvedContainer = await Container.ResolveAsync();
if (resolvedContainer == null) return CanCommandRun.Forceable; if (resolvedContainer == null) return CanCommandRun.Forceable;
if (resolvedContainer is not IContainer container if (resolvedContainer is not IContainer container

View File

@@ -48,9 +48,9 @@ namespace FileTime.Core.Command
return startPoint.WithDifferences(newDifferences); return startPoint.WithDifferences(newDifferences);
} }
public Task<CanCommandRun> CanRun(PointInTime startPoint) public async Task<CanCommandRun> CanRun(PointInTime startPoint)
{ {
return Task.FromResult(Source.ResolveAsync() != null ? CanCommandRun.True : CanCommandRun.False); return await Source.ResolveAsync() != null ? CanCommandRun.True : CanCommandRun.False;
} }
} }
} }

View File

@@ -41,11 +41,14 @@ namespace FileTime.Core.CommandHandlers
do do
{ {
dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize); dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize);
if (dataRead.Length > 0)
{
await writer.WriteBytesAsync(dataRead); await writer.WriteBytesAsync(dataRead);
await writer.FlushAsync(); await writer.FlushAsync();
if (operationProgress != null) operationProgress.Progress += dataRead.LongLength; if (operationProgress != null) operationProgress.Progress += dataRead.LongLength;
await copyCommandContext.UpdateProgress(); await copyCommandContext.UpdateProgress();
} }
}
while (dataRead.Length > 0); while (dataRead.Length > 0);
} }
} }

View File

@@ -45,7 +45,8 @@ namespace FileTime.Core.Components
_currentLocation = value; _currentLocation = value;
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty); await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; var currentLocationItems = await (await GetCurrentLocation()).GetItems();
if (currentLocationItems == null) throw new Exception("Could not get current location items.");
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null)); await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh); _currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
} }
@@ -155,11 +156,11 @@ namespace FileTime.Core.Components
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!; var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
if (currentSelectedName != null) if (currentSelectedName != null)
{ {
await SetCurrentSelectedItem(currentLocationItems.FirstOrDefault(i => i.FullName == currentSelectedName) ?? currentLocationItems[0]); await SetCurrentSelectedItem(currentLocationItems.FirstOrDefault(i => i.FullName == currentSelectedName) ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null), token: token);
} }
else if (currentLocationItems.Count > 0) else if (currentLocationItems.Count > 0)
{ {
await SetCurrentSelectedItem(currentLocationItems[0]); await SetCurrentSelectedItem(currentLocationItems[0], token: token);
} }
} }

View File

@@ -23,7 +23,12 @@ namespace FileTime.Core.Models
if (item is IContainer container) if (item is IContainer container)
{ {
var result = await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch); IItem? result = null;
try
{
result = await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)), acceptDeepestMatch);
}
catch { }
return result == null && acceptDeepestMatch ? this : result; return result == null && acceptDeepestMatch ? this : result;
} }

View File

@@ -139,9 +139,14 @@ namespace FileTime.Core.Timeline
_commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait(); _commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait();
} }
} }
catch(Exception e) catch (Exception e)
{ {
_logger.LogError(e, "Error while running command: {CommandType} ({Command}).", commandToRun?.Command.GetType().Name, commandToRun?.Command.DisplayLabel); _logger.LogError(
e,
"Error while running command: {CommandType} ({Command}) {Error}.",
commandToRun?.Command.GetType().Name,
commandToRun?.Command.DisplayLabel,
e.Message);
} }
finally finally
{ {

View File

@@ -11,7 +11,27 @@ namespace FileTime.Avalonia.Converters
if (value is not Exception e) return value; if (value is not Exception e) return value;
if (e is UnauthorizedAccessException) return e.Message; if (e is UnauthorizedAccessException) return e.Message;
else if (e.InnerException != null)
{
return TraverseInnerException(e);
}
return FormatException(e);
}
private static string TraverseInnerException(Exception e)
{
string s = "";
if (e.InnerException != null) s += TraverseInnerException(e.InnerException) + Environment.NewLine;
else return FormatException(e);
s += "In: " + FormatException(e);
return s;
}
private static string FormatException(Exception e)
{
return $"{e.Message} ({e.GetType().FullName})"; return $"{e.Message} ({e.GetType().FullName})";
} }

View File

@@ -268,9 +268,18 @@ namespace FileTime.Avalonia.Services
{ {
var handler = async (List<InputElementWrapper> inputs) => var handler = async (List<InputElementWrapper> inputs) =>
{ {
string? containerName = null;
try
{
containerName = inputs[0].Value;
var container = _appState.SelectedTab.CurrentLocation.Container; var container = _appState.SelectedTab.CurrentLocation.Container;
var createContainerCommand = new CreateContainerCommand(new AbsolutePath(container), inputs[0].Value); var createContainerCommand = new CreateContainerCommand(new AbsolutePath(container), containerName);
await AddCommand(createContainerCommand); await AddCommand(createContainerCommand);
}
catch(Exception e)
{
_logger.LogError(e, "Error while creating container {Container}", containerName);
}
}; };
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Container name", InputType.Text) }, handler); _dialogService.ReadInputs(new List<InputElement>() { new InputElement("Container name", InputType.Text) }, handler);
@@ -282,9 +291,18 @@ namespace FileTime.Avalonia.Services
{ {
var handler = async (List<InputElementWrapper> inputs) => var handler = async (List<InputElementWrapper> inputs) =>
{ {
string? elementName = null;
try
{
elementName = inputs[0].Value;
var container = _appState.SelectedTab.CurrentLocation.Container; var container = _appState.SelectedTab.CurrentLocation.Container;
var createElementCommand = new CreateElementCommand(new AbsolutePath(container), inputs[0].Value); var createElementCommand = new CreateElementCommand(new AbsolutePath(container), elementName);
await AddCommand(createElementCommand); await AddCommand(createElementCommand);
}
catch(Exception e)
{
_logger.LogError(e, "Error while creating element {Element}", elementName);
}
}; };
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Element name", InputType.Text) }, handler); _dialogService.ReadInputs(new List<InputElement>() { new InputElement("Element name", InputType.Text) }, handler);

View File

@@ -207,7 +207,7 @@ namespace FileTime.Avalonia.Services
} }
catch (Exception e) catch (Exception e)
{ {
_logger.LogError(e, "Unknown error while running commnad. {Command}", command); _logger.LogError(e, "Unknown error while running command. {Command} {Error}", command, e);
} }
} }

View File

@@ -137,7 +137,19 @@ namespace FileTime.Avalonia.Services
if (container == null) continue; if (container == null) continue;
var newTab = new Tab(); var newTab = new Tab();
while (true)
{
try
{
if (container == null) throw new Exception($"Could not find an initializable path along {tab.Path}");
await newTab.Init(container); await newTab.Init(container);
break;
}
catch
{
container = container!.GetParent();
}
}
var newTabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService); var newTabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService);
await newTabContainer.Init(tab.Number); await newTabContainer.Init(tab.Number);

View File

@@ -323,7 +323,7 @@
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}"> <ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/> <TextBlock Margin="5,0,5,10" Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsRepeater.ItemTemplate>
</ItemsRepeater> </ItemsRepeater>

View File

@@ -4,24 +4,29 @@ namespace FileTime.Providers.Smb
{ {
public class SmbClientContext public class SmbClientContext
{ {
private readonly Func<Task<ISMBClient>> _getSmbClient; private readonly Func<int, Task<ISMBClient>> _getSmbClient;
private readonly Action _disposeClient; private readonly Action _disposeClient;
private bool _isRunning; private bool _isRunning;
private readonly object _lock = new(); private readonly object _lock = new();
public SmbClientContext(Func<Task<ISMBClient>> getSmbClient, Action disposeClient) public SmbClientContext(Func<int, Task<ISMBClient>> getSmbClient, Action disposeClient)
{ {
_getSmbClient = getSmbClient; _getSmbClient = getSmbClient;
_disposeClient = disposeClient; _disposeClient = disposeClient;
} }
public async Task<T> RunWithSmbClientAsync<T>(Func<ISMBClient, T> func) public async Task RunWithSmbClientAsync(Action<ISMBClient> action, int maxRetries = SmbServer.MAXRETRIES)
{
await RunWithSmbClientAsync<object?>((client) => { action(client); return null; }, maxRetries);
}
public async Task<T> RunWithSmbClientAsync<T>(Func<ISMBClient, T> func, int maxRetries = SmbServer.MAXRETRIES)
{ {
while (true) while (true)
{ {
lock (_lock) lock (_lock)
{ {
if(!_isRunning) if (!_isRunning)
{ {
_isRunning = true; _isRunning = true;
break; break;
@@ -37,7 +42,7 @@ namespace FileTime.Providers.Smb
{ {
try try
{ {
client = await _getSmbClient(); client = await _getSmbClient(maxRetries);
return func(client); return func(client);
} }
catch (Exception e) when (e.Source == "SMBLibrary") catch (Exception e) when (e.Source == "SMBLibrary")

View File

@@ -94,8 +94,23 @@ namespace FileTime.Providers.Smb
} }
var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1)); var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1));
try
{
return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath, acceptDeepestMatch); return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath, acceptDeepestMatch);
} }
catch (Exception e)
{
_logger.LogError(e, "Error while getting path {Path}", path);
if (acceptDeepestMatch)
{
return rootContainer ?? this;
}
else
{
throw;
}
}
}
public IContainer? GetParent() => _parent; public IContainer? GetParent() => _parent;

View File

@@ -9,7 +9,7 @@ namespace FileTime.Providers.Smb
private readonly ISMBFileStore _smbFileStore; private readonly ISMBFileStore _smbFileStore;
private readonly object _fileHandle; private readonly object _fileHandle;
private readonly ISMBClient _client; private readonly ISMBClient _client;
private bool disposed; private bool _disposed;
private long _bytesRead; private long _bytesRead;
public int PreferredBufferSize => (int)_client.MaxReadSize; public int PreferredBufferSize => (int)_client.MaxReadSize;
@@ -53,7 +53,7 @@ namespace FileTime.Providers.Smb
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (!disposed) if (!_disposed)
{ {
if (disposing) if (disposing)
{ {
@@ -61,7 +61,7 @@ namespace FileTime.Providers.Smb
_smbFileStore.Disconnect(); _smbFileStore.Disconnect();
} }
} }
disposed = true; _disposed = true;
} }
} }
} }

View File

@@ -1,24 +1,65 @@
using FileTime.Core.Providers; using FileTime.Core.Providers;
using SMBLibrary;
using SMBLibrary.Client;
namespace FileTime.Providers.Smb namespace FileTime.Providers.Smb
{ {
public class SmbContentWriter : IContentWriter public class SmbContentWriter : IContentWriter
{ {
public int PreferredBufferSize => throw new NotImplementedException(); private readonly ISMBFileStore _smbFileStore;
private readonly object _fileHandle;
private readonly ISMBClient _client;
private bool _disposed;
private int _writeOffset;
public void Dispose() public int PreferredBufferSize => (int)_client.MaxWriteSize;
public SmbContentWriter(ISMBFileStore smbFileStore, object fileHandle, ISMBClient client)
{ {
throw new NotImplementedException(); _smbFileStore = smbFileStore;
_fileHandle = fileHandle;
_client = client;
} }
public Task FlushAsync() public Task FlushAsync()
{ {
throw new NotImplementedException(); return Task.CompletedTask;
} }
public Task WriteBytesAsync(byte[] data) public Task WriteBytesAsync(byte[] data)
{ {
throw new NotImplementedException(); var status = _smbFileStore.WriteFile(out int numberOfBytesWritten, _fileHandle, _writeOffset, data);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception("Failed to write to file");
}
_writeOffset += numberOfBytesWritten;
return Task.CompletedTask;
}
~SmbContentWriter()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_smbFileStore.CloseFile(_fileHandle);
_smbFileStore.Disconnect();
}
}
_disposed = true;
} }
} }
} }

View File

@@ -37,9 +37,34 @@ namespace FileTime.Providers.Smb
_smbShare = smbShare; _smbShare = smbShare;
} }
public Task Delete(bool hardDelete = false) public async Task Delete(bool hardDelete = false)
{ {
throw new NotImplementedException(); await _smbClientContext.RunWithSmbClientAsync(client =>
{
var fileStore = _smbShare.TreeConnect(client, out var status);
status = fileStore.CreateFile(
out object fileHandle,
out FileStatus fileStatus,
GetPathFromShare(),
AccessMask.GENERIC_WRITE | AccessMask.DELETE | AccessMask.SYNCHRONIZE,
SMBLibrary.FileAttributes.Normal,
ShareAccess.None,
CreateDisposition.FILE_OPEN,
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
null);
if (status == NTStatus.STATUS_SUCCESS)
{
var fileDispositionInformation = new FileDispositionInformation
{
DeletePending = true
};
status = fileStore.SetFileInformation(fileHandle, fileDispositionInformation);
bool deleteSucceeded = status == NTStatus.STATUS_SUCCESS;
status = fileStore.CloseFile(fileHandle);
}
status = fileStore.Disconnect();
});
} }
public Task Rename(string newName) public Task Rename(string newName)
{ {
@@ -69,9 +94,16 @@ namespace FileTime.Providers.Smb
throw new Exception($"Could not open file {NativePath} for read."); throw new Exception($"Could not open file {NativePath} for read.");
} }
var path = NativePath!; status = fileStore.CreateFile(
path = path[(_parent.NativePath!.Length + 1)..]; out object fileHandle,
status = fileStore.CreateFile(out object fileHandle, out FileStatus fileStatus, path, AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE, SMBLibrary.FileAttributes.Normal, ShareAccess.Read, CreateDisposition.FILE_OPEN, CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT, null); out FileStatus fileStatus,
GetPathFromShare(),
AccessMask.GENERIC_READ | AccessMask.SYNCHRONIZE,
SMBLibrary.FileAttributes.Normal,
ShareAccess.Read,
CreateDisposition.FILE_OPEN,
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
null);
if (status != NTStatus.STATUS_SUCCESS) if (status != NTStatus.STATUS_SUCCESS)
{ {
@@ -82,9 +114,38 @@ namespace FileTime.Providers.Smb
}); });
} }
public Task<IContentWriter> GetContentWriterAsync() public async Task<IContentWriter> GetContentWriterAsync()
{ {
throw new NotImplementedException(); return await _smbClientContext.RunWithSmbClientAsync(client =>
{
NTStatus status = NTStatus.STATUS_DATA_ERROR;
var fileStore = _smbShare.TreeConnect(client, out status);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Could not open file {NativePath} for write.");
} }
status = fileStore.CreateFile(
out object fileHandle,
out FileStatus fileStatus,
GetPathFromShare(),
AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE,
SMBLibrary.FileAttributes.Normal,
ShareAccess.None,
CreateDisposition.FILE_OPEN_IF,
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
null);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Could not open file {NativePath} for write.");
}
return new SmbContentWriter(fileStore, fileHandle, client);
});
}
private string GetPathFromShare() => SmbContentProvider.GetNativePath(FullName![(_smbShare.FullName!.Length + 1)..]);
} }
} }

View File

@@ -1,6 +1,7 @@
using AsyncEvent; using AsyncEvent;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Providers; using FileTime.Core.Providers;
using SMBLibrary;
namespace FileTime.Providers.Smb namespace FileTime.Providers.Smb
{ {
@@ -10,6 +11,7 @@ namespace FileTime.Providers.Smb
private IReadOnlyList<IContainer>? _containers; private IReadOnlyList<IContainer>? _containers;
private IReadOnlyList<IElement>? _elements; private IReadOnlyList<IElement>? _elements;
private readonly IContainer? _parent; private readonly IContainer? _parent;
private readonly SmbClientContext _smbClientContext;
public string Name { get; } public string Name { get; }
@@ -32,7 +34,7 @@ namespace FileTime.Providers.Smb
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent) public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent, SmbClientContext smbClientContext)
{ {
_parent = parent; _parent = parent;
SmbShare = smbShare; SmbShare = smbShare;
@@ -41,30 +43,65 @@ namespace FileTime.Providers.Smb
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name; FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
NativePath = SmbContentProvider.GetNativePath(FullName); NativePath = SmbContentProvider.GetNativePath(FullName);
Provider = contentProvider; Provider = contentProvider;
_smbClientContext = smbClientContext;
} }
public Task<IContainer> CreateContainerAsync(string name) public async Task<IContainer> CreateContainerAsync(string name)
{ {
throw new NotImplementedException(); var path = FullName![(SmbShare.FullName!.Length + 1)..] + Constants.SeparatorChar + name;
await SmbShare.CreateContainerWithPathAsync(SmbContentProvider.GetNativePath(path));
await RefreshAsync();
return _containers!.FirstOrDefault(e => e.Name == name)!;
} }
public Task<IElement> CreateElementAsync(string name) public async Task<IElement> CreateElementAsync(string name)
{ {
throw new NotImplementedException(); var path = FullName![(SmbShare.FullName!.Length + 1)..] + Constants.SeparatorChar + name;
await SmbShare.CreateElementWithPathAsync(SmbContentProvider.GetNativePath(path));
await RefreshAsync();
return _elements!.FirstOrDefault(e => e.Name == name)!;
} }
public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this); public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
public IContainer? GetParent() => _parent; public IContainer? GetParent() => _parent;
public Task<bool> IsExistsAsync(string name) public async Task<bool> IsExistsAsync(string name)
{ {
throw new NotImplementedException(); var items = await GetItems();
return items?.Any(i => i.Name == name) ?? false;
} }
public Task Delete(bool hardDelete = false) public async Task Delete(bool hardDelete = false)
{ {
throw new NotImplementedException(); await _smbClientContext.RunWithSmbClientAsync(client =>
{
var fileStore = SmbShare.TreeConnect(client, out var status);
status = fileStore.CreateFile(
out object fileHandle,
out FileStatus fileStatus,
GetPathFromShare(),
AccessMask.GENERIC_WRITE | AccessMask.DELETE | AccessMask.SYNCHRONIZE,
SMBLibrary.FileAttributes.Normal,
ShareAccess.None,
CreateDisposition.FILE_OPEN,
CreateOptions.FILE_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
null);
if (status == NTStatus.STATUS_SUCCESS)
{
var fileDispositionInformation = new FileDispositionInformation
{
DeletePending = true
};
status = fileStore.SetFileInformation(fileHandle, fileDispositionInformation);
bool deleteSucceeded = status == NTStatus.STATUS_SUCCESS;
status = fileStore.CloseFile(fileHandle);
}
status = fileStore.Disconnect();
});
} }
public Task Rename(string newName) public Task Rename(string newName)
{ {
@@ -123,5 +160,7 @@ namespace FileTime.Providers.Smb
_containers = null; _containers = null;
_elements = null; _elements = null;
} }
private string GetPathFromShare() => SmbContentProvider.GetNativePath(FullName![(SmbShare.FullName!.Length + 1)..]);
} }
} }

View File

@@ -10,6 +10,8 @@ namespace FileTime.Providers.Smb
{ {
public class SmbServer : IContainer public class SmbServer : IContainer
{ {
internal const int MAXRETRIES = 5;
private bool _reenterCredentials; private bool _reenterCredentials;
private IReadOnlyList<IContainer>? _shares; private IReadOnlyList<IContainer>? _shares;
@@ -20,6 +22,8 @@ namespace FileTime.Providers.Smb
private bool _refreshingClient; private bool _refreshingClient;
private readonly IInputInterface _inputInterface; private readonly IInputInterface _inputInterface;
private readonly SmbClientContext _smbClientContext; private readonly SmbClientContext _smbClientContext;
private readonly List<Exception> _exceptions = new();
public string? Username { get; private set; } public string? Username { get; private set; }
public string? Password { get; private set; } public string? Password { get; private set; }
@@ -35,7 +39,7 @@ namespace FileTime.Providers.Smb
IContentProvider IItem.Provider => Provider; IContentProvider IItem.Provider => Provider;
public SupportsDelete CanDelete => SupportsDelete.True; public SupportsDelete CanDelete => SupportsDelete.True;
public bool CanRename => false; public bool CanRename => false;
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly(); public IReadOnlyList<Exception> Exceptions { get; }
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
@@ -47,6 +51,7 @@ namespace FileTime.Providers.Smb
{ {
_inputInterface = inputInterface; _inputInterface = inputInterface;
_smbClientContext = new SmbClientContext(GetSmbClient, DisposeSmbClient); _smbClientContext = new SmbClientContext(GetSmbClient, DisposeSmbClient);
Exceptions = _exceptions.AsReadOnly();
Username = username; Username = username;
Password = password; Password = password;
@@ -113,12 +118,20 @@ namespace FileTime.Providers.Smb
public async Task RefreshAsync(CancellationToken token = default) public async Task RefreshAsync(CancellationToken token = default)
{ {
List<string> shares = await _smbClientContext.RunWithSmbClientAsync((client) => client.ListShares(out var status)); try
{
_exceptions.Clear();
List<string> shares = await _smbClientContext.RunWithSmbClientAsync((client) => client.ListShares(out var status), _shares == null ? 1 : MAXRETRIES);
_shares = shares.ConvertAll(s => new SmbShare(s, Provider, this, _smbClientContext)).AsReadOnly(); _shares = shares.ConvertAll(s => new SmbShare(s, Provider, this, _smbClientContext)).AsReadOnly();
_items = _shares.Cast<IItem>().ToList().AsReadOnly(); _items = _shares.Cast<IItem>().ToList().AsReadOnly();
await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
} }
catch (Exception e)
{
_exceptions.Add(e);
}
}
public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this); public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
@@ -130,7 +143,7 @@ namespace FileTime.Providers.Smb
} }
} }
private async Task<ISMBClient> GetSmbClient() private async Task<ISMBClient> GetSmbClient(int maxRetries = MAXRETRIES)
{ {
bool isClientNull; bool isClientNull;
lock (_clientGuard) lock (_clientGuard)
@@ -138,6 +151,7 @@ namespace FileTime.Providers.Smb
isClientNull = _client == null; isClientNull = _client == null;
} }
int reTries = 0;
while (isClientNull) while (isClientNull)
{ {
if (!await RefreshSmbClient()) if (!await RefreshSmbClient())
@@ -149,6 +163,12 @@ namespace FileTime.Providers.Smb
{ {
isClientNull = _client == null; isClientNull = _client == null;
} }
if (reTries >= maxRetries)
{
throw new Exception($"Could not connect to server {Name} after {reTries} retry");
}
reTries++;
} }
return _client!; return _client!;
} }

View File

@@ -61,28 +61,99 @@ namespace FileTime.Providers.Smb
return _elements; return _elements;
} }
public Task<IContainer> CreateContainerAsync(string name) public async Task<IContainer> CreateContainerAsync(string name)
{ {
throw new NotImplementedException(); await CreateContainerWithPathAsync(name);
await RefreshAsync();
return _containers!.FirstOrDefault(e => e.Name == name)!;
}
internal async Task CreateContainerWithPathAsync(string path)
{
await _smbClientContext.RunWithSmbClientAsync(client =>
{
NTStatus status = NTStatus.STATUS_DATA_ERROR;
var fileStore = TreeConnect(client, out status);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Could not create directory {path}.");
} }
public Task<IElement> CreateElementAsync(string name) status = fileStore.CreateFile(
out object fileHandle,
out FileStatus fileStatus,
path,
AccessMask.GENERIC_ALL,
SMBLibrary.FileAttributes.Directory,
ShareAccess.Read,
CreateDisposition.FILE_OPEN_IF,
CreateOptions.FILE_DIRECTORY_FILE,
null);
if (status != NTStatus.STATUS_SUCCESS)
{ {
throw new NotImplementedException(); throw new Exception($"Could not create directory {path}.");
}
fileStore.CloseFile(fileHandle);
fileStore.Disconnect();
});
}
public async Task<IElement> CreateElementAsync(string name)
{
await CreateElementWithPathAsync(name);
await RefreshAsync();
return _elements!.FirstOrDefault(e => e.Name == name)!;
}
internal async Task CreateElementWithPathAsync(string path)
{
await _smbClientContext.RunWithSmbClientAsync(client =>
{
NTStatus status = NTStatus.STATUS_DATA_ERROR;
var fileStore = TreeConnect(client, out status);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Could not create file {path}.");
}
status = fileStore.CreateFile(
out object fileHandle,
out FileStatus fileStatus,
path,
AccessMask.GENERIC_WRITE | AccessMask.SYNCHRONIZE,
SMBLibrary.FileAttributes.Normal,
ShareAccess.None,
CreateDisposition.FILE_CREATE,
CreateOptions.FILE_NON_DIRECTORY_FILE | CreateOptions.FILE_SYNCHRONOUS_IO_ALERT,
null);
if (status != NTStatus.STATUS_SUCCESS)
{
throw new Exception($"Could not create file {path}.");
}
fileStore.CloseFile(fileHandle);
fileStore.Disconnect();
});
} }
public Task Delete(bool hardDelete = false) public Task Delete(bool hardDelete = false)
{ {
throw new NotImplementedException(); throw new NotSupportedException();
} }
public IContainer? GetParent() => _parent; public IContainer? GetParent() => _parent;
public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this); public Task<IContainer> CloneAsync() => Task.FromResult((IContainer)this);
public Task<bool> IsExistsAsync(string name) public async Task<bool> IsExistsAsync(string name)
{ {
throw new NotImplementedException(); var items = await GetItems();
return items?.Any(i => i.Name == name) ?? false;
} }
public async Task RefreshAsync(CancellationToken token = default) public async Task RefreshAsync(CancellationToken token = default)
@@ -134,7 +205,7 @@ namespace FileTime.Providers.Smb
{ {
if ((fileDirectoryInformation.FileAttributes & SMBLibrary.FileAttributes.Directory) == SMBLibrary.FileAttributes.Directory) if ((fileDirectoryInformation.FileAttributes & SMBLibrary.FileAttributes.Directory) == SMBLibrary.FileAttributes.Directory)
{ {
containers.Add(new SmbFolder(fileDirectoryInformation.FileName, Provider, this, parent)); containers.Add(new SmbFolder(fileDirectoryInformation.FileName, Provider, this, parent, _smbClientContext));
} }
else else
{ {