NativePath, Editor, ToastMessageSink

This commit is contained in:
2022-02-15 19:47:38 +01:00
parent b11d009195
commit 45defb4021
28 changed files with 265 additions and 54 deletions

View File

@@ -12,6 +12,7 @@ namespace FileTime.App.Core.Command
CreateContainer,
CreateElement,
Cut,
Edit,
EnterRapidTravel,
GoToHome,
GoToPath,

View File

@@ -6,6 +6,7 @@ namespace FileTime.Core.Models
{
string Name { get; }
string? FullName { get; }
string? NativePath { get; }
bool IsHidden { get; }
bool IsDestroyed { get; }
SupportsDelete CanDelete { get; }

View File

@@ -22,6 +22,7 @@ namespace FileTime.Core.Models
public string Name => BaseContainer.Name;
public string? FullName => BaseContainer.FullName;
public string? NativePath => BaseContainer.NativePath;
public bool IsHidden => BaseContainer.IsHidden;
public bool IsLoaded => BaseContainer.IsLoaded;

View File

@@ -17,6 +17,7 @@ namespace FileTime.Core.Providers
#pragma warning restore CS8603 // Possible null reference return.
public string? FullName => null;
public string? NativePath => null;
public bool IsHidden => false;
public bool IsLoaded => true;

View File

@@ -31,6 +31,9 @@ namespace FileTime.Core.Timeline
public bool IsDestroyed { get; private set; }
//FIXME: currently this can be different of the real items NativePath, should be fixed
public string? NativePath => FullName;
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
{
_parent = parent;

View File

@@ -22,6 +22,9 @@ namespace FileTime.Core.Timeline
public string? FullName { get; }
//FIXME: currently this can be different of the real items NativePath, should be fixed
public string? NativePath => FullName;
public bool IsHidden => false;
public SupportsDelete CanDelete => SupportsDelete.True;

View File

@@ -15,6 +15,7 @@ namespace FileTime.Core.Timeline
public string Name => "time";
public string? FullName => null;
public string? NativePath => null;
public bool IsHidden => false;

View File

@@ -18,12 +18,12 @@ namespace FileTime.Avalonia
ServiceProvider ??= DependencyInjection
.RegisterDefaultServices()
.AddConfiguration()
.InitSerilog()
.AddServices()
.RegisterLogging()
.AddViewModels()
.AddServices()
.RegisterCommandHandlers()
.BuildServiceProvider();
.BuildServiceProvider()
.InitSerilog();
var _logger = ServiceProvider.GetService<ILogger<App>>();
_logger?.LogInformation("App initialization completed.");

View File

@@ -8,32 +8,32 @@ namespace FileTime.Avalonia.Configuration
public static class MainConfiguration
{
private static readonly Lazy<List<CommandBindingConfiguration>> _defaultKeybindings = new(InitDefaultKeyBindings);
internal const string KeybindingBaseConfigKey = "KeyBindings";
public static Dictionary<string, string> Configuration { get; }
static MainConfiguration()
{
Configuration = new();
PopulateDefaultKeyBindings(Configuration, _defaultKeybindings.Value, KeybindingBaseConfigKey + ":" + nameof(KeyBindingConfiguration.DefaultKeyBindings));
PopulateDefaultEditorPrograms(Configuration);
PopulateDefaultKeyBindings(Configuration, _defaultKeybindings.Value, SectionNames.KeybindingSectionName + ":" + nameof(KeyBindingConfiguration.DefaultKeyBindings));
}
private static void PopulateDefaultKeyBindings(Dictionary<string, string> keybindings, List<CommandBindingConfiguration> commandBindingConfigs, string basePath)
private static void PopulateDefaultKeyBindings(Dictionary<string, string> configuration, List<CommandBindingConfiguration> commandBindingConfigs, string basePath)
{
for (var i = 0; i < commandBindingConfigs.Count; i++)
{
var baseKey = basePath + $":[{i}]:";
var commandBindingConfig = commandBindingConfigs[i];
keybindings.Add(baseKey + nameof(CommandBindingConfiguration.Command), commandBindingConfig.Command.ToString());
configuration.Add(baseKey + nameof(CommandBindingConfiguration.Command), commandBindingConfig.Command.ToString());
for (var j = 0; j < commandBindingConfig.Keys.Count; j++)
{
var key = commandBindingConfig.Keys[j];
var keyBaseKey = baseKey + $"keys:[{j}]:";
keybindings.Add(keyBaseKey + nameof(KeyConfig.Key), key.Key.ToString());
keybindings.Add(keyBaseKey + nameof(KeyConfig.Shift), key.Shift.ToString());
keybindings.Add(keyBaseKey + nameof(KeyConfig.Alt), key.Alt.ToString());
keybindings.Add(keyBaseKey + nameof(KeyConfig.Ctrl), key.Ctrl.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Key), key.Key.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Shift), key.Shift.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Alt), key.Alt.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Ctrl), key.Ctrl.ToString());
}
}
}
@@ -51,6 +51,7 @@ namespace FileTime.Avalonia.Configuration
new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }),
new CommandBindingConfiguration(Commands.CreateElement, new[] { Key.C, Key.E }),
new CommandBindingConfiguration(Commands.Cut, new[] { Key.D, Key.D }),
new CommandBindingConfiguration(Commands.Edit, new KeyConfig(Key.F4)),
new CommandBindingConfiguration(Commands.EnterRapidTravel, new KeyConfig(Key.OemComma, shift: true)),
new CommandBindingConfiguration(Commands.GoToHome, new[] { Key.G, Key.H }),
new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.OemComma, ctrl: true)),
@@ -97,5 +98,25 @@ namespace FileTime.Avalonia.Configuration
new CommandBindingConfiguration(Commands.MoveCursorDownPage, Key.PageDown),
};
}
private static void PopulateDefaultEditorPrograms(Dictionary<string, string> configuration)
{
var editorPrograms = new List<ProgramConfiguration>()
{
new ProgramConfiguration(@"c:\Program Files\Notepad++\notepad++1.exe"),
new ProgramConfiguration("notepad.exe"),
};
for (var i = 0; i < editorPrograms.Count; i++)
{
if (editorPrograms[i].Path is not string path) continue;
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Path)}", path);
if (editorPrograms[i].Arguments is string arguments)
{
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Arguments)}", arguments);
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
namespace FileTime.Avalonia.Configuration
{
public class ProgramConfiguration
{
public string? Path { get; set; }
public string? Arguments { get; set; }
public ProgramConfiguration() { }
public ProgramConfiguration(string? path, string? arguments = null)
{
Path = path;
Arguments = arguments;
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace FileTime.Avalonia.Configuration
{
public class ProgramsConfiguration
{
public List<ProgramConfiguration> DefaultEditorPrograms { get; set; } = new();
public List<ProgramConfiguration> EditorPrograms { get; set; } = new();
}
}

View File

@@ -0,0 +1,8 @@
namespace FileTime.Avalonia.Configuration
{
public static class SectionNames
{
internal const string KeybindingSectionName = "KeyBindings";
internal const string ProgramsSectionName = "Programs";
}
}

View File

@@ -0,0 +1,25 @@
using FileTime.Avalonia.Services;
using Serilog.Core;
using Serilog.Events;
namespace FileTime.Avalonia.Logging
{
public class ToastMessageSink : ILogEventSink
{
private readonly IDialogService dialogService;
public ToastMessageSink(IDialogService dialogService)
{
this.dialogService = dialogService;
}
public void Emit(LogEvent logEvent)
{
if (logEvent.Level >= LogEventLevel.Error)
{
var message = logEvent.RenderMessage();
dialogService.ShowToastMessage(message);
}
}
}
}

View File

@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Threading;
using FileTime.App.Core.Clipboard;
using FileTime.App.Core.Command;
using FileTime.Avalonia.Application;
@@ -17,6 +16,7 @@ using FileTime.Core.Models;
using FileTime.Core.Providers;
using FileTime.Core.Timeline;
using FileTime.Providers.Local;
using Microsoft.Extensions.Logging;
namespace FileTime.Avalonia.Services
{
@@ -27,22 +27,26 @@ namespace FileTime.Avalonia.Services
private readonly AppState _appState;
private readonly LocalContentProvider _localContentProvider;
private readonly ItemNameConverterService _itemNameConverterService;
private readonly DialogService _dialogService;
private readonly IDialogService _dialogService;
private readonly IClipboard _clipboard;
private readonly TimeRunner _timeRunner;
private readonly IIconProvider _iconProvider;
private readonly IEnumerable<IContentProvider> _contentProviders;
private readonly Dictionary<Commands, Func<Task>> _commandHandlers;
private readonly ProgramsService _programsService;
private readonly ILogger<CommandHandlerService> _logger;
public CommandHandlerService(
AppState appState,
LocalContentProvider localContentProvider,
ItemNameConverterService itemNameConverterService,
DialogService dialogService,
IDialogService dialogService,
IClipboard clipboard,
TimeRunner timeRunner,
IIconProvider iconProvider,
IEnumerable<IContentProvider> contentProviders)
IEnumerable<IContentProvider> contentProviders,
ProgramsService programsService,
ILogger<CommandHandlerService> logger)
{
_appState = appState;
_localContentProvider = localContentProvider;
@@ -52,6 +56,8 @@ namespace FileTime.Avalonia.Services
_timeRunner = timeRunner;
_iconProvider = iconProvider;
_contentProviders = contentProviders;
_programsService = programsService;
_logger = logger;
_commandHandlers = new Dictionary<Commands, Func<Task>>
{
@@ -541,7 +547,7 @@ namespace FileTime.Avalonia.Services
{
if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder)
{
var path = localFolder.Directory.FullName;
var path = localFolder.NativePath;
if (path != null)
{
Process.Start("explorer.exe", "\"" + path + "\"");
@@ -553,23 +559,12 @@ namespace FileTime.Avalonia.Services
private async Task CopyPath()
{
string? textToCopy = null;
if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder)
{
textToCopy = localFolder.Directory.FullName;
}
if (_appState.SelectedTab.CurrentLocation.Container is LocalFile localFile)
{
textToCopy = localFile.File.FullName;
}
else if (_appState.SelectedTab.CurrentLocation.Container.FullName is string fullName)
{
textToCopy = fullName;
}
var currentContainer = _appState.SelectedTab.CurrentLocation.Container;
var textToCopy = currentContainer.NativePath;
if (textToCopy != null && global::Avalonia.Application.Current?.Clipboard is not null)
if (textToCopy != null && global::Avalonia.Application.Current?.Clipboard is global::Avalonia.Input.Platform.IClipboard clipboard)
{
await global::Avalonia.Application.Current.Clipboard.SetTextAsync(textToCopy);
await clipboard.SetTextAsync(textToCopy);
}
}
@@ -783,6 +778,46 @@ namespace FileTime.Avalonia.Services
private Task Edit()
{
if (_appState.SelectedTab.SelectedItem?.Item is IElement element && element.NativePath is string filePath)
{
var getNext = false;
while (true)
{
try
{
var editorProgram = _programsService.GetEditorProgram(getNext);
if (editorProgram is null)
{
break;
}
else if (editorProgram.Path is string executablePath)
{
if (string.IsNullOrWhiteSpace(editorProgram.Arguments))
{
Process.Start(executablePath, "\"" + filePath + "\"");
}
else
{
var parts = editorProgram.Arguments.Split("%%1");
var arguments = string.Join("%%1", parts.Select(p => p.Replace("%1", "\"" + filePath + "\""))).Replace("%%1", "%1");
Process.Start(executablePath, arguments);
}
}
//TODO: else
break;
}
catch (System.ComponentModel.Win32Exception e)
{
_logger.LogError(e, "Error while running editor program, possible the executable path does not exists.");
}
catch (Exception e)
{
_logger.LogError(e, "Unkown error while running editor program.");
}
getNext = true;
}
}
//TODO: else
return Task.CompletedTask;
}
}

View File

@@ -9,7 +9,7 @@ using FileTime.Core.Interactions;
namespace FileTime.Avalonia.Services
{
public class DialogService
public class DialogService : IDialogService
{
private readonly AppState _appState;
@@ -93,10 +93,9 @@ namespace FileTime.Avalonia.Services
public void ShowToastMessage(string text)
{
_appState.PopupTexts.Add(text);
Task.Run(async () =>
{
await Dispatcher.UIThread.InvokeAsync(() => _appState.PopupTexts.Add(text));
await Task.Delay(5000);
await Dispatcher.UIThread.InvokeAsync(() => _appState.PopupTexts.Remove(text));
});

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using FileTime.Avalonia.Misc;
using FileTime.Core.Interactions;
namespace FileTime.Avalonia.Services
{
public interface IDialogService
{
void CancelInputs();
void CancelMessageBox();
void ClearInputs();
Task ProcessInputs();
void ProcessMessageBox();
void ReadInputs(List<InputElement> inputs, Action<List<InputElementWrapper>> inputHandler);
void ReadInputs(List<InputElement> inputs, Func<List<InputElementWrapper>, Task> inputHandler);
Task<string?[]> ReadInputs(IEnumerable<InputElement> fields);
void ShowMessageBox(string text, Func<Task> inputHandler);
void ShowToastMessage(string text);
}
}

View File

@@ -17,13 +17,13 @@ namespace FileTime.Avalonia.Services
private readonly AppState _appState;
private readonly KeyboardConfigurationService _keyboardConfigurationService;
private readonly CommandHandlerService _commandHandlerService;
private readonly DialogService _dialogService;
private readonly IDialogService _dialogService;
public KeyInputHandlerService(
AppState appState,
KeyboardConfigurationService keyboardConfigurationService,
CommandHandlerService commandHandlerService,
DialogService dialogService)
IDialogService dialogService)
{
_appState = appState;
_keyboardConfigurationService = keyboardConfigurationService;

View File

@@ -0,0 +1,42 @@
using System.Collections.Generic;
using FileTime.Avalonia.Configuration;
using Microsoft.Extensions.Options;
namespace FileTime.Avalonia.Services
{
public class ProgramsService
{
private readonly List<ProgramConfiguration> _editorPrograms;
private int lastGoodEditorProgramIndex;
public ProgramsService(IOptions<ProgramsConfiguration> configuration)
{
var config = configuration.Value;
_editorPrograms = config.EditorPrograms.Count == 0 ? config.DefaultEditorPrograms : config.EditorPrograms;
}
public ProgramConfiguration? GetEditorProgram(bool getNext = false)
{
if (getNext)
{
lastGoodEditorProgramIndex++;
}
if (lastGoodEditorProgramIndex < 0)
{
lastGoodEditorProgramIndex = 0;
}
if (_editorPrograms.Count <= lastGoodEditorProgramIndex)
{
ResetLastGoodEditor();
return null;
}
return _editorPrograms[lastGoodEditorProgramIndex];
}
public void ResetLastGoodEditor()
{
lastGoodEditorProgramIndex = -1;
}
}
}

View File

@@ -1,8 +1,10 @@
using System.IO;
using System;
using System.IO;
using System.Runtime.InteropServices;
using FileTime.Avalonia.Application;
using FileTime.Avalonia.Configuration;
using FileTime.Avalonia.IconProviders;
using FileTime.Avalonia.Logging;
using FileTime.Avalonia.Services;
using FileTime.Avalonia.ViewModels;
using FileTime.Core.Command;
@@ -12,6 +14,7 @@ using FileTime.Providers.Smb;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using Serilog.Configuration;
namespace FileTime.Avalonia
{
@@ -20,20 +23,22 @@ namespace FileTime.Avalonia
internal static IServiceCollection AddViewModels(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<AppState>()
.AddTransient<MainPageViewModel>()
.AddSingleton<IInputInterface, BasicInputHandler>();
}
internal static IServiceCollection AddServices(this IServiceCollection serviceCollection)
{
serviceCollection = serviceCollection
.AddSingleton<AppState>()
.AddSingleton<ItemNameConverterService>()
.AddSingleton<StatePersistenceService>()
.AddSingleton<CommandHandlerService>()
.AddSingleton<KeyboardConfigurationService>()
.AddSingleton<KeyInputHandlerService>()
.AddSingleton<DialogService>()
.AddSingleton<IDialogService, DialogService>()
.AddSingleton(new PersistenceSettings(Program.AppDataRoot))
.AddSingleton<ProgramsService>()
.AddSingleton<ToastMessageSink>()
.AddSmbServices()
.AddSingleton<IIconProvider, MaterialIconProvider>();
@@ -74,13 +79,13 @@ namespace FileTime.Avalonia
.Build();
return serviceCollection
.Configure<KeyBindingConfiguration>(configuration.GetSection(MainConfiguration.KeybindingBaseConfigKey))
.Configure<ProgramsConfiguration>(configuration.GetSection(SectionNames.ProgramsSectionName))
.Configure<KeyBindingConfiguration>(configuration.GetSection(SectionNames.KeybindingSectionName))
.AddSingleton<IConfiguration>(configuration);
}
internal static IServiceCollection InitSerilog(this IServiceCollection serviceCollection)
internal static IServiceProvider InitSerilog(this IServiceProvider serviceProvider)
{
using var serviceProvider = serviceCollection.BuildServiceProvider();
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(serviceProvider.GetService<IConfiguration>())
.Enrich.FromLogContext()
@@ -89,9 +94,17 @@ namespace FileTime.Avalonia
fileSizeLimitBytes: 10 * 1024 * 1024,
rollOnFileSizeLimit: true,
rollingInterval: RollingInterval.Day)
.WriteTo.MessageBoxSink(serviceProvider)
.CreateLogger();
return serviceCollection;
return serviceProvider;
}
public static LoggerConfiguration MessageBoxSink(
this LoggerSinkConfiguration loggerConfiguration,
IServiceProvider serviceProvider)
{
return loggerConfiguration.Sink(serviceProvider.GetService<ToastMessageSink>());
}
}
}

View File

@@ -33,7 +33,7 @@ namespace FileTime.Avalonia.ViewModels
[Inject(typeof(ILogger<MainPageViewModel>), PropertyName = "_logger")]
[Inject(typeof(KeyboardConfigurationService))]
[Inject(typeof(CommandHandlerService), PropertyAccessModifier = AccessModifier.Public)]
[Inject(typeof(DialogService))]
[Inject(typeof(IDialogService), PropertyName = "_dialogService")]
[Inject(typeof(KeyInputHandlerService))]
public partial class MainPageViewModel : IMainPageViewModelBase
{
@@ -73,7 +73,7 @@ namespace FileTime.Avalonia.ViewModels
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
var inputInterface = (BasicInputHandler)App.ServiceProvider.GetService<IInputInterface>()!;
inputInterface.InputHandler = DialogService.ReadInputs;
inputInterface.InputHandler = _dialogService.ReadInputs;
App.ServiceProvider.GetService<TopContainer>();
await StatePersistence.LoadStatesAsync();
@@ -246,25 +246,25 @@ namespace FileTime.Avalonia.ViewModels
[Command]
public async void ProcessInputs()
{
await DialogService.ProcessInputs();
await _dialogService.ProcessInputs();
}
[Command]
public void CancelInputs()
{
DialogService.CancelInputs();
_dialogService.CancelInputs();
}
[Command]
public void ProcessMessageBox()
{
DialogService.ProcessMessageBox();
_dialogService.ProcessMessageBox();
}
[Command]
public void CancelMessageBox()
{
DialogService.CancelMessageBox();
_dialogService.CancelMessageBox();
}
public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)

View File

@@ -19,6 +19,7 @@ namespace FileTime.Providers.Local
public string Name { get; } = "local";
public string? FullName { get; }
public string? NativePath => null;
public bool IsHidden => false;
public bool IsLoaded => true;

View File

@@ -14,6 +14,7 @@ namespace FileTime.Providers.Local
public string Name { get; }
public string FullName { get; }
public string? NativePath => File.FullName;
public IContentProvider Provider { get; }

View File

@@ -22,6 +22,7 @@ namespace FileTime.Providers.Local
public string Name { get; }
public string FullName { get; }
public string? NativePath => Directory.FullName;
public bool IsLoaded => _items != null;
public SupportsDelete CanDelete { get; }

View File

@@ -24,6 +24,7 @@ namespace FileTime.Providers.Smb
public string Name { get; } = "smb";
public string? FullName { get; }
public string? NativePath => null;
public bool IsHidden => false;
public bool IsLoaded => true;
@@ -177,5 +178,7 @@ namespace FileTime.Providers.Smb
}
}
}
public static string GetNativePath(string fullName) => fullName.Replace("/", "\\");
}
}

View File

@@ -1,6 +1,5 @@
using FileTime.Core.Models;
using FileTime.Core.Providers;
using SMBLibrary.Client;
namespace FileTime.Providers.Smb
{
@@ -11,6 +10,7 @@ namespace FileTime.Providers.Smb
public string Name { get; }
public string? FullName { get; }
public string? NativePath { get; }
public bool IsHidden => false;
public SupportsDelete CanDelete => SupportsDelete.True;
@@ -25,6 +25,7 @@ namespace FileTime.Providers.Smb
{
Name = name;
FullName = parent.FullName + Constants.SeparatorChar + Name;
NativePath = SmbContentProvider.GetNativePath(FullName);
Provider = provider;
_parent = parent;

View File

@@ -1,8 +1,6 @@
using AsyncEvent;
using FileTime.Core.Models;
using FileTime.Core.Providers;
using SMBLibrary;
using SMBLibrary.Client;
namespace FileTime.Providers.Smb
{
@@ -17,6 +15,7 @@ namespace FileTime.Providers.Smb
public string Name { get; }
public string? FullName { get; }
public string? NativePath { get; }
public bool IsHidden => false;
public bool IsLoaded => _items != null;
@@ -40,6 +39,7 @@ namespace FileTime.Providers.Smb
Name = name;
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
NativePath = SmbContentProvider.GetNativePath(FullName);
Provider = contentProvider;
}

View File

@@ -24,8 +24,8 @@ namespace FileTime.Providers.Smb
public string? Password { get; private set; }
public string Name { get; }
public string? FullName { get; }
public string? NativePath { get; }
public bool IsHidden => false;
public bool IsLoaded => _items != null;
@@ -51,7 +51,7 @@ namespace FileTime.Providers.Smb
Password = password;
Provider = contentProvider;
FullName = Name = path;
NativePath = FullName = Name = path;
}
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)

View File

@@ -17,6 +17,7 @@ namespace FileTime.Providers.Smb
public string Name { get; }
public string? FullName { get; }
public string? NativePath { get; }
public bool IsHidden => false;
public bool IsLoaded => _items != null;
@@ -40,6 +41,7 @@ namespace FileTime.Providers.Smb
Name = name;
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
NativePath = SmbContentProvider.GetNativePath(FullName);
Provider = contentProvider;
}