Base features
This commit is contained in:
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": ".NET Core Launch (console)",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"preLaunchTask": "build",
|
||||||
|
"program": "${workspaceFolder}/src/FileTime.ConsoleUI/bin/Debug/net6.0/FileTime.ConsoleUI.dll",
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/src/FileTime.ConsoleUI",
|
||||||
|
"console": "internalConsole",
|
||||||
|
"stopAtEntry": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
59
.vscode/tasks.json
vendored
Normal file
59
.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/src/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/src/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "publish singlefile linux",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"publish",
|
||||||
|
"${workspaceFolder}/src/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj",
|
||||||
|
"-p:PublishSingleFile=true",
|
||||||
|
"-c",
|
||||||
|
"Release",
|
||||||
|
"-r",
|
||||||
|
"linux-x64",
|
||||||
|
"--self-contained",
|
||||||
|
"true"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"watch",
|
||||||
|
"run",
|
||||||
|
"${workspaceFolder}/src/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
50
src/FileTime.App.Core/Clipboard/Clipboard.cs
Normal file
50
src/FileTime.App.Core/Clipboard/Clipboard.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Clipboard
|
||||||
|
{
|
||||||
|
public class Clipboard : IClipboard
|
||||||
|
{
|
||||||
|
private readonly List<ClipboardItem> _content;
|
||||||
|
public IReadOnlyList<ClipboardItem> Content { get; }
|
||||||
|
public Type? CommandType { get; private set; }
|
||||||
|
|
||||||
|
public Clipboard()
|
||||||
|
{
|
||||||
|
_content = new List<ClipboardItem>();
|
||||||
|
Content = _content.AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddContent(IContentProvider contentProvider, string path)
|
||||||
|
{
|
||||||
|
foreach (var content in _content)
|
||||||
|
{
|
||||||
|
if (content.ContentProvider == contentProvider && content.Path == path) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_content.Add(new ClipboardItem(contentProvider, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveContent(IContentProvider contentProvider, string path)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _content.Count; i++)
|
||||||
|
{
|
||||||
|
if (_content[i].ContentProvider == contentProvider && _content[i].Path == path)
|
||||||
|
{
|
||||||
|
_content.RemoveAt(i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_content.Clear();
|
||||||
|
CommandType = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetCommand<T>() where T : ITransportationCommand
|
||||||
|
{
|
||||||
|
CommandType = typeof(T);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/FileTime.App.Core/Clipboard/ClipboardItem.cs
Normal file
17
src/FileTime.App.Core/Clipboard/ClipboardItem.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Clipboard
|
||||||
|
{
|
||||||
|
public class ClipboardItem : IAbsolutePath
|
||||||
|
{
|
||||||
|
public IContentProvider ContentProvider { get; }
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
|
public ClipboardItem(IContentProvider contentProvider, string path)
|
||||||
|
{
|
||||||
|
ContentProvider = contentProvider;
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/FileTime.App.Core/Clipboard/IClipboard.cs
Normal file
16
src/FileTime.App.Core/Clipboard/IClipboard.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Clipboard
|
||||||
|
{
|
||||||
|
public interface IClipboard
|
||||||
|
{
|
||||||
|
IReadOnlyList<ClipboardItem> Content { get; }
|
||||||
|
Type? CommandType { get; }
|
||||||
|
|
||||||
|
void AddContent(IContentProvider contentProvider, string path);
|
||||||
|
void Clear();
|
||||||
|
void RemoveContent(IContentProvider contentProvider, string path);
|
||||||
|
void SetCommand<T>() where T : ITransportationCommand;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/FileTime.App.Core/FileTime.App.Core.csproj
Normal file
13
src/FileTime.App.Core/FileTime.App.Core.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FileTime.Core\FileTime.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
17
src/FileTime.App.Core/Pane/PaneItem.cs
Normal file
17
src/FileTime.App.Core/Pane/PaneItem.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Pane
|
||||||
|
{
|
||||||
|
public class PaneItem : IAbsolutePath
|
||||||
|
{
|
||||||
|
public IContentProvider ContentProvider { get; }
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
|
public PaneItem(IContentProvider contentProvider, string path)
|
||||||
|
{
|
||||||
|
ContentProvider = contentProvider;
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
src/FileTime.App.Core/Pane/PaneState.cs
Normal file
73
src/FileTime.App.Core/Pane/PaneState.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.App.Core.Pane
|
||||||
|
{
|
||||||
|
public class PaneState
|
||||||
|
{
|
||||||
|
private readonly Dictionary<IContainer, List<PaneItem>> _selectedItems;
|
||||||
|
private readonly Dictionary<IContainer, IReadOnlyList<PaneItem>> _selectedItemsReadOnly;
|
||||||
|
public IReadOnlyDictionary<IContainer, IReadOnlyList<PaneItem>> SelectedItems { get; }
|
||||||
|
|
||||||
|
public FileTime.Core.Components.Pane Pane { get; }
|
||||||
|
|
||||||
|
public PaneState(FileTime.Core.Components.Pane pane)
|
||||||
|
{
|
||||||
|
Pane = pane;
|
||||||
|
|
||||||
|
_selectedItems = new Dictionary<IContainer, List<PaneItem>>();
|
||||||
|
_selectedItemsReadOnly = new Dictionary<IContainer, IReadOnlyList<PaneItem>>();
|
||||||
|
SelectedItems = new ReadOnlyDictionary<IContainer, IReadOnlyList<PaneItem>>(_selectedItemsReadOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||||
|
{
|
||||||
|
if (!_selectedItems.ContainsKey(container))
|
||||||
|
{
|
||||||
|
var val = new List<PaneItem>();
|
||||||
|
_selectedItems.Add(container, val);
|
||||||
|
_selectedItemsReadOnly.Add(container, val.AsReadOnly());
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var content in _selectedItems[container])
|
||||||
|
{
|
||||||
|
if (content.ContentProvider == contentProvider && content.Path == path) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedItems[container].Add(new PaneItem(contentProvider, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||||
|
{
|
||||||
|
if (_selectedItems.ContainsKey(container))
|
||||||
|
{
|
||||||
|
var selectedItems = _selectedItems[container];
|
||||||
|
for (var i = 0; i < selectedItems.Count; i++)
|
||||||
|
{
|
||||||
|
if (selectedItems[i].ContentProvider == contentProvider && selectedItems[i].Path == path)
|
||||||
|
{
|
||||||
|
selectedItems.RemoveAt(i--);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||||
|
{
|
||||||
|
if (!_selectedItems.ContainsKey(container)) return false;
|
||||||
|
|
||||||
|
foreach (var content in _selectedItems[container])
|
||||||
|
{
|
||||||
|
if (content.ContentProvider == contentProvider && content.Path == path) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<PaneItem> GetCurrentSelectedItems() =>
|
||||||
|
SelectedItems.ContainsKey(Pane.CurrentLocation)
|
||||||
|
? SelectedItems[Pane.CurrentLocation]
|
||||||
|
: new List<PaneItem>().AsReadOnly();
|
||||||
|
}
|
||||||
|
}
|
||||||
254
src/FileTime.ConsoleUI.App/Application.CommandHandlers.cs
Normal file
254
src/FileTime.ConsoleUI.App/Application.CommandHandlers.cs
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App
|
||||||
|
{
|
||||||
|
public partial class Application
|
||||||
|
{
|
||||||
|
private void ClosePane()
|
||||||
|
{
|
||||||
|
var currentPaneIndex = _panes.IndexOf(_selectedPane!);
|
||||||
|
RemovePane(_selectedPane!);
|
||||||
|
|
||||||
|
if (_panes.Count > 0)
|
||||||
|
{
|
||||||
|
_selectedPane = _panes[currentPaneIndex == 0 ? 0 : currentPaneIndex - 1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_selectedPane = null;
|
||||||
|
IsRunning = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveCursorUp() => _selectedPane!.SelectPreviousItem();
|
||||||
|
private void MoveCursorDown() => _selectedPane!.SelectNextItem();
|
||||||
|
private void GoUp() => _selectedPane!.GoUp();
|
||||||
|
private void Open() => _selectedPane!.Open();
|
||||||
|
|
||||||
|
private void MoveCursorUpPage() => _selectedPane!.SelectPreviousItem(_renderers[_selectedPane].PageSize);
|
||||||
|
private void MoveCursorDownPage() => _selectedPane!.SelectNextItem(_renderers[_selectedPane].PageSize);
|
||||||
|
private void MoveCursorToTop() => _selectedPane!.SelectFirstItem();
|
||||||
|
private void MoveCursorToBottom() => _selectedPane!.SelectLastItem();
|
||||||
|
|
||||||
|
private void ToggleHidden()
|
||||||
|
{
|
||||||
|
const string hiddenFilterName = "filter_showhiddenelements";
|
||||||
|
|
||||||
|
IContainer containerToOpen = _selectedPane!.CurrentLocation;
|
||||||
|
|
||||||
|
if (_selectedPane.CurrentLocation is VirtualContainer oldVirtualContainer)
|
||||||
|
{
|
||||||
|
containerToOpen = oldVirtualContainer.HasWithName(hiddenFilterName)
|
||||||
|
? oldVirtualContainer.ExceptWithName(hiddenFilterName)
|
||||||
|
: GenerateHiddenFilterVirtualContainer(_selectedPane.CurrentLocation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
containerToOpen = GenerateHiddenFilterVirtualContainer(_selectedPane.CurrentLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedPane.OpenContainer(containerToOpen);
|
||||||
|
|
||||||
|
static VirtualContainer GenerateHiddenFilterVirtualContainer(IContainer container)
|
||||||
|
{
|
||||||
|
return new VirtualContainer(
|
||||||
|
container,
|
||||||
|
new List<Func<IEnumerable<IContainer>, IEnumerable<IContainer>>>()
|
||||||
|
{
|
||||||
|
container => container.Where(c => !c.IsHidden)
|
||||||
|
},
|
||||||
|
new List<Func<IEnumerable<IElement>, IEnumerable<IElement>>>()
|
||||||
|
{
|
||||||
|
element => element.Where(e => !e.IsHidden)
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
hiddenFilterName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Select()
|
||||||
|
{
|
||||||
|
if (_selectedPane!.CurrentSelectedItem != null)
|
||||||
|
{
|
||||||
|
var currentSelectedItem = _selectedPane.CurrentSelectedItem;
|
||||||
|
if (_paneStates[_selectedPane].ContainsSelectedItem(currentSelectedItem.Provider, _selectedPane.CurrentLocation, currentSelectedItem.FullName!))
|
||||||
|
{
|
||||||
|
_paneStates[_selectedPane].RemoveSelectedItem(currentSelectedItem.Provider, _selectedPane.CurrentLocation, currentSelectedItem.FullName!);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_paneStates[_selectedPane].AddSelectedItem(currentSelectedItem.Provider, _selectedPane.CurrentLocation, currentSelectedItem.FullName!);
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedPane.SelectNextItem();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Copy()
|
||||||
|
{
|
||||||
|
_clipboard.Clear();
|
||||||
|
_clipboard.SetCommand<CopyCommand>();
|
||||||
|
|
||||||
|
if (_paneStates[_selectedPane!].GetCurrentSelectedItems().Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var selectedItem in _paneStates[_selectedPane!].GetCurrentSelectedItems())
|
||||||
|
{
|
||||||
|
_clipboard.AddContent(selectedItem.ContentProvider, selectedItem.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_clipboard.AddContent(_selectedPane!.CurrentSelectedItem!.Provider, _selectedPane.CurrentSelectedItem.FullName!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cut()
|
||||||
|
{
|
||||||
|
_clipboard.Clear();
|
||||||
|
_clipboard.SetCommand<MoveCommand>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PasteMerge()
|
||||||
|
{
|
||||||
|
Paste(TransportMode.Merge);
|
||||||
|
}
|
||||||
|
public void PasteOverwrite()
|
||||||
|
{
|
||||||
|
Paste(TransportMode.Overwrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PasteSkip()
|
||||||
|
{
|
||||||
|
Paste(TransportMode.Skip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Paste(TransportMode transportMode)
|
||||||
|
{
|
||||||
|
if (_clipboard.CommandType != null)
|
||||||
|
{
|
||||||
|
var command = (ITransportationCommand)Activator.CreateInstance(_clipboard.CommandType!)!;
|
||||||
|
command.TransportMode = transportMode;
|
||||||
|
|
||||||
|
command.Sources.Clear();
|
||||||
|
|
||||||
|
foreach (var item in _clipboard.Content)
|
||||||
|
{
|
||||||
|
command.Sources.Add(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
command.Target = _selectedPane.CurrentLocation is VirtualContainer virtualContainer
|
||||||
|
? virtualContainer.BaseContainer
|
||||||
|
: _selectedPane.CurrentLocation;
|
||||||
|
|
||||||
|
_commandExecutor.ExecuteCommand(command);
|
||||||
|
|
||||||
|
_clipboard.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateContainer()
|
||||||
|
{
|
||||||
|
if (_selectedPane?.CurrentLocation != null)
|
||||||
|
{
|
||||||
|
_coloredConsoleRenderer.ResetColor();
|
||||||
|
MoveToIOLine(2);
|
||||||
|
_coloredConsoleRenderer.Write("New container name: ");
|
||||||
|
var newContainerName = _consoleReader.ReadText(validator: Validator);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(newContainerName))
|
||||||
|
{
|
||||||
|
_selectedPane.CurrentLocation.CreateContainer(newContainerName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Validator(string newPath)
|
||||||
|
{
|
||||||
|
if (_selectedPane!.CurrentLocation.IsExists(newPath))
|
||||||
|
{
|
||||||
|
_coloredConsoleRenderer.ForegroundColor = AnsiColor.From8bit(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_coloredConsoleRenderer.ResetColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HardDelete()
|
||||||
|
{
|
||||||
|
IList<IAbsolutePath> itemsToDelete = null;
|
||||||
|
|
||||||
|
if (_paneStates[_selectedPane!].GetCurrentSelectedItems().Count > 0)
|
||||||
|
{
|
||||||
|
var delete = true;
|
||||||
|
|
||||||
|
if (_paneStates[_selectedPane!].GetCurrentSelectedItems().Count == 1
|
||||||
|
&& _paneStates[_selectedPane!].GetCurrentSelectedItems()[0] is IContainer container
|
||||||
|
&& container.Items.Count > 0)
|
||||||
|
{
|
||||||
|
delete = AskForApprove($"The container '{container.Name}' is not empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delete)
|
||||||
|
{
|
||||||
|
itemsToDelete = _paneStates[_selectedPane].GetCurrentSelectedItems().Cast<IAbsolutePath>().ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_selectedPane?.CurrentSelectedItem != null)
|
||||||
|
{
|
||||||
|
bool delete = true;
|
||||||
|
if (_selectedPane?.CurrentSelectedItem is IContainer container && container.Items.Count > 0)
|
||||||
|
{
|
||||||
|
delete = AskForApprove($"The container '{container.Name}' is not empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delete)
|
||||||
|
{
|
||||||
|
itemsToDelete = new List<IAbsolutePath>()
|
||||||
|
{
|
||||||
|
new AbsolutePath(_selectedPane.CurrentSelectedItem.Provider, _selectedPane.CurrentSelectedItem.FullName!)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemsToDelete != null)
|
||||||
|
{
|
||||||
|
var deleteCommand = new DeleteCommand();
|
||||||
|
|
||||||
|
foreach (var itemToDelete in itemsToDelete)
|
||||||
|
{
|
||||||
|
deleteCommand.ItemsToDelete.Add(itemToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
_commandExecutor.ExecuteCommand(deleteCommand);
|
||||||
|
_clipboard.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AskForApprove(string name)
|
||||||
|
{
|
||||||
|
MoveToIOLine(2);
|
||||||
|
_coloredConsoleRenderer.Write(name + " Proceed to delete? (Y/N)");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var key = Console.ReadKey(true);
|
||||||
|
|
||||||
|
if (key.Key == ConsoleKey.Y)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.N)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
271
src/FileTime.ConsoleUI.App/Application.cs
Normal file
271
src/FileTime.ConsoleUI.App/Application.cs
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.ConsoleUI.App.UI;
|
||||||
|
using FileTime.ConsoleUI.App.Command;
|
||||||
|
using FileTime.Core.Components;
|
||||||
|
using FileTime.Core.Extensions;
|
||||||
|
using FileTime.App.Core.Clipboard;
|
||||||
|
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using FileTime.App.Core.Pane;
|
||||||
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
|
using FileTime.Core.Command;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App
|
||||||
|
{
|
||||||
|
public partial class Application
|
||||||
|
{
|
||||||
|
private readonly List<Pane> _panes = new();
|
||||||
|
private readonly Dictionary<Pane, Render> _renderers = new();
|
||||||
|
private readonly Dictionary<Pane, PaneState> _paneStates = new();
|
||||||
|
private Pane? _selectedPane;
|
||||||
|
|
||||||
|
private readonly List<CommandBinding> _commandBindings = new();
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
private readonly IClipboard _clipboard;
|
||||||
|
private readonly IColoredConsoleRenderer _coloredConsoleRenderer;
|
||||||
|
private readonly CommandExecutor _commandExecutor;
|
||||||
|
private readonly ConsoleReader _consoleReader;
|
||||||
|
private readonly List<ConsoleKeyInfo> _previousKeys = new();
|
||||||
|
|
||||||
|
public bool IsRunning { get; private set; } = true;
|
||||||
|
|
||||||
|
public Application(
|
||||||
|
IServiceProvider serviceProvider,
|
||||||
|
IClipboard clipboard,
|
||||||
|
IColoredConsoleRenderer coloredConsoleRenderer,
|
||||||
|
CommandExecutor commandExecutor,
|
||||||
|
ConsoleReader consoleReader)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
_clipboard = clipboard;
|
||||||
|
_coloredConsoleRenderer = coloredConsoleRenderer;
|
||||||
|
_commandExecutor = commandExecutor;
|
||||||
|
_consoleReader = consoleReader;
|
||||||
|
InitCommandBindings();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetContainer(IContainer currentPath)
|
||||||
|
{
|
||||||
|
_selectedPane = CreatePane(currentPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Pane CreatePane(IContainer container)
|
||||||
|
{
|
||||||
|
var pane = new Pane(container);
|
||||||
|
_panes.Add(pane);
|
||||||
|
|
||||||
|
var paneState = new PaneState(pane);
|
||||||
|
_paneStates.Add(pane, paneState);
|
||||||
|
|
||||||
|
var renderer = _serviceProvider.GetService<Render>()!;
|
||||||
|
renderer.Init(pane, paneState);
|
||||||
|
_renderers.Add(pane, renderer);
|
||||||
|
|
||||||
|
return pane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemovePane(Pane pane)
|
||||||
|
{
|
||||||
|
_panes.Remove(pane);
|
||||||
|
_renderers.Remove(pane);
|
||||||
|
_paneStates.Remove(pane);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitCommandBindings()
|
||||||
|
{
|
||||||
|
var commandBindings = new List<CommandBinding>()
|
||||||
|
{
|
||||||
|
new CommandBinding("close pane", Commands.ClosePane, new[] { new ConsoleKeyInfo('q', ConsoleKey.Q, false, false, false) }, ClosePane),
|
||||||
|
new CommandBinding("cursor up", Commands.MoveCursorUp, new[] { new ConsoleKeyInfo('↑', ConsoleKey.UpArrow, false, false, false) }, MoveCursorUp),
|
||||||
|
new CommandBinding("cursor down", Commands.MoveCursorDown, new[] { new ConsoleKeyInfo('↓', ConsoleKey.DownArrow, false, false, false) }, MoveCursorDown),
|
||||||
|
new CommandBinding("cursor page up", Commands.MoveCursorUpPage, new[] { new ConsoleKeyInfo(' ', ConsoleKey.PageUp, false, false, false) }, MoveCursorUpPage),
|
||||||
|
new CommandBinding("cursor page down", Commands.MoveCursorDownPage, new[] { new ConsoleKeyInfo(' ', ConsoleKey.PageDown, false, false, false) }, MoveCursorDownPage),
|
||||||
|
new CommandBinding("go up", Commands.GoUp, new[] { new ConsoleKeyInfo('←', ConsoleKey.LeftArrow, false, false, false) }, GoUp),
|
||||||
|
new CommandBinding("open", Commands.Open, new[] { new ConsoleKeyInfo('→', ConsoleKey.RightArrow, false, false, false) }, Open),
|
||||||
|
new CommandBinding(
|
||||||
|
"go to top",
|
||||||
|
Commands.GoToTop,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('g', ConsoleKey.G, false, false, false),
|
||||||
|
new ConsoleKeyInfo('g', ConsoleKey.G, false, false, false)
|
||||||
|
},
|
||||||
|
MoveCursorToTop),
|
||||||
|
new CommandBinding(
|
||||||
|
"go to bottom",
|
||||||
|
Commands.GoToBottom,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('G', ConsoleKey.G, true, false, false)
|
||||||
|
},
|
||||||
|
MoveCursorToBottom),
|
||||||
|
new CommandBinding(
|
||||||
|
"toggle hidden",
|
||||||
|
Commands.ToggleHidden,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('z', ConsoleKey.Z, false, false, false),
|
||||||
|
new ConsoleKeyInfo('h', ConsoleKey.H, false, false, false)
|
||||||
|
},
|
||||||
|
ToggleHidden),
|
||||||
|
new CommandBinding("select", Commands.Select, new[] { new ConsoleKeyInfo(' ', ConsoleKey.Spacebar, false, false, false) }, Select),
|
||||||
|
new CommandBinding(
|
||||||
|
"copy",
|
||||||
|
Commands.Copy,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('y', ConsoleKey.Y, false, false, false),
|
||||||
|
new ConsoleKeyInfo('y', ConsoleKey.Y, false, false, false)
|
||||||
|
},
|
||||||
|
Copy),
|
||||||
|
new CommandBinding(
|
||||||
|
"cut",
|
||||||
|
Commands.Cut,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('d', ConsoleKey.D, false, false, false),
|
||||||
|
new ConsoleKeyInfo('d', ConsoleKey.D, false, false, false)
|
||||||
|
},
|
||||||
|
Cut),
|
||||||
|
new CommandBinding(
|
||||||
|
"paste (merge)",
|
||||||
|
Commands.Paste,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||||
|
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false)
|
||||||
|
},
|
||||||
|
PasteMerge),
|
||||||
|
new CommandBinding(
|
||||||
|
"paste (overwrite)",
|
||||||
|
Commands.Paste,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||||
|
new ConsoleKeyInfo('o', ConsoleKey.O, false, false, false)
|
||||||
|
},
|
||||||
|
PasteOverwrite),
|
||||||
|
new CommandBinding(
|
||||||
|
"paste (skip)",
|
||||||
|
Commands.Paste,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||||
|
new ConsoleKeyInfo('s', ConsoleKey.S, false, false, false)
|
||||||
|
},
|
||||||
|
PasteSkip),
|
||||||
|
new CommandBinding(
|
||||||
|
"create container",
|
||||||
|
Commands.CreateContainer,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('c', ConsoleKey.C, false, false, false),
|
||||||
|
new ConsoleKeyInfo('c', ConsoleKey.C, false, false, false)
|
||||||
|
},
|
||||||
|
CreateContainer),
|
||||||
|
new CommandBinding(
|
||||||
|
"delete",
|
||||||
|
Commands.CreateContainer,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new ConsoleKeyInfo('d', ConsoleKey.D, false, false, false),
|
||||||
|
new ConsoleKeyInfo('D', ConsoleKey.D, true, false, false)
|
||||||
|
},
|
||||||
|
HardDelete),
|
||||||
|
};
|
||||||
|
|
||||||
|
_commandBindings.AddRange(commandBindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintUI()
|
||||||
|
{
|
||||||
|
if (_selectedPane != null)
|
||||||
|
{
|
||||||
|
_renderers[_selectedPane].PrintUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ProcessKey(ConsoleKeyInfo keyinfo)
|
||||||
|
{
|
||||||
|
var key = keyinfo.Key;
|
||||||
|
_previousKeys.Add(keyinfo);
|
||||||
|
|
||||||
|
CommandBinding? selectedCommandBinding = _commandBindings.Find(c => AreKeysEqual(c.Keys, _previousKeys));
|
||||||
|
|
||||||
|
if (keyinfo.Key == ConsoleKey.Escape)
|
||||||
|
{
|
||||||
|
_previousKeys.Clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (_previousKeys.Count == 2 && selectedCommandBinding == null)
|
||||||
|
{
|
||||||
|
HandleNoCommandFound();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (selectedCommandBinding != null)
|
||||||
|
{
|
||||||
|
selectedCommandBinding.Invoke();
|
||||||
|
_previousKeys.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.ResetColor();
|
||||||
|
int commandToDisplay = 0;
|
||||||
|
var possibleCommands = _commandBindings.Where(c => AreKeysEqual(c.Keys[0], keyinfo)).ToList();
|
||||||
|
|
||||||
|
if (possibleCommands.Count == 0)
|
||||||
|
{
|
||||||
|
HandleNoCommandFound();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var commandBinding in possibleCommands)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(10, Console.WindowHeight - 1 - possibleCommands.Count + commandToDisplay++);
|
||||||
|
_coloredConsoleRenderer.Write(
|
||||||
|
$"{{0,-{Console.WindowWidth - 10}}}",
|
||||||
|
string.Concat(commandBinding.Keys.Select(k => DisplayKey(k).Map(s => s.Length > 1 ? $" {s} " : s))) + ": " + commandBinding.Name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
void HandleNoCommandFound()
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(10, Console.WindowHeight - 2);
|
||||||
|
_coloredConsoleRenderer.Write(
|
||||||
|
$"{{0,-{Console.WindowWidth - 10}}}",
|
||||||
|
"No command found for key(s): " + string.Format("{0,-20}", string.Concat(_previousKeys.Select(k => DisplayKey(k).Map(s => s.Length > 1 ? $" {s} " : s))))
|
||||||
|
);
|
||||||
|
_previousKeys.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DisplayKey(ConsoleKeyInfo keyInfo) =>
|
||||||
|
string.IsNullOrWhiteSpace(keyInfo.KeyChar.ToString()) || keyInfo.KeyChar == '\0'
|
||||||
|
? keyInfo.Key.ToString()
|
||||||
|
: keyInfo.KeyChar.ToString();
|
||||||
|
|
||||||
|
private static bool AreKeysEqual(IReadOnlyList<ConsoleKeyInfo> collection1, IReadOnlyList<ConsoleKeyInfo> collection2)
|
||||||
|
{
|
||||||
|
if (collection1.Count != collection2.Count) return false;
|
||||||
|
|
||||||
|
for (var i = 0; i < collection1.Count; i++)
|
||||||
|
{
|
||||||
|
if (!AreKeysEqual(collection1[i], collection2[i])) return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AreKeysEqual(ConsoleKeyInfo keyInfo1, ConsoleKeyInfo keyInfo2) =>
|
||||||
|
keyInfo1.Key == keyInfo2.Key && keyInfo1.Modifiers == keyInfo2.Modifiers;
|
||||||
|
|
||||||
|
private static void MoveToIOLine(int left = 0)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(left, Console.WindowHeight - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/FileTime.ConsoleUI.App/Command/CommandBinding.cs
Normal file
22
src/FileTime.ConsoleUI.App/Command/CommandBinding.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.Command
|
||||||
|
{
|
||||||
|
public class CommandBinding
|
||||||
|
{
|
||||||
|
private readonly Action _commandHandler;
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public ConsoleKeyInfo[] Keys { get; }
|
||||||
|
public Commands Command { get; }
|
||||||
|
|
||||||
|
public CommandBinding(string name, Commands command, ConsoleKeyInfo[] keys, Action commandHandler)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Command = command;
|
||||||
|
Keys = keys;
|
||||||
|
_commandHandler = commandHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Invoke() => _commandHandler();
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/FileTime.ConsoleUI.App/Command/Commands.cs
Normal file
22
src/FileTime.ConsoleUI.App/Command/Commands.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.Command
|
||||||
|
{
|
||||||
|
public enum Commands
|
||||||
|
{
|
||||||
|
ClosePane,
|
||||||
|
Copy,
|
||||||
|
Cut,
|
||||||
|
GoUp,
|
||||||
|
MoveCursorDown,
|
||||||
|
MoveCursorUp,
|
||||||
|
Open,
|
||||||
|
Paste,
|
||||||
|
Select,
|
||||||
|
ToggleHidden,
|
||||||
|
CreateContainer,
|
||||||
|
CreateElement,
|
||||||
|
MoveCursorUpPage,
|
||||||
|
MoveCursorDownPage,
|
||||||
|
GoToTop,
|
||||||
|
GoToBottom,
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/FileTime.ConsoleUI.App/FileTime.ConsoleUI.App.csproj
Normal file
14
src/FileTime.ConsoleUI.App/FileTime.ConsoleUI.App.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FileTime.Core\FileTime.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
25
src/FileTime.ConsoleUI.App/UI/Color/AnsiColor.cs
Normal file
25
src/FileTime.ConsoleUI.App/UI/Color/AnsiColor.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.UI.Color
|
||||||
|
{
|
||||||
|
public class AnsiColor : IConsoleColor
|
||||||
|
{
|
||||||
|
public string? Color { get; }
|
||||||
|
public string? Prefix { get; }
|
||||||
|
public string? Postfix { get; }
|
||||||
|
public AnsiColor() { }
|
||||||
|
|
||||||
|
public AnsiColor(string color, string preFix = "5;", string postFix = "m")
|
||||||
|
{
|
||||||
|
Color = color;
|
||||||
|
Prefix = preFix;
|
||||||
|
Postfix = postFix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AnsiColor FromRgb(int r, int g, int b) => new($"{r};{g};{b}", "5;", "m");
|
||||||
|
public static AnsiColor From8bit(byte color) => new($"{color}", "5;", "m");
|
||||||
|
|
||||||
|
public override string? ToString()
|
||||||
|
{
|
||||||
|
return Color == null ? null : Prefix + Color + Postfix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/FileTime.ConsoleUI.App/UI/Color/BasicColor.cs
Normal file
14
src/FileTime.ConsoleUI.App/UI/Color/BasicColor.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.UI.Color
|
||||||
|
{
|
||||||
|
public class BasicColor : IConsoleColor
|
||||||
|
{
|
||||||
|
public BasicColor() { }
|
||||||
|
|
||||||
|
public BasicColor(ConsoleColor color)
|
||||||
|
{
|
||||||
|
Color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConsoleColor? Color { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.UI.Color
|
||||||
|
{
|
||||||
|
public class ColoredConsoleRenderer : IColoredConsoleRenderer
|
||||||
|
{
|
||||||
|
private readonly IStyles _styles;
|
||||||
|
|
||||||
|
public IConsoleColor? BackgroundColor { get; set; }
|
||||||
|
public IConsoleColor? ForegroundColor { get; set; }
|
||||||
|
|
||||||
|
public ColoredConsoleRenderer(IStyles styles)
|
||||||
|
{
|
||||||
|
_styles = styles;
|
||||||
|
BackgroundColor = _styles.DefaultBackground;
|
||||||
|
ForegroundColor = _styles.DefaultForeground;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string text) => DoWrite(text);
|
||||||
|
public void Write(char c) => DoWrite(c.ToString());
|
||||||
|
public void Write(string format, params object[] param) => DoWrite(string.Format(format, param));
|
||||||
|
|
||||||
|
private void DoWrite(string text)
|
||||||
|
{
|
||||||
|
if (BackgroundColor is AnsiColor ansiBackground && ForegroundColor is AnsiColor ansiForeground)
|
||||||
|
{
|
||||||
|
var formatting = "\u001b[0m";
|
||||||
|
|
||||||
|
if (ansiBackground.Color != null)
|
||||||
|
{
|
||||||
|
formatting += "\u001b[48;" + ansiBackground.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ansiForeground.Color != null)
|
||||||
|
{
|
||||||
|
formatting += "\u001b[38;" + ansiForeground.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.Write(formatting + text);
|
||||||
|
}
|
||||||
|
else if (BackgroundColor is BasicColor basicBackground && ForegroundColor is BasicColor basicForeground)
|
||||||
|
{
|
||||||
|
Console.BackgroundColor = basicBackground.Color ?? Console.BackgroundColor;
|
||||||
|
Console.ForegroundColor = basicForeground.Color ?? Console.ForegroundColor;
|
||||||
|
|
||||||
|
Console.Write(text);
|
||||||
|
}
|
||||||
|
else if (BackgroundColor == null && ForegroundColor == null)
|
||||||
|
{
|
||||||
|
Console.Write(text);
|
||||||
|
}
|
||||||
|
else if (BackgroundColor == null || ForegroundColor == null)
|
||||||
|
{
|
||||||
|
throw new Exception($"Either both of {nameof(BackgroundColor)} and {nameof(ForegroundColor)} must be null or neither of them.");
|
||||||
|
}
|
||||||
|
else if (BackgroundColor.GetType() != ForegroundColor.GetType())
|
||||||
|
{
|
||||||
|
throw new Exception($"Type of {nameof(BackgroundColor)} and {nameof(ForegroundColor)} must be the same.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new Exception($"Unsupported color type: {BackgroundColor.GetType()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetColor()
|
||||||
|
{
|
||||||
|
BackgroundColor = _styles.DefaultBackground;
|
||||||
|
ForegroundColor = _styles.DefaultForeground;
|
||||||
|
|
||||||
|
Console.ResetColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(0, 0);
|
||||||
|
Write(new string(' ', Console.WindowHeight * Console.WindowWidth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.UI.Color
|
||||||
|
{
|
||||||
|
public interface IColoredConsoleRenderer
|
||||||
|
{
|
||||||
|
IConsoleColor? BackgroundColor { get; set; }
|
||||||
|
IConsoleColor? ForegroundColor { get; set; }
|
||||||
|
|
||||||
|
void Clear();
|
||||||
|
void ResetColor();
|
||||||
|
void Write(string text);
|
||||||
|
void Write(char c);
|
||||||
|
void Write(string format, params object[] param);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/FileTime.ConsoleUI.App/UI/Color/IConsoleColor.cs
Normal file
4
src/FileTime.ConsoleUI.App/UI/Color/IConsoleColor.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
namespace FileTime.ConsoleUI.App.UI.Color
|
||||||
|
{
|
||||||
|
public interface IConsoleColor { }
|
||||||
|
}
|
||||||
89
src/FileTime.ConsoleUI.App/UI/ConsoleReader.cs
Normal file
89
src/FileTime.ConsoleUI.App/UI/ConsoleReader.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.UI
|
||||||
|
{
|
||||||
|
public class ConsoleReader
|
||||||
|
{
|
||||||
|
private readonly IColoredConsoleRenderer _coloredConsoleRenderer;
|
||||||
|
|
||||||
|
public ConsoleReader(IColoredConsoleRenderer coloredConsoleRenderer)
|
||||||
|
{
|
||||||
|
_coloredConsoleRenderer = coloredConsoleRenderer;
|
||||||
|
}
|
||||||
|
public string ReadText(int? maxLength = null, Action<string>? validator = null)
|
||||||
|
{
|
||||||
|
var cursorVisible = false;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cursorVisible = Console.CursorVisible;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
Console.CursorVisible = true;
|
||||||
|
|
||||||
|
var currentConsoleLeft = Console.CursorLeft;
|
||||||
|
var currentConsoleTop = Console.CursorTop;
|
||||||
|
|
||||||
|
maxLength ??= Console.WindowWidth - currentConsoleLeft;
|
||||||
|
|
||||||
|
var input = "";
|
||||||
|
var position = 0;
|
||||||
|
|
||||||
|
_coloredConsoleRenderer.Write($"{{0,-{maxLength}}}", input);
|
||||||
|
|
||||||
|
var key = Console.ReadKey(true);
|
||||||
|
while (key.Key != ConsoleKey.Enter)
|
||||||
|
{
|
||||||
|
if (key.Key == ConsoleKey.Escape)
|
||||||
|
{
|
||||||
|
input = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.Backspace)
|
||||||
|
{
|
||||||
|
if (position != 0)
|
||||||
|
{
|
||||||
|
input = input.Length > 0
|
||||||
|
? input[..(position - 1)] + input[position..]
|
||||||
|
: input;
|
||||||
|
|
||||||
|
position--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.LeftArrow)
|
||||||
|
{
|
||||||
|
if (position > 0)
|
||||||
|
position--;
|
||||||
|
}
|
||||||
|
else if (key.Key == ConsoleKey.RightArrow)
|
||||||
|
{
|
||||||
|
if (position < input.Length)
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
else if (key.KeyChar != '\0')
|
||||||
|
{
|
||||||
|
var newInput = input[..position] + key.KeyChar;
|
||||||
|
|
||||||
|
if (position < input.Length)
|
||||||
|
{
|
||||||
|
newInput += input[position..];
|
||||||
|
}
|
||||||
|
|
||||||
|
input = newInput;
|
||||||
|
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
validator?.Invoke(input);
|
||||||
|
|
||||||
|
Console.SetCursorPosition(currentConsoleLeft, currentConsoleTop);
|
||||||
|
_coloredConsoleRenderer.Write($"{{0,-{maxLength}}}", input);
|
||||||
|
|
||||||
|
Console.SetCursorPosition(currentConsoleLeft + position, currentConsoleTop);
|
||||||
|
key = Console.ReadKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.CursorVisible = cursorVisible;
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/FileTime.ConsoleUI.App/UI/IStyles.cs
Normal file
18
src/FileTime.ConsoleUI.App/UI/IStyles.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.UI
|
||||||
|
{
|
||||||
|
public interface IStyles
|
||||||
|
{
|
||||||
|
IConsoleColor? DefaultBackground { get; }
|
||||||
|
IConsoleColor? DefaultForeground { get; }
|
||||||
|
IConsoleColor? ContainerBackground { get; }
|
||||||
|
IConsoleColor? ContainerForeground { get; }
|
||||||
|
IConsoleColor? ElementBackground { get; }
|
||||||
|
IConsoleColor? ElementForeground { get; }
|
||||||
|
IConsoleColor? ElementSpecialBackground { get; }
|
||||||
|
IConsoleColor? ElementSpecialForeground { get; }
|
||||||
|
IConsoleColor? SelectedItemBackground { get; }
|
||||||
|
IConsoleColor? SelectedItemForeground { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/FileTime.ConsoleUI.App/UI/PrintMode.cs
Normal file
9
src/FileTime.ConsoleUI.App/UI/PrintMode.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace FileTime.ConsoleUI.UI.App
|
||||||
|
{
|
||||||
|
public enum PrintMode
|
||||||
|
{
|
||||||
|
Previous,
|
||||||
|
Current,
|
||||||
|
Next
|
||||||
|
}
|
||||||
|
}
|
||||||
281
src/FileTime.ConsoleUI.App/UI/Render.cs
Normal file
281
src/FileTime.ConsoleUI.App/UI/Render.cs
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
using FileTime.App.Core.Pane;
|
||||||
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
|
using FileTime.ConsoleUI.UI.App;
|
||||||
|
using FileTime.Core.Components;
|
||||||
|
using FileTime.Core.Extensions;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.UI
|
||||||
|
{
|
||||||
|
public class Render
|
||||||
|
{
|
||||||
|
private const int _contentPaddingTop = 1;
|
||||||
|
private const int _contentPaddingBottom = 2;
|
||||||
|
private readonly int _contentRowCount;
|
||||||
|
private readonly IStyles _appStyle;
|
||||||
|
private readonly IColoredConsoleRenderer _coloredRenderer;
|
||||||
|
|
||||||
|
private int _currentDisplayStartY;
|
||||||
|
|
||||||
|
private const int ITEMPADDINGLEFT = 1;
|
||||||
|
private const int ITEMPADDINGRIGHT = 2;
|
||||||
|
|
||||||
|
private readonly string _paddingLeft;
|
||||||
|
private readonly string _paddingRight;
|
||||||
|
|
||||||
|
public Pane Pane { get; private set; }
|
||||||
|
public PaneState PaneState { get; private set; }
|
||||||
|
|
||||||
|
public int PageSize => Console.WindowHeight - _contentPaddingTop - _contentPaddingBottom;
|
||||||
|
public Render(IColoredConsoleRenderer coloredRenderer, IStyles appStyle)
|
||||||
|
{
|
||||||
|
_coloredRenderer = coloredRenderer;
|
||||||
|
_appStyle = appStyle;
|
||||||
|
|
||||||
|
_paddingLeft = new string(' ', ITEMPADDINGLEFT);
|
||||||
|
_paddingRight = new string(' ', ITEMPADDINGRIGHT);
|
||||||
|
_contentRowCount = Console.WindowHeight - _contentPaddingTop - _contentPaddingBottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Init(Pane pane, PaneState paneState)
|
||||||
|
{
|
||||||
|
if (pane == null) throw new Exception($"{nameof(pane)} can not be null");
|
||||||
|
if (paneState == null) throw new Exception($"{nameof(paneState)} can not be null");
|
||||||
|
|
||||||
|
Pane = pane;
|
||||||
|
Pane.CurrentLocationChanged += (o, e) => _currentDisplayStartY = 0;
|
||||||
|
|
||||||
|
PaneState = paneState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrintUI()
|
||||||
|
{
|
||||||
|
if (Pane != null)
|
||||||
|
{
|
||||||
|
PrintPrompt();
|
||||||
|
PrintPanes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintPanes()
|
||||||
|
{
|
||||||
|
var previousColumnWidth = (int)Math.Floor(Console.WindowWidth * 0.15) - 1;
|
||||||
|
var currentColumnWidth = (int)Math.Floor(Console.WindowWidth * 0.4) - 1;
|
||||||
|
var nextColumnWidth = Console.WindowWidth - currentColumnWidth - previousColumnWidth - 2;
|
||||||
|
var currentVirtualContainer = Pane!.CurrentLocation as VirtualContainer;
|
||||||
|
|
||||||
|
if (Pane.CurrentLocation.GetParent() is var parentContainer && parentContainer is not null)
|
||||||
|
{
|
||||||
|
parentContainer.Refresh();
|
||||||
|
|
||||||
|
PrintColumn(
|
||||||
|
currentVirtualContainer != null
|
||||||
|
? currentVirtualContainer.CloneVirtualChainFor(parentContainer, v => v.IsTransitive)
|
||||||
|
: parentContainer,
|
||||||
|
currentVirtualContainer != null
|
||||||
|
? currentVirtualContainer.GetRealContainer()
|
||||||
|
: Pane.CurrentLocation,
|
||||||
|
PrintMode.Previous,
|
||||||
|
0,
|
||||||
|
_contentPaddingTop,
|
||||||
|
previousColumnWidth,
|
||||||
|
_contentRowCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CleanColumn(
|
||||||
|
0,
|
||||||
|
_contentPaddingTop,
|
||||||
|
previousColumnWidth,
|
||||||
|
_contentRowCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pane.CurrentLocation.Refresh();
|
||||||
|
|
||||||
|
CheckAndSetCurrentDisplayStartY();
|
||||||
|
PrintColumn(
|
||||||
|
Pane.CurrentLocation,
|
||||||
|
Pane.CurrentSelectedItem,
|
||||||
|
PrintMode.Current,
|
||||||
|
previousColumnWidth + 1,
|
||||||
|
_contentPaddingTop,
|
||||||
|
currentColumnWidth,
|
||||||
|
_contentRowCount);
|
||||||
|
|
||||||
|
if (Pane.CurrentSelectedItem is IContainer selectedContainer)
|
||||||
|
{
|
||||||
|
selectedContainer.Refresh();
|
||||||
|
|
||||||
|
selectedContainer = currentVirtualContainer != null
|
||||||
|
? currentVirtualContainer.CloneVirtualChainFor(selectedContainer, v => v.IsTransitive)
|
||||||
|
: selectedContainer;
|
||||||
|
|
||||||
|
PrintColumn(
|
||||||
|
selectedContainer,
|
||||||
|
selectedContainer.Items.Count > 0 ? selectedContainer.Items[0] : null,
|
||||||
|
PrintMode.Next,
|
||||||
|
previousColumnWidth + currentColumnWidth + 2,
|
||||||
|
_contentPaddingTop,
|
||||||
|
nextColumnWidth,
|
||||||
|
_contentRowCount);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CleanColumn(
|
||||||
|
previousColumnWidth + currentColumnWidth + 2,
|
||||||
|
_contentPaddingTop,
|
||||||
|
nextColumnWidth,
|
||||||
|
_contentRowCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintPrompt()
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(0, 0);
|
||||||
|
_coloredRenderer.ResetColor();
|
||||||
|
_coloredRenderer.ForegroundColor = AnsiColor.From8bit(2);
|
||||||
|
_coloredRenderer.Write(Environment.UserName + "@" + Environment.MachineName);
|
||||||
|
|
||||||
|
_coloredRenderer.ResetColor();
|
||||||
|
_coloredRenderer.Write(' ');
|
||||||
|
|
||||||
|
_coloredRenderer.ForegroundColor = AnsiColor.From8bit(4);
|
||||||
|
var path = Pane!.CurrentLocation.FullName + "/";
|
||||||
|
_coloredRenderer.Write(path);
|
||||||
|
|
||||||
|
if (Pane.CurrentSelectedItem?.Name != null)
|
||||||
|
{
|
||||||
|
_coloredRenderer.ResetColor();
|
||||||
|
_coloredRenderer.Write($"{{0,-{300 - path.Length}}}", Pane.CurrentSelectedItem.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintColumn(IContainer currentContainer, IItem? currentItem, PrintMode printMode, int startX, int startY, int elementWidth, int availableRows)
|
||||||
|
{
|
||||||
|
var allItem = currentContainer.Containers.Cast<IItem>().Concat(currentContainer.Elements).ToList();
|
||||||
|
var printedItemsCount = 0;
|
||||||
|
var currentY = 0;
|
||||||
|
if (allItem.Count > 0)
|
||||||
|
{
|
||||||
|
var currentIndex = allItem.FindIndex(i => i == currentItem);
|
||||||
|
|
||||||
|
var skipElements = printMode switch
|
||||||
|
{
|
||||||
|
PrintMode.Previous => (currentIndex - (availableRows / 2)).Map(r => r < 0 ? 0 : r),
|
||||||
|
PrintMode.Current => _currentDisplayStartY,
|
||||||
|
PrintMode.Next => 0,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
|
||||||
|
var maxTextWidth = elementWidth - ITEMPADDINGLEFT - ITEMPADDINGRIGHT;
|
||||||
|
|
||||||
|
var itemsToPrint = currentContainer.Items.Skip(skipElements).Take(availableRows).ToList();
|
||||||
|
printedItemsCount = itemsToPrint.Count;
|
||||||
|
foreach (var item in itemsToPrint)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(startX, startY + currentY++);
|
||||||
|
var namePart = item.Name.Length > maxTextWidth
|
||||||
|
? string.Concat(item.Name.AsSpan(0, maxTextWidth - 1), "~")
|
||||||
|
: item.Name;
|
||||||
|
|
||||||
|
var attributePart = "";
|
||||||
|
|
||||||
|
var container = item as IContainer;
|
||||||
|
var element = item as IElement;
|
||||||
|
|
||||||
|
IConsoleColor? backgroundColor = null;
|
||||||
|
IConsoleColor? foregroundColor = null;
|
||||||
|
|
||||||
|
if (container != null)
|
||||||
|
{
|
||||||
|
backgroundColor = _appStyle.ContainerBackground;
|
||||||
|
foregroundColor = _appStyle.ContainerForeground;
|
||||||
|
}
|
||||||
|
else if (element != null)
|
||||||
|
{
|
||||||
|
if (element.IsSpecial)
|
||||||
|
{
|
||||||
|
backgroundColor = _appStyle.ElementSpecialBackground;
|
||||||
|
foregroundColor = _appStyle.ElementSpecialForeground;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
backgroundColor = _appStyle.ElementBackground;
|
||||||
|
foregroundColor = _appStyle.ElementForeground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isSelected = PaneState.ContainsSelectedItem(item.Provider, currentContainer, item.FullName!);
|
||||||
|
if (isSelected)
|
||||||
|
{
|
||||||
|
backgroundColor = _appStyle.SelectedItemBackground;
|
||||||
|
foregroundColor = _appStyle.SelectedItemForeground;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item == currentItem)
|
||||||
|
{
|
||||||
|
(backgroundColor, foregroundColor) = (foregroundColor, backgroundColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
_coloredRenderer.BackgroundColor = backgroundColor;
|
||||||
|
_coloredRenderer.ForegroundColor = foregroundColor;
|
||||||
|
|
||||||
|
attributePart = container != null ? "" + container.Items.Count : element!.GetPrimaryAttributeText();
|
||||||
|
|
||||||
|
var text = string.Format($"{{0,-{elementWidth}}}", _paddingLeft + (isSelected ? " " : "") + namePart + _paddingRight);
|
||||||
|
text = string.Concat(text.AsSpan(0, text.Length - attributePart.Length - 1), " ", attributePart);
|
||||||
|
|
||||||
|
_coloredRenderer.Write(text);
|
||||||
|
|
||||||
|
_coloredRenderer.ResetColor();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_coloredRenderer.BackgroundColor = new BasicColor(ConsoleColor.Red);
|
||||||
|
_coloredRenderer.ForegroundColor = new BasicColor(ConsoleColor.White);
|
||||||
|
Console.SetCursorPosition(startX, startY + currentY++);
|
||||||
|
|
||||||
|
_coloredRenderer.Write($"{{0,-{elementWidth}}}", _paddingLeft + "<empty>" + _paddingRight);
|
||||||
|
}
|
||||||
|
|
||||||
|
var padding = new string(' ', elementWidth);
|
||||||
|
_coloredRenderer.ResetColor();
|
||||||
|
for (var i = 0; i < availableRows - printedItemsCount + 1; i++)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(startX, startY + currentY++);
|
||||||
|
_coloredRenderer.Write(padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanColumn(int startX, int startY, int elementWidth, int availableRows)
|
||||||
|
{
|
||||||
|
_coloredRenderer.ResetColor();
|
||||||
|
|
||||||
|
var currentY = 0;
|
||||||
|
var placeholder = new string(' ', elementWidth);
|
||||||
|
for (var i = 0; i < availableRows; i++)
|
||||||
|
{
|
||||||
|
Console.SetCursorPosition(startX, startY + currentY++);
|
||||||
|
_coloredRenderer.Write(placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckAndSetCurrentDisplayStartY()
|
||||||
|
{
|
||||||
|
const int padding = 5;
|
||||||
|
|
||||||
|
while (Pane.CurrentSelectedIndex < _currentDisplayStartY + padding
|
||||||
|
&& _currentDisplayStartY > 0)
|
||||||
|
{
|
||||||
|
_currentDisplayStartY--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (Pane.CurrentSelectedIndex > _currentDisplayStartY + _contentRowCount - padding
|
||||||
|
&& _currentDisplayStartY < Pane.CurrentLocation.Items.Count - _contentRowCount)
|
||||||
|
{
|
||||||
|
_currentDisplayStartY++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/FileTime.ConsoleUI.App/UI/Styles.cs
Normal file
39
src/FileTime.ConsoleUI.App/UI/Styles.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI.App.UI
|
||||||
|
{
|
||||||
|
public class Styles : IStyles
|
||||||
|
{
|
||||||
|
public IConsoleColor? DefaultBackground { get; }
|
||||||
|
public IConsoleColor? DefaultForeground { get; }
|
||||||
|
public IConsoleColor? ContainerBackground { get; }
|
||||||
|
public IConsoleColor? ContainerForeground { get; }
|
||||||
|
public IConsoleColor? ElementBackground { get; }
|
||||||
|
public IConsoleColor? ElementForeground { get; }
|
||||||
|
public IConsoleColor? ElementSpecialBackground { get; }
|
||||||
|
public IConsoleColor? ElementSpecialForeground { get; }
|
||||||
|
public IConsoleColor? SelectedItemBackground { get; }
|
||||||
|
public IConsoleColor? SelectedItemForeground { get; }
|
||||||
|
|
||||||
|
public Styles(bool useAnsiColors)
|
||||||
|
{
|
||||||
|
if (useAnsiColors)
|
||||||
|
{
|
||||||
|
ContainerForeground = AnsiColor.From8bit(4);
|
||||||
|
ElementForeground = AnsiColor.From8bit(14);
|
||||||
|
ElementSpecialForeground = AnsiColor.From8bit(2);
|
||||||
|
SelectedItemForeground = AnsiColor.From8bit(3);
|
||||||
|
|
||||||
|
DefaultForeground = ElementForeground;
|
||||||
|
SelectedItemBackground = ElementSpecialBackground = ContainerBackground = DefaultBackground = ElementBackground = AnsiColor.From8bit(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ContainerBackground = new BasicColor(Console.BackgroundColor);
|
||||||
|
ContainerForeground = new BasicColor(ConsoleColor.Blue);
|
||||||
|
ElementBackground = new BasicColor(Console.BackgroundColor);
|
||||||
|
ElementForeground = new BasicColor(Console.ForegroundColor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj
Normal file
17
src/FileTime.ConsoleUI/FileTime.ConsoleUI.csproj
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FileTime.Core\FileTime.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.ConsoleUI.App\FileTime.ConsoleUI.App.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
110
src/FileTime.ConsoleUI/Program.cs
Normal file
110
src/FileTime.ConsoleUI/Program.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using FileTime.App.Core.Clipboard;
|
||||||
|
using FileTime.ConsoleUI.App;
|
||||||
|
using FileTime.ConsoleUI.App.UI;
|
||||||
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
using FileTime.Core.StateManagement;
|
||||||
|
using FileTime.Providers.Local;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace FileTime.ConsoleUI
|
||||||
|
{
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
/* Console.Clear();
|
||||||
|
for (var x = 0; x < 16; x++)
|
||||||
|
{
|
||||||
|
for (var y = 0; y < 16; y++)
|
||||||
|
{
|
||||||
|
var i = x * 16 + y;
|
||||||
|
Console.Write("\u001b[48;5;{0}m{0,4}", i);
|
||||||
|
Console.ResetColor();
|
||||||
|
Console.Write(' ');
|
||||||
|
}
|
||||||
|
Console.WriteLine("\n");
|
||||||
|
}
|
||||||
|
return; */
|
||||||
|
|
||||||
|
/* var colors = new int[][]
|
||||||
|
{
|
||||||
|
new int[] {0,43,54},
|
||||||
|
new int[] {255,0,0},
|
||||||
|
new int[] {0,255,0},
|
||||||
|
new int[] {0,0,255},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var color in colors)
|
||||||
|
{
|
||||||
|
Console.Write($"\u001b[0m\u001b[48;2;{color[0]};{color[1]};{color[2]}mTESZT ");
|
||||||
|
Console.WriteLine($"\u001b[0m\u001b[38;2;{color[0]};{color[1]};{color[2]}mTESZT");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("\u001b[0m\u001b[48;5;0;38;5;14mASD");
|
||||||
|
return; */
|
||||||
|
|
||||||
|
var serviceProvider = CreateServiceProvider();
|
||||||
|
|
||||||
|
var coloredConsoleRenderer = serviceProvider.GetService<IColoredConsoleRenderer>()!;
|
||||||
|
var localContentProvider = serviceProvider.GetService<LocalContentProvider>()!;
|
||||||
|
|
||||||
|
var currentPossibleDirectory = localContentProvider.GetByPath(Environment.CurrentDirectory.Replace(Path.DirectorySeparatorChar, Constants.SeparatorChar));
|
||||||
|
|
||||||
|
if (currentPossibleDirectory is IContainer container)
|
||||||
|
{
|
||||||
|
coloredConsoleRenderer.Clear();
|
||||||
|
Console.CursorVisible = false;
|
||||||
|
|
||||||
|
var app = serviceProvider.GetService<Application>()!;
|
||||||
|
app.SetContainer(container);
|
||||||
|
app.PrintUI();
|
||||||
|
|
||||||
|
while (app.IsRunning)
|
||||||
|
{
|
||||||
|
if (app.ProcessKey(Console.ReadKey(true)))
|
||||||
|
{
|
||||||
|
app.PrintUI();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.SetCursorPosition(0, Console.WindowHeight - 1);
|
||||||
|
|
||||||
|
Console.CursorVisible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Current working directory is not a directory???");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ServiceProvider CreateServiceProvider()
|
||||||
|
{
|
||||||
|
return new ServiceCollection()
|
||||||
|
.AddSingleton<Application>()
|
||||||
|
.AddSingleton<IStyles>(new Styles(true))
|
||||||
|
.AddSingleton<IColoredConsoleRenderer, ColoredConsoleRenderer>()
|
||||||
|
.AddSingleton<IClipboard, Clipboard>()
|
||||||
|
.AddSingleton<LocalContentProvider>()
|
||||||
|
.AddSingleton<IContentProvider, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"))
|
||||||
|
.AddSingleton<ElementCreationStates>()
|
||||||
|
.AddSingleton<CommandExecutor>()
|
||||||
|
.AddSingleton<ConsoleReader>()
|
||||||
|
.AddTransient<Render>()
|
||||||
|
.RegisterCommandHandlers()
|
||||||
|
.BuildServiceProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IServiceCollection RegisterCommandHandlers(this IServiceCollection serviceCollection)
|
||||||
|
{
|
||||||
|
foreach (var commandHandler in Startup.GetCommandHandlers())
|
||||||
|
{
|
||||||
|
serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceCollection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
src/FileTime.Core/Command/CommandExecutor.cs
Normal file
24
src/FileTime.Core/Command/CommandExecutor.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public class CommandExecutor
|
||||||
|
{
|
||||||
|
private readonly List<ICommandHandler> _commandHandlers;
|
||||||
|
|
||||||
|
public CommandExecutor(IEnumerable<ICommandHandler> commandHandlers)
|
||||||
|
{
|
||||||
|
_commandHandlers = commandHandlers.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ExecuteCommand(ICommand command)
|
||||||
|
{
|
||||||
|
if (command is IExecutableCommand executableCommand)
|
||||||
|
{
|
||||||
|
executableCommand.Execute();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_commandHandlers.Find(c => c.CanHandle(command))?.Execute(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/FileTime.Core/Command/CopyCommand.cs
Normal file
62
src/FileTime.Core/Command/CopyCommand.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public class CopyCommand : ITransportationCommand
|
||||||
|
{
|
||||||
|
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||||
|
|
||||||
|
public IContainer? Target { get; set; }
|
||||||
|
|
||||||
|
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||||
|
|
||||||
|
public PointInTime SimulateCommand(PointInTime delta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(Action<IAbsolutePath, IAbsolutePath> copy)
|
||||||
|
{
|
||||||
|
DoCopy(Sources, Target, TransportMode, copy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoCopy(IEnumerable<IAbsolutePath> sources, IContainer target, TransportMode transportMode, Action<IAbsolutePath, IAbsolutePath> copy)
|
||||||
|
{
|
||||||
|
foreach (var source in sources)
|
||||||
|
{
|
||||||
|
var item = source.ContentProvider.GetByPath(source.Path);
|
||||||
|
|
||||||
|
if (item is IContainer container)
|
||||||
|
{
|
||||||
|
var targetContainer = target.Containers.FirstOrDefault(d => d.Name == container.Name) ?? (target.CreateContainer(container.Name)!);
|
||||||
|
|
||||||
|
var childDirectories = container.Containers.Select(d => new AbsolutePath(item.Provider, d.FullName!));
|
||||||
|
var childFiles = container.Elements.Select(f => new AbsolutePath(item.Provider, f.FullName!));
|
||||||
|
|
||||||
|
DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode, copy);
|
||||||
|
}
|
||||||
|
else if (item is IElement element)
|
||||||
|
{
|
||||||
|
var targetName = element.Name;
|
||||||
|
|
||||||
|
if (transportMode == TransportMode.Merge)
|
||||||
|
{
|
||||||
|
for (var i = 0; target.IsExists(targetName); i++)
|
||||||
|
{
|
||||||
|
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (transportMode == TransportMode.Skip && target.IsExists(targetName))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetPath = target.FullName + Constants.SeparatorChar + targetName;
|
||||||
|
|
||||||
|
copy(new AbsolutePath(source.ContentProvider, element.FullName!), new AbsolutePath(target.Provider, targetPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/FileTime.Core/Command/CreateContainerCommand.cs
Normal file
12
src/FileTime.Core/Command/CreateContainerCommand.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public class CreateContainerCommand : ICommand
|
||||||
|
{
|
||||||
|
public PointInTime SimulateCommand(PointInTime delta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/FileTime.Core/Command/CreateElementCommand.cs
Normal file
12
src/FileTime.Core/Command/CreateElementCommand.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public class CreateElementCommand : ICommand
|
||||||
|
{
|
||||||
|
public PointInTime SimulateCommand(PointInTime delta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/FileTime.Core/Command/DeleteCommand.cs
Normal file
41
src/FileTime.Core/Command/DeleteCommand.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public class DeleteCommand : IExecutableCommand
|
||||||
|
{
|
||||||
|
public IList<IAbsolutePath> ItemsToDelete { get; } = new List<IAbsolutePath>();
|
||||||
|
|
||||||
|
public PointInTime SimulateCommand(PointInTime delta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute()
|
||||||
|
{
|
||||||
|
foreach (var item in ItemsToDelete)
|
||||||
|
{
|
||||||
|
DoDelete(item.ContentProvider.GetByPath(item.Path)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DoDelete(IItem item)
|
||||||
|
{
|
||||||
|
if (item is IContainer container)
|
||||||
|
{
|
||||||
|
foreach (var child in container.Items)
|
||||||
|
{
|
||||||
|
DoDelete(child);
|
||||||
|
child.Delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
item.Delete();
|
||||||
|
}
|
||||||
|
else if(item is IElement element)
|
||||||
|
{
|
||||||
|
element.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/FileTime.Core/Command/ICommand.cs
Normal file
9
src/FileTime.Core/Command/ICommand.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public interface ICommand
|
||||||
|
{
|
||||||
|
PointInTime SimulateCommand(PointInTime moment);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/FileTime.Core/Command/ICommandHandler.cs
Normal file
8
src/FileTime.Core/Command/ICommandHandler.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public interface ICommandHandler
|
||||||
|
{
|
||||||
|
bool CanHandle(object command);
|
||||||
|
void Execute(object command);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/FileTime.Core/Command/IExecutableCommand.cs
Normal file
7
src/FileTime.Core/Command/IExecutableCommand.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public interface IExecutableCommand : ICommand
|
||||||
|
{
|
||||||
|
void Execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/FileTime.Core/Command/ITransportationCommand.cs
Normal file
11
src/FileTime.Core/Command/ITransportationCommand.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public interface ITransportationCommand : ICommand
|
||||||
|
{
|
||||||
|
IList<IAbsolutePath> Sources { get; }
|
||||||
|
IContainer Target { get; set;}
|
||||||
|
TransportMode TransportMode { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/FileTime.Core/Command/MoveCommand.cs
Normal file
18
src/FileTime.Core/Command/MoveCommand.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public class MoveCommand : ITransportationCommand
|
||||||
|
{
|
||||||
|
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||||
|
|
||||||
|
public IContainer? Target { get; set; }
|
||||||
|
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||||
|
|
||||||
|
public PointInTime SimulateCommand(PointInTime delta)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/FileTime.Core/Command/TransportMode.cs
Normal file
9
src/FileTime.Core/Command/TransportMode.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace FileTime.Core.Command
|
||||||
|
{
|
||||||
|
public enum TransportMode
|
||||||
|
{
|
||||||
|
Merge,
|
||||||
|
Overwrite,
|
||||||
|
Skip
|
||||||
|
}
|
||||||
|
}
|
||||||
168
src/FileTime.Core/Components/Pane.cs
Normal file
168
src/FileTime.Core/Components/Pane.cs
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Components
|
||||||
|
{
|
||||||
|
public class Pane
|
||||||
|
{
|
||||||
|
private IItem? currentSelectedItem;
|
||||||
|
private IContainer currentLocation;
|
||||||
|
|
||||||
|
public IContainer CurrentLocation
|
||||||
|
{
|
||||||
|
get => currentLocation;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (currentLocation != value)
|
||||||
|
{
|
||||||
|
if (currentLocation != null)
|
||||||
|
{
|
||||||
|
currentLocation.Refreshed -= HandleCurrentLocationRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLocation = value;
|
||||||
|
CurrentLocationChanged?.Invoke(this, EventArgs.Empty);
|
||||||
|
CurrentSelectedItem = CurrentLocation.Items.Count > 0 ? CurrentLocation.Items[0] : null;
|
||||||
|
currentLocation.Refreshed += HandleCurrentLocationRefresh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public IItem? CurrentSelectedItem
|
||||||
|
{
|
||||||
|
get => currentSelectedItem;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (currentSelectedItem != value && (currentLocation.Items.Contains(value) || value == null))
|
||||||
|
{
|
||||||
|
currentSelectedItem = value;
|
||||||
|
CurrentSelectedIndex = GetItemIndex(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int CurrentSelectedIndex { get; private set; }
|
||||||
|
|
||||||
|
public event EventHandler CurrentLocationChanged;
|
||||||
|
|
||||||
|
public Pane(IContainer currentPath)
|
||||||
|
{
|
||||||
|
CurrentLocation = currentPath;
|
||||||
|
CurrentSelectedItem = CurrentLocation.Items.Count > 0 ? CurrentLocation.Items[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCurrentLocationRefresh(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var currentSelectedName = CurrentSelectedItem?.FullName;
|
||||||
|
if (currentSelectedName != null)
|
||||||
|
{
|
||||||
|
CurrentSelectedItem = CurrentLocation.Items.FirstOrDefault(i => i.FullName == currentSelectedName) ?? currentLocation.Items.FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectFirstItem()
|
||||||
|
{
|
||||||
|
if (CurrentLocation.Items.Count > 0)
|
||||||
|
{
|
||||||
|
CurrentSelectedItem = CurrentLocation.Items[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectLastItem()
|
||||||
|
{
|
||||||
|
if (CurrentLocation.Items.Count > 0)
|
||||||
|
{
|
||||||
|
CurrentSelectedItem = CurrentLocation.Items[CurrentLocation.Items.Count - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectPreviousItem(int skip = 0)
|
||||||
|
{
|
||||||
|
var possibleItemsToSelect = CurrentLocation.Items.Take(CurrentSelectedIndex).Reverse().Skip(skip).ToList();
|
||||||
|
|
||||||
|
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = CurrentLocation.Items.ToList();
|
||||||
|
SelectItem(possibleItemsToSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectNextItem(int skip = 0)
|
||||||
|
{
|
||||||
|
var possibleItemsToSelect = CurrentLocation.Items.Skip(CurrentSelectedIndex + 1 + skip).ToList();
|
||||||
|
|
||||||
|
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = CurrentLocation.Items.Reverse().ToList();
|
||||||
|
SelectItem(possibleItemsToSelect);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectItem(IEnumerable<IItem> currentPossibleItems)
|
||||||
|
{
|
||||||
|
if (!currentPossibleItems.Any()) return;
|
||||||
|
|
||||||
|
if (CurrentSelectedItem != null)
|
||||||
|
{
|
||||||
|
CurrentLocation.Refresh();
|
||||||
|
|
||||||
|
IItem? newSelectedItem = null;
|
||||||
|
foreach (var item in currentPossibleItems)
|
||||||
|
{
|
||||||
|
if (CurrentLocation.Items.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem
|
||||||
|
&& possibleNewSelectedItem is not null)
|
||||||
|
{
|
||||||
|
newSelectedItem = possibleNewSelectedItem;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CurrentSelectedItem = newSelectedItem ?? (CurrentLocation.Items.Count > 0 ? CurrentLocation.Items[0] : null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentSelectedItem = CurrentLocation.Items.Count > 0 ? CurrentLocation.Items[0] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GoUp()
|
||||||
|
{
|
||||||
|
var lastCurrentLocation = CurrentLocation;
|
||||||
|
var parent = CurrentLocation.GetParent();
|
||||||
|
|
||||||
|
if (parent is not null)
|
||||||
|
{
|
||||||
|
if (lastCurrentLocation is VirtualContainer lastCurrentVirtualContainer)
|
||||||
|
{
|
||||||
|
CurrentLocation = lastCurrentVirtualContainer.CloneVirtualChainFor(parent, v => v.IsPermanent);
|
||||||
|
CurrentSelectedItem = lastCurrentVirtualContainer.GetRealContainer();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentLocation = parent;
|
||||||
|
CurrentSelectedItem = lastCurrentLocation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Open()
|
||||||
|
{
|
||||||
|
if (currentSelectedItem is IContainer childContainer)
|
||||||
|
{
|
||||||
|
if (CurrentLocation is VirtualContainer currentVirtuakContainer)
|
||||||
|
{
|
||||||
|
CurrentLocation = currentVirtuakContainer.CloneVirtualChainFor(childContainer, v => v.IsPermanent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentLocation = childContainer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenContainer(IContainer container) => CurrentLocation = container;
|
||||||
|
|
||||||
|
private int GetItemIndex(IItem? item)
|
||||||
|
{
|
||||||
|
if (item == null) return -1;
|
||||||
|
|
||||||
|
for (var i = 0; i < CurrentLocation.Items.Count; i++)
|
||||||
|
{
|
||||||
|
if (CurrentLocation.Items[i] == item) return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/FileTime.Core/Extensions/GeneralExtensions.cs
Normal file
7
src/FileTime.Core/Extensions/GeneralExtensions.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.Core.Extensions
|
||||||
|
{
|
||||||
|
public static class GeneralExtensions
|
||||||
|
{
|
||||||
|
public static TResult Map<T, TResult>(this T obj, Func<T, TResult> map) => map(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/FileTime.Core/FileTime.Core.csproj
Normal file
9
src/FileTime.Core/FileTime.Core.csproj
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
17
src/FileTime.Core/Models/AbsolutePath.cs
Normal file
17
src/FileTime.Core/Models/AbsolutePath.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Models
|
||||||
|
{
|
||||||
|
public class AbsolutePath : IAbsolutePath
|
||||||
|
{
|
||||||
|
public IContentProvider ContentProvider { get; }
|
||||||
|
|
||||||
|
public string Path { get; }
|
||||||
|
|
||||||
|
public AbsolutePath(IContentProvider contentProvider, string path)
|
||||||
|
{
|
||||||
|
ContentProvider = contentProvider;
|
||||||
|
Path = path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/FileTime.Core/Models/Constants.cs
Normal file
7
src/FileTime.Core/Models/Constants.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.Core.Models
|
||||||
|
{
|
||||||
|
public static class Constants
|
||||||
|
{
|
||||||
|
public const char SeparatorChar = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
10
src/FileTime.Core/Models/IAbsolutePath.cs
Normal file
10
src/FileTime.Core/Models/IAbsolutePath.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Models
|
||||||
|
{
|
||||||
|
public interface IAbsolutePath
|
||||||
|
{
|
||||||
|
IContentProvider ContentProvider { get; }
|
||||||
|
string Path { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/FileTime.Core/Models/IContainer.cs
Normal file
19
src/FileTime.Core/Models/IContainer.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace FileTime.Core.Models
|
||||||
|
{
|
||||||
|
public interface IContainer : IItem
|
||||||
|
{
|
||||||
|
IReadOnlyList<IItem> Items { get; }
|
||||||
|
IReadOnlyList<IContainer> Containers { get; }
|
||||||
|
IReadOnlyList<IElement> Elements { get; }
|
||||||
|
|
||||||
|
void Refresh();
|
||||||
|
IContainer? GetParent();
|
||||||
|
IItem? GetByPath(string path);
|
||||||
|
IContainer CreateContainer(string name);
|
||||||
|
IElement CreateElement(string name);
|
||||||
|
|
||||||
|
bool IsExists(string name);
|
||||||
|
|
||||||
|
event EventHandler? Refreshed;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/FileTime.Core/Models/IElement.cs
Normal file
8
src/FileTime.Core/Models/IElement.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace FileTime.Core.Models
|
||||||
|
{
|
||||||
|
public interface IElement : IItem
|
||||||
|
{
|
||||||
|
bool IsSpecial { get; }
|
||||||
|
string GetPrimaryAttributeText();
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/FileTime.Core/Models/IItem.cs
Normal file
13
src/FileTime.Core/Models/IItem.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Models
|
||||||
|
{
|
||||||
|
public interface IItem
|
||||||
|
{
|
||||||
|
string Name { get; }
|
||||||
|
string? FullName { get; }
|
||||||
|
bool IsHidden { get; }
|
||||||
|
IContentProvider Provider { get; }
|
||||||
|
void Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/FileTime.Core/Models/VirtualContainer.cs
Normal file
122
src/FileTime.Core/Models/VirtualContainer.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Models
|
||||||
|
{
|
||||||
|
public class VirtualContainer : IContainer
|
||||||
|
{
|
||||||
|
private readonly List<Func<IEnumerable<IContainer>, IEnumerable<IContainer>>> _containerTransformators;
|
||||||
|
private readonly List<Func<IEnumerable<IElement>, IEnumerable<IElement>>> _elementTransformators;
|
||||||
|
|
||||||
|
public IContainer BaseContainer { get; }
|
||||||
|
|
||||||
|
public bool IsPermanent { get; }
|
||||||
|
public bool IsTransitive { get; }
|
||||||
|
public string? VirtualContainerName { get; }
|
||||||
|
public IReadOnlyList<IItem> Items { get; private set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<IContainer> Containers { get; private set; }
|
||||||
|
|
||||||
|
public IReadOnlyList<IElement> Elements { get; private set; }
|
||||||
|
|
||||||
|
public string Name => BaseContainer.Name;
|
||||||
|
|
||||||
|
public string? FullName => BaseContainer.FullName;
|
||||||
|
|
||||||
|
public bool IsHidden => BaseContainer.IsHidden;
|
||||||
|
|
||||||
|
public IContentProvider Provider => BaseContainer.Provider;
|
||||||
|
|
||||||
|
public event EventHandler? Refreshed
|
||||||
|
{
|
||||||
|
add => BaseContainer.Refreshed += value;
|
||||||
|
remove => BaseContainer.Refreshed -= value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public VirtualContainer(
|
||||||
|
IContainer baseContainer,
|
||||||
|
List<Func<IEnumerable<IContainer>, IEnumerable<IContainer>>> containerTransformators,
|
||||||
|
List<Func<IEnumerable<IElement>, IEnumerable<IElement>>> elementTransformators,
|
||||||
|
bool isPermanent = false,
|
||||||
|
bool isTransitive = false,
|
||||||
|
string? virtualContainerName = null)
|
||||||
|
{
|
||||||
|
BaseContainer = baseContainer;
|
||||||
|
_containerTransformators = containerTransformators;
|
||||||
|
_elementTransformators = elementTransformators;
|
||||||
|
|
||||||
|
InitItems();
|
||||||
|
IsPermanent = isPermanent;
|
||||||
|
IsTransitive = isTransitive;
|
||||||
|
VirtualContainerName = virtualContainerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitItems()
|
||||||
|
{
|
||||||
|
Containers = _containerTransformators.Aggregate(BaseContainer.Containers.AsEnumerable(), (a, t) => t(a)).ToList().AsReadOnly();
|
||||||
|
Elements = _elementTransformators.Aggregate(BaseContainer.Elements.AsEnumerable(), (a, t) => t(a)).ToList().AsReadOnly();
|
||||||
|
|
||||||
|
Items = Containers.Cast<IItem>().Concat(Elements).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IItem? GetByPath(string path) => BaseContainer.GetByPath(path);
|
||||||
|
|
||||||
|
public IContainer? GetParent() => BaseContainer.GetParent();
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
BaseContainer.Refresh();
|
||||||
|
InitItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContainer GetRealContainer() =>
|
||||||
|
BaseContainer is VirtualContainer virtualContainer ? virtualContainer.GetRealContainer() : BaseContainer;
|
||||||
|
|
||||||
|
public bool HasWithName(string name) =>
|
||||||
|
VirtualContainerName == name
|
||||||
|
|| (BaseContainer is VirtualContainer virtualContainer
|
||||||
|
&& virtualContainer.HasWithName(name));
|
||||||
|
|
||||||
|
public IContainer ExceptWithName(string name)
|
||||||
|
{
|
||||||
|
if (BaseContainer is VirtualContainer virtualBaseContainer && virtualBaseContainer.VirtualContainerName == name)
|
||||||
|
{
|
||||||
|
return new VirtualContainer(
|
||||||
|
virtualBaseContainer.ExceptWithName(name),
|
||||||
|
_containerTransformators,
|
||||||
|
_elementTransformators,
|
||||||
|
IsPermanent,
|
||||||
|
IsTransitive,
|
||||||
|
VirtualContainerName);
|
||||||
|
}
|
||||||
|
else if (VirtualContainerName == name)
|
||||||
|
{
|
||||||
|
return BaseContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContainer CloneVirtualChainFor(IContainer container, Func<VirtualContainer, bool> predicate)
|
||||||
|
{
|
||||||
|
var baseContainer = BaseContainer is VirtualContainer baseVirtualContainer
|
||||||
|
? baseVirtualContainer.CloneVirtualChainFor(container, predicate)
|
||||||
|
: container;
|
||||||
|
|
||||||
|
return predicate(this)
|
||||||
|
? new VirtualContainer(
|
||||||
|
baseContainer,
|
||||||
|
_containerTransformators,
|
||||||
|
_elementTransformators,
|
||||||
|
IsPermanent,
|
||||||
|
IsTransitive,
|
||||||
|
VirtualContainerName)
|
||||||
|
: baseContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContainer CreateContainer(string name) => BaseContainer.CreateContainer(name);
|
||||||
|
public IElement CreateElement(string name) => BaseContainer.CreateElement(name);
|
||||||
|
public bool IsExists(string name) => BaseContainer.IsExists(name);
|
||||||
|
|
||||||
|
public void Delete() => BaseContainer.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
9
src/FileTime.Core/Providers/IContentProvider.cs
Normal file
9
src/FileTime.Core/Providers/IContentProvider.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Providers
|
||||||
|
{
|
||||||
|
public interface IContentProvider : IContainer
|
||||||
|
{
|
||||||
|
IReadOnlyList<IContainer> RootContainers { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.Core.StateManagement
|
||||||
|
{
|
||||||
|
public class ElementCreationStates
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/FileTime.Core/Timeline/ContainerSnapshot.cs
Normal file
7
src/FileTime.Core/Timeline/ContainerSnapshot.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.Core.Timeline
|
||||||
|
{
|
||||||
|
public class ContainerSnapshot
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/FileTime.Core/Timeline/ElementSnapshot.cs
Normal file
0
src/FileTime.Core/Timeline/ElementSnapshot.cs
Normal file
12
src/FileTime.Core/Timeline/PointInTime.cs
Normal file
12
src/FileTime.Core/Timeline/PointInTime.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Timeline
|
||||||
|
{
|
||||||
|
public class PointInTime
|
||||||
|
{
|
||||||
|
private readonly Dictionary<IContentProvider, RootSnapshot> snapshots = new();
|
||||||
|
|
||||||
|
public IReadOnlyDictionary<IContentProvider, RootSnapshot> Snapshots => new Lazy<IReadOnlyDictionary<IContentProvider, RootSnapshot>>(() => new ReadOnlyDictionary<IContentProvider, RootSnapshot>(snapshots)).Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/FileTime.Core/Timeline/RootSnapshot.cs
Normal file
7
src/FileTime.Core/Timeline/RootSnapshot.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.Core.Timeline
|
||||||
|
{
|
||||||
|
public class RootSnapshot
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
55
src/FileTime.sln
Normal file
55
src/FileTime.sln
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 16.0.30114.105
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Core", "FileTime.Core\FileTime.Core.csproj", "{F5C58BDC-BDCE-47B8-9371-70DB815E9B0E}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.ConsoleUI", "FileTime.ConsoleUI\FileTime.ConsoleUI.csproj", "{EC1F7FC4-5600-4953-A85A-534CA43601C3}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.ConsoleUI.App", "FileTime.ConsoleUI.App\FileTime.ConsoleUI.App.csproj", "{A88EB44A-EE33-4A91-8C61-B33B31C9DF07}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Providers", "Providers", "{517D96CE-A956-4638-A93D-465D34DE22B1}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.Providers.Local", "Providers\FileTime.Providers.Local\FileTime.Providers.Local.csproj", "{AAE01ED7-2E8B-40A2-AD0E-95BDA7C99272}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.Core", "FileTime.App.Core\FileTime.App.Core.csproj", "{2C0F630D-FD5D-4554-B8DD-F11BF4EB49C5}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{F5C58BDC-BDCE-47B8-9371-70DB815E9B0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{F5C58BDC-BDCE-47B8-9371-70DB815E9B0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{F5C58BDC-BDCE-47B8-9371-70DB815E9B0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{F5C58BDC-BDCE-47B8-9371-70DB815E9B0E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{EC1F7FC4-5600-4953-A85A-534CA43601C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EC1F7FC4-5600-4953-A85A-534CA43601C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EC1F7FC4-5600-4953-A85A-534CA43601C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EC1F7FC4-5600-4953-A85A-534CA43601C3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{D9FC4BC8-4E7D-491A-AA67-EABE52CF2999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{D9FC4BC8-4E7D-491A-AA67-EABE52CF2999}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{D9FC4BC8-4E7D-491A-AA67-EABE52CF2999}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{D9FC4BC8-4E7D-491A-AA67-EABE52CF2999}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{A88EB44A-EE33-4A91-8C61-B33B31C9DF07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{A88EB44A-EE33-4A91-8C61-B33B31C9DF07}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{A88EB44A-EE33-4A91-8C61-B33B31C9DF07}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{A88EB44A-EE33-4A91-8C61-B33B31C9DF07}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{AAE01ED7-2E8B-40A2-AD0E-95BDA7C99272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{AAE01ED7-2E8B-40A2-AD0E-95BDA7C99272}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{AAE01ED7-2E8B-40A2-AD0E-95BDA7C99272}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{AAE01ED7-2E8B-40A2-AD0E-95BDA7C99272}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{2C0F630D-FD5D-4554-B8DD-F11BF4EB49C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{2C0F630D-FD5D-4554-B8DD-F11BF4EB49C5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{2C0F630D-FD5D-4554-B8DD-F11BF4EB49C5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{2C0F630D-FD5D-4554-B8DD-F11BF4EB49C5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{AAE01ED7-2E8B-40A2-AD0E-95BDA7C99272} = {517D96CE-A956-4638-A93D-465D34DE22B1}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.StateManagement;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local.CommandHandlers
|
||||||
|
{
|
||||||
|
public class CopyCommandHandler : ICommandHandler
|
||||||
|
{
|
||||||
|
private readonly List<Thread> _copyOperations = new();
|
||||||
|
private readonly ElementCreationStates _elementCreationStates;
|
||||||
|
|
||||||
|
public CopyCommandHandler(ElementCreationStates elementCreationStates)
|
||||||
|
{
|
||||||
|
_elementCreationStates = elementCreationStates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHandle(object command)
|
||||||
|
{
|
||||||
|
if (command is not CopyCommand copyCommand) return false;
|
||||||
|
|
||||||
|
if (copyCommand.Target != null && copyCommand.Target is not LocalFolder) return false;
|
||||||
|
|
||||||
|
if (copyCommand.Sources.Any(s => s.ContentProvider is not LocalContentProvider)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(object command)
|
||||||
|
{
|
||||||
|
if (command is not CopyCommand copyCommand) throw new ArgumentException($"Can not execute command of type '{command.GetType()}'.");
|
||||||
|
|
||||||
|
var thread = new Thread(() => copyCommand.Execute(CopyElement));
|
||||||
|
thread.Start();
|
||||||
|
|
||||||
|
_copyOperations.Add(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CopyElement(IAbsolutePath sourcePath, IAbsolutePath targetPath)
|
||||||
|
{
|
||||||
|
using var sourceStream = File.OpenRead(sourcePath.Path);
|
||||||
|
using var sourceReader = new BinaryReader(sourceStream);
|
||||||
|
|
||||||
|
using var targetStream = File.OpenWrite(targetPath.Path);
|
||||||
|
using var targetWriter = new BinaryWriter(targetStream);
|
||||||
|
|
||||||
|
var bufferSize = 1024 * 1024;
|
||||||
|
byte[] dataRead;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
dataRead = sourceReader.ReadBytes(bufferSize);
|
||||||
|
targetWriter.Write(dataRead);
|
||||||
|
targetWriter.Flush();
|
||||||
|
}
|
||||||
|
while (dataRead.Length > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
namespace FileTime.Providers.Local.Extensions
|
||||||
|
{
|
||||||
|
public static class FormatExtensions
|
||||||
|
{
|
||||||
|
private const long OneKiloByte = 1024;
|
||||||
|
private const long OneMegaByte = OneKiloByte * 1024;
|
||||||
|
private const long OneGigaByte = OneMegaByte * 1024;
|
||||||
|
private const long OneTerraByte = OneGigaByte * 1024;
|
||||||
|
|
||||||
|
public static string ToSizeString(this long fileSize, int precision = 1)
|
||||||
|
{
|
||||||
|
var fileSizeD = (decimal)fileSize;
|
||||||
|
var (size, suffix) = fileSize switch
|
||||||
|
{
|
||||||
|
> OneTerraByte => (fileSizeD / OneTerraByte, "T"),
|
||||||
|
> OneGigaByte => (fileSizeD / OneGigaByte, "G"),
|
||||||
|
> OneMegaByte => (fileSizeD / OneMegaByte, "M"),
|
||||||
|
> OneKiloByte => (fileSizeD / OneKiloByte, "K"),
|
||||||
|
_ => (fileSizeD, "B")
|
||||||
|
};
|
||||||
|
|
||||||
|
return string.Format("{0:N" + precision + "}", size).TrimEnd('0').Replace(',', '.').TrimEnd('.') + " " + suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\FileTime.Core\FileTime.Core.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local
|
||||||
|
{
|
||||||
|
public class LocalContentProvider : IContentProvider
|
||||||
|
{
|
||||||
|
public IReadOnlyList<IContainer> RootContainers { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<IItem> Items => RootContainers;
|
||||||
|
|
||||||
|
public IReadOnlyList<IContainer> Containers => RootContainers;
|
||||||
|
|
||||||
|
public IReadOnlyList<IElement> Elements { get; } = new List<IElement>();
|
||||||
|
|
||||||
|
public string Name { get; } = "local";
|
||||||
|
|
||||||
|
public string? FullName { get; }
|
||||||
|
public bool IsHidden => false;
|
||||||
|
|
||||||
|
public IContentProvider Provider => this;
|
||||||
|
|
||||||
|
public event EventHandler? Refreshed;
|
||||||
|
|
||||||
|
public LocalContentProvider()
|
||||||
|
{
|
||||||
|
var rootDirectories = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||||
|
? new DirectoryInfo("/").GetDirectories()
|
||||||
|
: Environment.GetLogicalDrives().Select(d => new DirectoryInfo(d));
|
||||||
|
|
||||||
|
FullName = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "" : null;
|
||||||
|
|
||||||
|
RootContainers = rootDirectories.Select(d => new LocalFolder(d, this, this)).OrderBy(d => d.Name).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IItem? GetByPath(string path)
|
||||||
|
{
|
||||||
|
var pathParts = path.TrimStart(Constants.SeparatorChar).Split(Constants.SeparatorChar);
|
||||||
|
var rootContainer = RootContainers.FirstOrDefault(c => c.Name == pathParts[0]);
|
||||||
|
|
||||||
|
if (rootContainer == null) return null;
|
||||||
|
|
||||||
|
return rootContainer.GetByPath(string.Join(Constants.SeparatorChar, pathParts.Skip(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContainer? GetParent()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public IContainer CreateContainer(string name) => throw new NotSupportedException();
|
||||||
|
public IElement CreateElement(string name) => throw new NotSupportedException();
|
||||||
|
public bool IsExists(string name) => Items.Any(i => i.Name == name);
|
||||||
|
|
||||||
|
public void Delete() => throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/Providers/FileTime.Providers.Local/LocalFile.cs
Normal file
40
src/Providers/FileTime.Providers.Local/LocalFile.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
using FileTime.Providers.Local.Extensions;
|
||||||
|
using Mono.Unix;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local
|
||||||
|
{
|
||||||
|
public class LocalFile : IElement
|
||||||
|
{
|
||||||
|
private readonly FileInfo _file;
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string FullName { get; }
|
||||||
|
|
||||||
|
public IContentProvider Provider { get; }
|
||||||
|
|
||||||
|
public bool IsHidden => (_file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||||
|
public bool IsSpecial =>
|
||||||
|
RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||||
|
&& (new UnixFileInfo(_file.FullName).FileAccessPermissions & FileAccessPermissions.UserExecute) == FileAccessPermissions.UserExecute;
|
||||||
|
|
||||||
|
public LocalFile(FileInfo file, IContentProvider contentProvider)
|
||||||
|
{
|
||||||
|
_file = file;
|
||||||
|
|
||||||
|
Name = file.Name;
|
||||||
|
FullName = file.FullName;
|
||||||
|
Provider = contentProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPrimaryAttributeText() => _file.Length.ToSizeString();
|
||||||
|
|
||||||
|
public void Delete()
|
||||||
|
{
|
||||||
|
_file.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/Providers/FileTime.Providers.Local/LocalFolder.cs
Normal file
125
src/Providers/FileTime.Providers.Local/LocalFolder.cs
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local
|
||||||
|
{
|
||||||
|
public class LocalFolder : IContainer
|
||||||
|
{
|
||||||
|
private IReadOnlyList<IItem>? _items;
|
||||||
|
private IReadOnlyList<IContainer>? _containers;
|
||||||
|
private IReadOnlyList<IElement>? _elements;
|
||||||
|
private readonly IContainer? _parent;
|
||||||
|
|
||||||
|
public bool IsHidden => (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||||
|
public DirectoryInfo Directory { get; }
|
||||||
|
public IContentProvider Provider { get; }
|
||||||
|
|
||||||
|
public IReadOnlyList<IItem> Items
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_items == null) Refresh();
|
||||||
|
return _items!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private set => _items = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<IContainer> Containers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_containers == null) Refresh();
|
||||||
|
return _containers!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private set => _containers = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<IElement> Elements
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_elements == null) Refresh();
|
||||||
|
return _elements!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private set => _elements = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public string FullName { get; }
|
||||||
|
|
||||||
|
public event EventHandler? Refreshed;
|
||||||
|
|
||||||
|
public LocalFolder(DirectoryInfo directory, IContentProvider contentProvider, IContainer? parent)
|
||||||
|
{
|
||||||
|
Directory = directory;
|
||||||
|
_parent = parent;
|
||||||
|
|
||||||
|
Name = directory.Name;
|
||||||
|
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||||
|
Provider = contentProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IContainer? GetParent() => _parent;
|
||||||
|
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
_containers = new List<IContainer>();
|
||||||
|
_elements = new List<IElement>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_containers = Directory.GetDirectories().Select(d => new LocalFolder(d, Provider, this)).OrderBy(d => d.Name).ToList().AsReadOnly();
|
||||||
|
_elements = Directory.GetFiles().Select(f => new LocalFile(f, Provider)).OrderBy(f => f.Name).ToList().AsReadOnly();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
|
||||||
|
Refreshed?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IItem? GetByPath(string path)
|
||||||
|
{
|
||||||
|
var paths = path.Split(Constants.SeparatorChar);
|
||||||
|
|
||||||
|
var item = Items.FirstOrDefault(i => i.Name == paths[0]);
|
||||||
|
|
||||||
|
if (paths.Length == 1)
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item is IContainer container)
|
||||||
|
{
|
||||||
|
return container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public IContainer CreateContainer(string name)
|
||||||
|
{
|
||||||
|
Directory.CreateSubdirectory(name);
|
||||||
|
Refresh();
|
||||||
|
|
||||||
|
return _containers!.FirstOrDefault(c => c.Name == name)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IElement CreateElement(string name)
|
||||||
|
{
|
||||||
|
using (File.Create(Path.Combine(Directory.FullName, name))) { }
|
||||||
|
Refresh();
|
||||||
|
|
||||||
|
return _elements!.FirstOrDefault(e => e.Name == name)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsExists(string name) => Items.Any(i => i.Name == name);
|
||||||
|
|
||||||
|
public void Delete()
|
||||||
|
{
|
||||||
|
Directory.Delete(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/Providers/FileTime.Providers.Local/Startup.cs
Normal file
14
src/Providers/FileTime.Providers.Local/Startup.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using FileTime.Providers.Local.CommandHandlers;
|
||||||
|
|
||||||
|
namespace FileTime.Providers.Local
|
||||||
|
{
|
||||||
|
public static class Startup
|
||||||
|
{
|
||||||
|
public static Type[] GetCommandHandlers()
|
||||||
|
{
|
||||||
|
return new Type[]{
|
||||||
|
typeof(CopyCommandHandler)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user