Smb write, delete, fixes, error handling
This commit is contained in:
@@ -169,8 +169,16 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
IList<AbsolutePath>? itemsToDelete = null;
|
||||
|
||||
var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.ResolveAsync()).ToList();
|
||||
var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem();
|
||||
var currentSelectedItems = new List<IItem>();
|
||||
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)
|
||||
{
|
||||
var delete = true;
|
||||
|
||||
@@ -60,9 +60,9 @@ namespace FileTime.Core.Command
|
||||
|
||||
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(
|
||||
target is IElement
|
||||
? DifferenceItemType.Element
|
||||
@@ -70,8 +70,6 @@ namespace FileTime.Core.Command
|
||||
DifferenceActionType.Create,
|
||||
to
|
||||
));
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
_createContainer = async (IContainer target, string name) =>
|
||||
@@ -188,6 +186,7 @@ namespace FileTime.Core.Command
|
||||
for (var i = 0; targetNameExists; i++)
|
||||
{
|
||||
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
||||
targetNameExists = await target.IsExistsAsync(targetName);
|
||||
}
|
||||
}
|
||||
else if (transportMode == Command.TransportMode.Skip && targetNameExists)
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace FileTime.Core.Command
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = Container.ResolveAsync();
|
||||
var resolvedContainer = await Container.ResolveAsync();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|
||||
@@ -48,9 +48,9 @@ namespace FileTime.Core.Command
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,11 +41,14 @@ namespace FileTime.Core.CommandHandlers
|
||||
do
|
||||
{
|
||||
dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize);
|
||||
if (dataRead.Length > 0)
|
||||
{
|
||||
await writer.WriteBytesAsync(dataRead);
|
||||
await writer.FlushAsync();
|
||||
if (operationProgress != null) operationProgress.Progress += dataRead.LongLength;
|
||||
await copyCommandContext.UpdateProgress();
|
||||
}
|
||||
}
|
||||
while (dataRead.Length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,8 @@ namespace FileTime.Core.Components
|
||||
_currentLocation = value;
|
||||
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));
|
||||
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
|
||||
}
|
||||
@@ -155,11 +156,11 @@ namespace FileTime.Core.Components
|
||||
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
||||
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)
|
||||
{
|
||||
await SetCurrentSelectedItem(currentLocationItems[0]);
|
||||
await SetCurrentSelectedItem(currentLocationItems[0], token: token);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,12 @@ namespace FileTime.Core.Models
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,9 +139,14 @@ namespace FileTime.Core.Timeline
|
||||
_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
|
||||
{
|
||||
|
||||
@@ -11,7 +11,27 @@ namespace FileTime.Avalonia.Converters
|
||||
if (value is not Exception e) return value;
|
||||
|
||||
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})";
|
||||
}
|
||||
|
||||
|
||||
@@ -268,9 +268,18 @@ namespace FileTime.Avalonia.Services
|
||||
{
|
||||
var handler = async (List<InputElementWrapper> inputs) =>
|
||||
{
|
||||
string? containerName = null;
|
||||
try
|
||||
{
|
||||
containerName = inputs[0].Value;
|
||||
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);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error while creating container {Container}", containerName);
|
||||
}
|
||||
};
|
||||
|
||||
_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) =>
|
||||
{
|
||||
string? elementName = null;
|
||||
try
|
||||
{
|
||||
elementName = inputs[0].Value;
|
||||
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);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Error while creating element {Element}", elementName);
|
||||
}
|
||||
};
|
||||
|
||||
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Element name", InputType.Text) }, handler);
|
||||
|
||||
@@ -207,7 +207,7 @@ namespace FileTime.Avalonia.Services
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,19 @@ namespace FileTime.Avalonia.Services
|
||||
if (container == null) continue;
|
||||
|
||||
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);
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
container = container!.GetParent();
|
||||
}
|
||||
}
|
||||
|
||||
var newTabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService);
|
||||
await newTabContainer.Init(tab.Number);
|
||||
|
||||
@@ -323,7 +323,7 @@
|
||||
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
|
||||
<TextBlock Margin="5,0,5,10" Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
|
||||
@@ -4,24 +4,29 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
public class SmbClientContext
|
||||
{
|
||||
private readonly Func<Task<ISMBClient>> _getSmbClient;
|
||||
private readonly Func<int, Task<ISMBClient>> _getSmbClient;
|
||||
private readonly Action _disposeClient;
|
||||
private bool _isRunning;
|
||||
private readonly object _lock = new();
|
||||
|
||||
public SmbClientContext(Func<Task<ISMBClient>> getSmbClient, Action disposeClient)
|
||||
public SmbClientContext(Func<int, Task<ISMBClient>> getSmbClient, Action disposeClient)
|
||||
{
|
||||
_getSmbClient = getSmbClient;
|
||||
_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)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if(!_isRunning)
|
||||
if (!_isRunning)
|
||||
{
|
||||
_isRunning = true;
|
||||
break;
|
||||
@@ -37,7 +42,7 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
try
|
||||
{
|
||||
client = await _getSmbClient();
|
||||
client = await _getSmbClient(maxRetries);
|
||||
return func(client);
|
||||
}
|
||||
catch (Exception e) when (e.Source == "SMBLibrary")
|
||||
|
||||
@@ -94,8 +94,23 @@ namespace FileTime.Providers.Smb
|
||||
}
|
||||
|
||||
var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1));
|
||||
try
|
||||
{
|
||||
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;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace FileTime.Providers.Smb
|
||||
private readonly ISMBFileStore _smbFileStore;
|
||||
private readonly object _fileHandle;
|
||||
private readonly ISMBClient _client;
|
||||
private bool disposed;
|
||||
private bool _disposed;
|
||||
private long _bytesRead;
|
||||
|
||||
public int PreferredBufferSize => (int)_client.MaxReadSize;
|
||||
@@ -53,7 +53,7 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposed)
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
@@ -61,7 +61,7 @@ namespace FileTime.Providers.Smb
|
||||
_smbFileStore.Disconnect();
|
||||
}
|
||||
}
|
||||
disposed = true;
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,65 @@
|
||||
using FileTime.Core.Providers;
|
||||
using SMBLibrary;
|
||||
using SMBLibrary.Client;
|
||||
|
||||
namespace FileTime.Providers.Smb
|
||||
{
|
||||
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()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,9 +37,34 @@ namespace FileTime.Providers.Smb
|
||||
_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)
|
||||
{
|
||||
@@ -69,9 +94,16 @@ namespace FileTime.Providers.Smb
|
||||
throw new Exception($"Could not open file {NativePath} for read.");
|
||||
}
|
||||
|
||||
var path = NativePath!;
|
||||
path = path[(_parent.NativePath!.Length + 1)..];
|
||||
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);
|
||||
status = fileStore.CreateFile(
|
||||
out object fileHandle,
|
||||
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)
|
||||
{
|
||||
@@ -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)..]);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using SMBLibrary;
|
||||
|
||||
namespace FileTime.Providers.Smb
|
||||
{
|
||||
@@ -10,6 +11,7 @@ namespace FileTime.Providers.Smb
|
||||
private IReadOnlyList<IContainer>? _containers;
|
||||
private IReadOnlyList<IElement>? _elements;
|
||||
private readonly IContainer? _parent;
|
||||
private readonly SmbClientContext _smbClientContext;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
@@ -32,7 +34,7 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
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;
|
||||
SmbShare = smbShare;
|
||||
@@ -41,30 +43,65 @@ namespace FileTime.Providers.Smb
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
NativePath = SmbContentProvider.GetNativePath(FullName);
|
||||
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 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)
|
||||
{
|
||||
@@ -123,5 +160,7 @@ namespace FileTime.Providers.Smb
|
||||
_containers = null;
|
||||
_elements = null;
|
||||
}
|
||||
|
||||
private string GetPathFromShare() => SmbContentProvider.GetNativePath(FullName![(SmbShare.FullName!.Length + 1)..]);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
public class SmbServer : IContainer
|
||||
{
|
||||
internal const int MAXRETRIES = 5;
|
||||
|
||||
private bool _reenterCredentials;
|
||||
|
||||
private IReadOnlyList<IContainer>? _shares;
|
||||
@@ -20,6 +22,8 @@ namespace FileTime.Providers.Smb
|
||||
private bool _refreshingClient;
|
||||
private readonly IInputInterface _inputInterface;
|
||||
private readonly SmbClientContext _smbClientContext;
|
||||
private readonly List<Exception> _exceptions = new();
|
||||
|
||||
public string? Username { get; private set; }
|
||||
public string? Password { get; private set; }
|
||||
|
||||
@@ -35,7 +39,7 @@ namespace FileTime.Providers.Smb
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
public SupportsDelete CanDelete => SupportsDelete.True;
|
||||
public bool CanRename => false;
|
||||
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
|
||||
public IReadOnlyList<Exception> Exceptions { get; }
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
@@ -47,6 +51,7 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
_inputInterface = inputInterface;
|
||||
_smbClientContext = new SmbClientContext(GetSmbClient, DisposeSmbClient);
|
||||
Exceptions = _exceptions.AsReadOnly();
|
||||
Username = username;
|
||||
Password = password;
|
||||
|
||||
@@ -113,12 +118,20 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
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();
|
||||
_items = _shares.Cast<IItem>().ToList().AsReadOnly();
|
||||
await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_exceptions.Add(e);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
lock (_clientGuard)
|
||||
@@ -138,6 +151,7 @@ namespace FileTime.Providers.Smb
|
||||
isClientNull = _client == null;
|
||||
}
|
||||
|
||||
int reTries = 0;
|
||||
while (isClientNull)
|
||||
{
|
||||
if (!await RefreshSmbClient())
|
||||
@@ -149,6 +163,12 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
isClientNull = _client == null;
|
||||
}
|
||||
|
||||
if (reTries >= maxRetries)
|
||||
{
|
||||
throw new Exception($"Could not connect to server {Name} after {reTries} retry");
|
||||
}
|
||||
reTries++;
|
||||
}
|
||||
return _client!;
|
||||
}
|
||||
|
||||
@@ -61,28 +61,99 @@ namespace FileTime.Providers.Smb
|
||||
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)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
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)
|
||||
@@ -134,7 +205,7 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
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
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user