Smb write, delete, fixes, error handling
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,10 +41,13 @@ namespace FileTime.Core.CommandHandlers
|
|||||||
do
|
do
|
||||||
{
|
{
|
||||||
dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize);
|
dataRead = await reader.ReadBytesAsync(writer.PreferredBufferSize);
|
||||||
await writer.WriteBytesAsync(dataRead);
|
if (dataRead.Length > 0)
|
||||||
await writer.FlushAsync();
|
{
|
||||||
if (operationProgress != null) operationProgress.Progress += dataRead.LongLength;
|
await writer.WriteBytesAsync(dataRead);
|
||||||
await copyCommandContext.UpdateProgress();
|
await writer.FlushAsync();
|
||||||
|
if (operationProgress != null) operationProgress.Progress += dataRead.LongLength;
|
||||||
|
await copyCommandContext.UpdateProgress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
while (dataRead.Length > 0);
|
while (dataRead.Length > 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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})";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -268,9 +268,18 @@ namespace FileTime.Avalonia.Services
|
|||||||
{
|
{
|
||||||
var handler = async (List<InputElementWrapper> inputs) =>
|
var handler = async (List<InputElementWrapper> inputs) =>
|
||||||
{
|
{
|
||||||
var container = _appState.SelectedTab.CurrentLocation.Container;
|
string? containerName = null;
|
||||||
var createContainerCommand = new CreateContainerCommand(new AbsolutePath(container), inputs[0].Value);
|
try
|
||||||
await AddCommand(createContainerCommand);
|
{
|
||||||
|
containerName = inputs[0].Value;
|
||||||
|
var container = _appState.SelectedTab.CurrentLocation.Container;
|
||||||
|
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);
|
_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) =>
|
||||||
{
|
{
|
||||||
var container = _appState.SelectedTab.CurrentLocation.Container;
|
string? elementName = null;
|
||||||
var createElementCommand = new CreateElementCommand(new AbsolutePath(container), inputs[0].Value);
|
try
|
||||||
await AddCommand(createElementCommand);
|
{
|
||||||
|
elementName = inputs[0].Value;
|
||||||
|
var container = _appState.SelectedTab.CurrentLocation.Container;
|
||||||
|
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);
|
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Element name", InputType.Text) }, handler);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,19 @@ namespace FileTime.Avalonia.Services
|
|||||||
if (container == null) continue;
|
if (container == null) continue;
|
||||||
|
|
||||||
var newTab = new Tab();
|
var newTab = new Tab();
|
||||||
await newTab.Init(container);
|
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);
|
var newTabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService);
|
||||||
await newTabContainer.Init(tab.Number);
|
await newTabContainer.Init(tab.Number);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -94,7 +94,22 @@ namespace FileTime.Providers.Smb
|
|||||||
}
|
}
|
||||||
|
|
||||||
var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1));
|
var remainingPath = string.Join(Constants.SeparatorChar, pathParts.Skip(1));
|
||||||
return remainingPath.Length == 0 ? rootContainer : await rootContainer.GetByPath(remainingPath, acceptDeepestMatch);
|
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;
|
public IContainer? GetParent() => _parent;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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)..]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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,11 +118,19 @@ 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!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Exception($"Could not create directory {path}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStore.CloseFile(fileHandle);
|
||||||
|
fileStore.Disconnect();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<IElement> CreateElementAsync(string name)
|
public async Task<IElement> CreateElementAsync(string name)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
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
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user