adding ability for IFileSystem to tell me when something changes on disk.

first pass at implementing the windows version of the new interface methods
IViewPort now has a public method to call whenever you want to notify it that its underlying file has been changed.
Added 3 tests showing how the Editor and ViewPort should behave with this new functionality.
- Editor should hook up ViewPorts whenever a new tab is opened
- Editor should clear out ViewPorts whenever a tab is closed
- ViewPort should reload when the file is changed and it is not.

Note that the model isn't implemented yet. This is just some failing tests.
This commit is contained in:
Benjamin Popp
2018-11-28 22:16:43 -06:00
parent c8542fb32c
commit 0b60953ecc
7 changed files with 109 additions and 6 deletions

View File

@@ -1,4 +1,6 @@
namespace HavenSoft.Gen3Hex.Model {
using System;
namespace HavenSoft.Gen3Hex.Model {
public interface IFileSystem {
string CopyText { get; set; }
@@ -34,6 +36,22 @@
/// </returns>
LoadedFile OpenFile(params string[] extensionOptions);
/// <summary>
/// Have the filesystem open a specific file.
/// </summary>
/// <returns>
/// If the file exists, it is loaded and returned.
/// If it doesn't exist, returns null.
/// </returns>
LoadedFile LoadFile(string fileName);
/// <summary>
/// When a file changes, the filesystem will call all listeners for that file.
/// </summary>
void AddListenerToFile(string fileName, Action<IFileSystem> listener);
void RemoveAllListenersForFile(string fileName);
/// <summary>
/// Saves the file without prompting the user for permission.
/// </summary>

View File

@@ -1,11 +1,15 @@
using HavenSoft.Gen3Hex.Model;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
namespace HavenSoft.Gen3Hex.View {
public class WindowsFileSystem : IFileSystem {
private readonly Dictionary<string, List<FileSystemWatcher>> watchers = new Dictionary<string, List<FileSystemWatcher>>();
public string CopyText {
get => Clipboard.ContainsText() ? Clipboard.GetText() : string.Empty;
set => Clipboard.SetText(value);
@@ -15,9 +19,31 @@ namespace HavenSoft.Gen3Hex.View {
var dialog = new OpenFileDialog { Filter = CreateFilterFromOptions(extensionOptions) };
var result = dialog.ShowDialog();
if (result != true) return null;
if (!File.Exists(dialog.FileName)) return null;
var data = File.ReadAllBytes(dialog.FileName);
return new LoadedFile(dialog.FileName, data);
return LoadFile(dialog.FileName);
}
public LoadedFile LoadFile(string fileName) {
if (!File.Exists(fileName)) return null;
var data = File.ReadAllBytes(fileName);
return new LoadedFile(fileName, data);
}
public void AddListenerToFile(string fileName, Action<IFileSystem> listener) {
var watcher = new FileSystemWatcher(Path.GetDirectoryName(fileName)) {
NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName,
};
watcher.Changed += (sender, e) => {
if (e.FullPath.EndsWith(fileName)) listener(this);
};
watcher.EnableRaisingEvents = true;
if (!watchers.ContainsKey(fileName)) watchers[fileName] = new List<FileSystemWatcher>();
watchers[fileName].Add(watcher);
}
public void RemoveAllListenersForFile(string fileName) {
if (!watchers.ContainsKey(fileName)) return;
watchers[fileName].Clear();
}
public string RequestNewName(string currentName, params string[] extensionOptions) {

View File

@@ -1,4 +1,5 @@
using System;
using HavenSoft.Gen3Hex.Model;
using System;
using System.Windows.Input;
namespace HavenSoft.Gen3Hex.ViewModel {

View File

@@ -22,5 +22,6 @@ namespace HavenSoft.Gen3Hex.ViewModel {
IReadOnlyList<int> Find(string search);
IChildViewPort CreateChildView(int offset);
void FollowLink(int x, int y);
void ConsiderReload(IFileSystem fileSystem);
}
}

View File

@@ -116,6 +116,8 @@ namespace HavenSoft.Gen3Hex.ViewModel {
RequestTabChange?.Invoke(this, parent);
}
public void ConsiderReload(IFileSystem fileSystem) { }
private void NotifyCollectionChanged() {
if (children.Count == 0) return;
UpdateHeaders();

View File

@@ -301,6 +301,10 @@ namespace HavenSoft.Gen3Hex.ViewModel {
public void FollowLink(int x, int y) { }
public void ConsiderReload(IFileSystem fileSystem) {
}
private void Edit(char input) {
var point = GetEditPoint();
var element = currentView[point.X, point.Y];

View File

@@ -204,7 +204,6 @@ namespace HavenSoft.HexTests {
Assert.Equal(7, count);
}
[Theory]
[InlineData(nameof(EditorViewModel.Copy))]
[InlineData(nameof(EditorViewModel.Delete))]
@@ -323,6 +322,58 @@ namespace HavenSoft.HexTests {
Assert.Equal(1, editor.SelectedIndex);
}
[Fact]
public void EditorAddsOpenedFilesToFileSystemWatch() {
var fileSystem = new StubFileSystem();
string name = null;
fileSystem.AddListenerToFile = (fileName, action) => name = fileName;
var editor = new EditorViewModel(fileSystem);
editor.Open.Execute(new LoadedFile("InputFile.txt", new byte[20]));
Assert.Equal("InputFile.txt", name);
}
[Fact]
public void EditorRemovesFileSystemWatchWhenTabsClose() {
var fileSystem = new StubFileSystem();
string name = null;
fileSystem.RemoveAllListenersForFile = fileName => name = fileName;
var editor = new EditorViewModel(fileSystem);
editor.Open.Execute(new LoadedFile("InputFile.txt", new byte[20]));
editor.Close.Execute();
Assert.Equal("InputFile.txt", name);
}
[Fact]
public void ViewPortReloadsIfNoLocalChangesWhenFileChanges() {
var fileSystem = new StubFileSystem();
string file = null;
fileSystem.LoadFile = input => { file = input; return new LoadedFile(input, new byte[] { 0x10, 0x20 }); };
var viewPort = new ViewPort(new LoadedFile("file.txt", new byte[] { 0x00, 0x00 }));
viewPort.ConsiderReload(fileSystem);
Assert.Equal("file.txt", file);
Assert.Equal(0x10, viewPort[0, 0].Value);
}
[Fact]
public void ViewPortDoesNotReloadIfLocalChangesWhenFileChanges() {
var fileSystem = new StubFileSystem();
string file = null;
fileSystem.LoadFile = input => { file = input; return new LoadedFile(input, new byte[] { 0x10, 0x20 }); };
var viewPort = new ViewPort(new LoadedFile("file.txt", new byte[] { 0x00, 0x00 }));
viewPort.Edit("05");
viewPort.ConsiderReload(fileSystem);
Assert.Null(file);
Assert.Equal(0x05, viewPort[0, 0].Value);
}
private StubTabContent CreateClosableTab() {
var tab = new StubTabContent();
var close = new StubCommand { CanExecute = arg => true };