diff --git a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs b/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs index beed8ce..e613725 100644 --- a/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs +++ b/src/Core/FileTime.Core.Abstraction/Extensions/DynamicDataExtensions.cs @@ -36,16 +36,16 @@ public static class DynamicDataExtensions public void OnCompleted() { Disposable?.Dispose(); - _taskCompletionSource.SetResult(default(TTaskResult)); + _taskCompletionSource.SetResult(default); } } public static async Task?> GetItemsAsync( this IObservable>?> stream) => await GetItemsAsync(stream - .Select(s => - s is null - ? new SourceList().Connect().StartWithEmpty().ToCollection() + .Select(s => + s is null + ? new SourceList().Connect().StartWithEmpty().ToCollection() : s.ToCollection()) .Switch()); @@ -60,13 +60,12 @@ public static class DynamicDataExtensions var context = new DisposableContext, IEnumerable?>(r => r, taskCompletionSource); - var disposable = stream + context.Disposable = stream .Subscribe( context.OnNext, context.OnError, context.OnCompleted ); - context.Disposable = disposable; return taskCompletionSource.Task; } diff --git a/src/Core/FileTime.Core.Abstraction/Extensions/TaskExtensions.cs b/src/Core/FileTime.Core.Abstraction/Extensions/TaskExtensions.cs new file mode 100644 index 0000000..759b8c0 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Extensions/TaskExtensions.cs @@ -0,0 +1,16 @@ +namespace FileTime.Core.Extensions; + +public static class TaskExtensions +{ + public static async Task AwaitWithTimeout(this Task task, int timeout, T? defaultValue = default) + { + if (await Task.WhenAny(task, Task.Delay(timeout)) == task) + { + return task.Result; + } + else + { + return defaultValue; + } + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Helper/PathHelper.cs b/src/Core/FileTime.Core.Abstraction/Helper/PathHelper.cs new file mode 100644 index 0000000..863d969 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Helper/PathHelper.cs @@ -0,0 +1,54 @@ +using FileTime.Core.Models; + +namespace FileTime.Core.Helper; + +public static class PathHelper +{ + public static string GetLongerPath(string? oldPath, string? newPath) + { + var oldPathParts = oldPath?.Split(Constants.SeparatorChar) ?? Array.Empty(); + var newPathParts = newPath?.Split(Constants.SeparatorChar) ?? Array.Empty(); + + var commonPathParts = new List(); + + var max = oldPathParts.Length > newPathParts.Length ? oldPathParts.Length : newPathParts.Length; + + for (var i = 0; i < max; i++) + { + if (newPathParts.Length <= i) + { + commonPathParts.AddRange(oldPathParts.Skip(i)); + break; + } + else if (oldPathParts.Length <= i || oldPathParts[i] != newPathParts[i]) + { + commonPathParts.AddRange(newPathParts.Skip(i)); + break; + } + else if (oldPathParts[i] == newPathParts[i]) + { + commonPathParts.Add(oldPathParts[i]); + } + } + + return string.Join(Constants.SeparatorChar, commonPathParts); + } + public static string GetCommonPath(string? path1, string? path2) + { + var path1Parts = path1?.Split(Constants.SeparatorChar) ?? Array.Empty(); + var path2Parts = path2?.Split(Constants.SeparatorChar) ?? Array.Empty(); + + var commonPathParts = new List(); + + var max = path1Parts.Length > path2Parts.Length ? path2Parts.Length : path1Parts.Length; + + for (var i = 0; i < max; i++) + { + if (path1Parts[i] != path2Parts[i]) break; + + commonPathParts.Add(path1Parts[i]); + } + + return string.Join(Constants.SeparatorChar, commonPathParts); + } +} diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs b/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs index da7ca5a..a0f27bd 100644 --- a/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs +++ b/src/Core/FileTime.Core.Abstraction/Timeline/CommandTimeState.cs @@ -9,13 +9,12 @@ public class CommandTimeState public bool ForceRun { get; set; } public ExecutionState ExecutionState { get; set; } - public CommandTimeState(ICommand command, PointInTime? startTime) + public CommandTimeState(ICommand command) { Command = command; - Task.Run(async () => await UpdateState(startTime)).Wait(); } - public async Task UpdateState(PointInTime? startPoint) + public async Task UpdateStateAsync(PointInTime? startPoint) { CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint); } diff --git a/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs b/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs index bdee8ad..835eee6 100644 --- a/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs +++ b/src/Core/FileTime.Core.Abstraction/Timeline/ParallelCommands.cs @@ -31,7 +31,8 @@ public class ParallelCommands var currentTime = startTime; foreach (var command in commands) { - CommandTimeState commandTimeState = new(command, currentTime); + var commandTimeState = new CommandTimeState(command); + await commandTimeState.UpdateStateAsync(currentTime); if (currentTime != null) { var canRun = await command.CanRun(currentTime); @@ -53,7 +54,9 @@ public class ParallelCommands public async Task AddCommand(ICommand command) { - _commands.Add(new CommandTimeState(command, Result)); + var commandTimeState = new CommandTimeState(command); + await commandTimeState.UpdateStateAsync(Result); + _commands.Add(commandTimeState); if (Result != null) { Result = await command.SimulateCommand(Result); @@ -63,15 +66,15 @@ public class ParallelCommands public async Task RefreshResult(PointInTime? startPoint) { var result = startPoint; - foreach (var command in _commands) + foreach (var commandTimeState in _commands) { - await command.UpdateState(result); + await commandTimeState.UpdateStateAsync(result); if (result != null) { - var canRun = await command.Command.CanRun(result); - if (canRun == CanCommandRun.True || (canRun == CanCommandRun.Forcable && command.ForceRun)) + var canRun = await commandTimeState.Command.CanRun(result); + if (canRun == CanCommandRun.True || (canRun == CanCommandRun.Forcable && commandTimeState.ForceRun)) { - result = await command.Command.SimulateCommand(result); + result = await commandTimeState.Command.SimulateCommand(result); } else { diff --git a/src/Core/FileTime.Core.Command/Create/CreateContainerCommand.cs b/src/Core/FileTime.Core.Command/Create/CreateContainerCommand.cs index 5674b36..260f879 100644 --- a/src/Core/FileTime.Core.Command/Create/CreateContainerCommand.cs +++ b/src/Core/FileTime.Core.Command/Create/CreateContainerCommand.cs @@ -41,7 +41,7 @@ public abstract class CreateItemBase : IExecutableCommand, IInitable()); if (items is null) return CanCommandRun.Forcable; var existingItem = items.FirstOrDefault(i => i.Path.GetName() == NewItemName); diff --git a/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs b/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs index 6ae0fdd..66d1c6c 100644 --- a/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs +++ b/src/Core/FileTime.Core.ContentAccess/ContentAccessorFactory.cs @@ -10,14 +10,14 @@ public class ContentAccessorFactory : IContentAccessorFactory { _serviceProvider = serviceProvider; } - + public IItemCreator GetItemCreator() where TContentProvider : IContentProvider { var genericType = typeof(IItemCreator<>).MakeGenericType(typeof(TContentProvider)); return (IItemCreator)_serviceProvider.GetRequiredService(genericType); } - + public IItemCreator GetItemCreator(IContentProvider provider) { var genericType = typeof(IItemCreator<>).MakeGenericType(provider.GetType()); diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index 30a428b..8572764 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -2,6 +2,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using DynamicData; using DynamicData.Alias; +using FileTime.Core.Helper; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -14,6 +15,7 @@ public class Tab : ITab private readonly BehaviorSubject _currentLocationForced = new(null); private readonly BehaviorSubject _currentSelectedItem = new(null); private readonly SourceList _itemFilters = new(); + private FullName? _lastDeepestSelected; private AbsolutePath? _currentSelectedItemCached; private PointInTime _currentPointInTime; @@ -77,6 +79,11 @@ public class Tab : ITab { _currentSelectedItemCached = s; _currentSelectedItem.OnNext(s); + + if (s is not null) + { + _lastDeepestSelected = new FullName(PathHelper.GetLongerPath(_lastDeepestSelected?.Path, s.Path.Path)); + } }); } @@ -89,8 +96,32 @@ public class Tab : ITab private AbsolutePath? GetSelectedItemByItems(IEnumerable items) { - //TODO: - return new AbsolutePath(_timelessContentProvider, items.First()); + if (!items.Any()) return null; + + var newSelectedItem = new AbsolutePath(_timelessContentProvider, items.First()); + if (_lastDeepestSelected is not null) + { + var parentPath = items.First().FullName?.GetParent()?.Path; + + if (parentPath is not null && _lastDeepestSelected.Path.StartsWith(parentPath)) + { + var itemNameToSelect = _lastDeepestSelected.Path + .Split(Constants.SeparatorChar) + .Skip((parentPath?.Split(Constants.SeparatorChar).Length) ?? 0) + .FirstOrDefault(); + + var itemToSelect = items.FirstOrDefault(i => i.FullName?.GetName() == itemNameToSelect); + + if (itemToSelect != null) + { + newSelectedItem = new AbsolutePath(_timelessContentProvider, itemToSelect); + } + } + } + + _lastDeepestSelected = new FullName(PathHelper.GetLongerPath(_lastDeepestSelected?.Path, newSelectedItem.Path.Path)); + + return newSelectedItem; } public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation); diff --git a/src/Core/FileTime.Core.Timeline/CommandScheduler.cs b/src/Core/FileTime.Core.Timeline/CommandScheduler.cs index 137598d..1d0e6b7 100644 --- a/src/Core/FileTime.Core.Timeline/CommandScheduler.cs +++ b/src/Core/FileTime.Core.Timeline/CommandScheduler.cs @@ -62,7 +62,7 @@ public class CommandScheduler : ICommandScheduler await batchToAdd.AddCommand(command); await RefreshCommands(); - + ExecuteCommands(); /*if (_commandRunners.Count == 0) diff --git a/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs b/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs index bc9c859..74c298a 100644 --- a/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs +++ b/src/Core/FileTime.Core.Timeline/LocalCommandExecutor.cs @@ -11,7 +11,7 @@ public class LocalCommandExecutor : ILocalCommandExecutor { _commandRunner = commandRunner; } - + public void ExecuteCommand(ICommand command) { var context = new CommandRunnerContext(command); @@ -27,8 +27,8 @@ public class LocalCommandExecutor : ILocalCommandExecutor { await _commandRunner.RunCommandAsync(context.Command); } - catch(Exception ex){} - + catch (Exception ex) { } + CommandFinished.Invoke(this, context.Command); } } \ No newline at end of file