diff --git a/Gen3Hex/Model/IFileSystem.cs b/Gen3Hex/Model/IFileSystem.cs
index 8811ac8d..4a7304f4 100644
--- a/Gen3Hex/Model/IFileSystem.cs
+++ b/Gen3Hex/Model/IFileSystem.cs
@@ -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 @@
///
LoadedFile OpenFile(params string[] extensionOptions);
+ ///
+ /// Have the filesystem open a specific file.
+ ///
+ ///
+ /// If the file exists, it is loaded and returned.
+ /// If it doesn't exist, returns null.
+ ///
+ LoadedFile LoadFile(string fileName);
+
+ ///
+ /// When a file changes, the filesystem will call all listeners for that file.
+ ///
+ void AddListenerToFile(string fileName, Action listener);
+
+ void RemoveAllListenersForFile(string fileName);
+
///
/// Saves the file without prompting the user for permission.
///
diff --git a/Gen3Hex/View/WindowsFileSystem.cs b/Gen3Hex/View/WindowsFileSystem.cs
index bfa9e425..61b5d6cc 100644
--- a/Gen3Hex/View/WindowsFileSystem.cs
+++ b/Gen3Hex/View/WindowsFileSystem.cs
@@ -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> watchers = new Dictionary>();
+
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 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();
+ 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) {
diff --git a/Gen3Hex/ViewModel/ITabContent.cs b/Gen3Hex/ViewModel/ITabContent.cs
index 91b1642c..acc23012 100644
--- a/Gen3Hex/ViewModel/ITabContent.cs
+++ b/Gen3Hex/ViewModel/ITabContent.cs
@@ -1,4 +1,5 @@
-using System;
+using HavenSoft.Gen3Hex.Model;
+using System;
using System.Windows.Input;
namespace HavenSoft.Gen3Hex.ViewModel {
diff --git a/Gen3Hex/ViewModel/IViewPort.cs b/Gen3Hex/ViewModel/IViewPort.cs
index ff69fe3e..dc40dd4c 100644
--- a/Gen3Hex/ViewModel/IViewPort.cs
+++ b/Gen3Hex/ViewModel/IViewPort.cs
@@ -22,5 +22,6 @@ namespace HavenSoft.Gen3Hex.ViewModel {
IReadOnlyList Find(string search);
IChildViewPort CreateChildView(int offset);
void FollowLink(int x, int y);
+ void ConsiderReload(IFileSystem fileSystem);
}
}
diff --git a/Gen3Hex/ViewModel/SearchResultsViewPort.cs b/Gen3Hex/ViewModel/SearchResultsViewPort.cs
index e7772246..ce7c01b1 100644
--- a/Gen3Hex/ViewModel/SearchResultsViewPort.cs
+++ b/Gen3Hex/ViewModel/SearchResultsViewPort.cs
@@ -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();
diff --git a/Gen3Hex/ViewModel/ViewPort.cs b/Gen3Hex/ViewModel/ViewPort.cs
index 993a4f85..ca511187 100644
--- a/Gen3Hex/ViewModel/ViewPort.cs
+++ b/Gen3Hex/ViewModel/ViewPort.cs
@@ -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];
diff --git a/HexTests/GeneralAppTests.cs b/HexTests/GeneralAppTests.cs
index 2e456298..9a10dfcd 100644
--- a/HexTests/GeneralAppTests.cs
+++ b/HexTests/GeneralAppTests.cs
@@ -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 };