diff --git a/Filtration.Tests/Filtration.Tests.csproj b/Filtration.Tests/Filtration.Tests.csproj
index d26871f..5a248a6 100644
--- a/Filtration.Tests/Filtration.Tests.csproj
+++ b/Filtration.Tests/Filtration.Tests.csproj
@@ -48,8 +48,10 @@
+
+
diff --git a/Filtration.Tests/Services/TestHTTPService.cs b/Filtration.Tests/Services/TestHTTPService.cs
new file mode 100644
index 0000000..54b7665
--- /dev/null
+++ b/Filtration.Tests/Services/TestHTTPService.cs
@@ -0,0 +1,23 @@
+using Filtration.Services;
+using NUnit.Framework;
+
+namespace Filtration.Tests.Services
+{
+ [Ignore("Integration Test - Makes real HTTP call")]
+ [TestFixture]
+ public class TestHTTPService
+ {
+ [Test]
+ public async void GetContent_FetchesDataFromUrl()
+ {
+ // Arrange
+ var service = new HTTPService();
+
+ // Act
+ var result = await service.GetContentAsync("http://ben-wallis.github.io/Filtration/filtration_version.xml");
+
+ // Assert
+ Assert.IsNotNull(result);
+ }
+ }
+}
diff --git a/Filtration.Tests/Services/TestUpdateService.cs b/Filtration.Tests/Services/TestUpdateService.cs
new file mode 100644
index 0000000..343c3ee
--- /dev/null
+++ b/Filtration.Tests/Services/TestUpdateService.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Filtration.Models;
+using Filtration.Services;
+using Moq;
+using NUnit.Framework;
+
+namespace Filtration.Tests.Services
+{
+ [TestFixture]
+ public class TestUpdateService
+ {
+ [Test]
+ public void DeserializeUpdateData_ReturnsCorrectData()
+ {
+ // Arrange
+ var testInputData = @"
+ 0.2
+ 2015-07-01
+ http://www.google.com
+ * Release notes line 1
+* Release notes line 2
+* More really great release notes!
+ ";
+
+ var expectedResult = new UpdateData
+ {
+ CurrentVersion = 0.2m,
+ DownloadUrl = "http://www.google.com",
+ ReleaseDate = new DateTime(2015, 7, 1),
+ ReleaseNotes = @"* Release notes line 1
+* Release notes line 2
+* More really great release notes!"
+ };
+
+ var mockHTTPService = new Mock();
+ var service = new UpdateCheckService(mockHTTPService.Object);
+
+ // Act
+ var result = service.DeserializeUpdateData(testInputData);
+
+ // Assert
+ Assert.AreEqual(expectedResult.CurrentVersion, result.CurrentVersion);
+ Assert.AreEqual(expectedResult.DownloadUrl, result.DownloadUrl);
+ Assert.AreEqual(expectedResult.ReleaseDate, result.ReleaseDate);
+ Assert.AreEqual(expectedResult.ReleaseNotes, result.ReleaseNotes);
+ }
+
+ }
+}
diff --git a/Filtration/App.config b/Filtration/App.config
index eee4e61..9cd140a 100644
--- a/Filtration/App.config
+++ b/Filtration/App.config
@@ -16,6 +16,12 @@
True
+
+ 0
+
+
+ False
+
diff --git a/Filtration/Filtration.csproj b/Filtration/Filtration.csproj
index 5421666..5c4307c 100644
--- a/Filtration/Filtration.csproj
+++ b/Filtration/Filtration.csproj
@@ -139,10 +139,13 @@
+
+
+
@@ -172,6 +175,7 @@
+
AvalonDockWorkspaceView.xaml
@@ -223,6 +227,9 @@
StartPageView.xaml
+
+ UpdateAvailableView.xaml
+
@@ -330,6 +337,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
@@ -397,7 +408,6 @@
-
Always
diff --git a/Filtration/Models/UpdateData.cs b/Filtration/Models/UpdateData.cs
new file mode 100644
index 0000000..b7a556a
--- /dev/null
+++ b/Filtration/Models/UpdateData.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace Filtration.Models
+{
+ [Serializable]
+ public class UpdateData
+ {
+ private string _releaseNotes;
+
+ public string DownloadUrl { get; set; }
+ public decimal CurrentVersion { get; set; }
+ public DateTime ReleaseDate { get; set; }
+
+ public string ReleaseNotes
+ {
+ get { return _releaseNotes; }
+ set
+ {
+ var r = new Regex("(?
True
+
+ 0
+
+
+ False
+
\ No newline at end of file
diff --git a/Filtration/Resources/Images/doge.jpg b/Filtration/Resources/Images/doge.jpg
deleted file mode 100644
index bfb772c..0000000
Binary files a/Filtration/Resources/Images/doge.jpg and /dev/null differ
diff --git a/Filtration/Services/HTTPService.cs b/Filtration/Services/HTTPService.cs
new file mode 100644
index 0000000..a9f728e
--- /dev/null
+++ b/Filtration/Services/HTTPService.cs
@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Threading.Tasks;
+
+namespace Filtration.Services
+{
+ internal interface IHTTPService
+ {
+ Task GetContentAsync(string url);
+ }
+
+ internal class HTTPService : IHTTPService
+ {
+ public async Task GetContentAsync(string url)
+ {
+ var urlUri = new Uri(url);
+
+ var request = WebRequest.Create(urlUri);
+
+ var response = await request.GetResponseAsync();
+ using (var s = response.GetResponseStream())
+ {
+ using (var sr = new StreamReader(s))
+ {
+ return sr.ReadToEnd();
+ }
+ }
+ }
+ }
+}
diff --git a/Filtration/Services/UpdateCheckService.cs b/Filtration/Services/UpdateCheckService.cs
new file mode 100644
index 0000000..5a01c83
--- /dev/null
+++ b/Filtration/Services/UpdateCheckService.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Serialization;
+using Filtration.Models;
+
+namespace Filtration.Services
+{
+ internal interface IUpdateCheckService
+ {
+ Task GetUpdateData();
+ }
+
+ internal class UpdateCheckService : IUpdateCheckService
+ {
+ private readonly IHTTPService _httpService;
+ private const string UpdateDataUrl = "http://ben-wallis.github.io/Filtration/filtration_version.xml";
+
+ public UpdateCheckService(IHTTPService httpService)
+ {
+ _httpService = httpService;
+ }
+
+ public async Task GetUpdateData()
+ {
+ var updateXml = await _httpService.GetContentAsync(UpdateDataUrl);
+ return (DeserializeUpdateData(updateXml));
+ }
+
+ public UpdateData DeserializeUpdateData(string updateDataString)
+ {
+ var serializer = new XmlSerializer(typeof(UpdateData));
+ object result;
+
+ using (TextReader reader = new StringReader(updateDataString))
+ {
+ result = serializer.Deserialize(reader);
+ }
+
+ return result as UpdateData;
+ }
+
+ }
+}
diff --git a/Filtration/ViewModels/MainWindowViewModel.cs b/Filtration/ViewModels/MainWindowViewModel.cs
index e0a29df..720fddd 100644
--- a/Filtration/ViewModels/MainWindowViewModel.cs
+++ b/Filtration/ViewModels/MainWindowViewModel.cs
@@ -2,13 +2,17 @@
using System.Diagnostics;
using System.IO;
using System.Reflection;
+using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Filtration.Common.ViewModels;
using Filtration.Interface;
+using Filtration.Models;
using Filtration.ObjectModel.ThemeEditor;
+using Filtration.Properties;
using Filtration.Repositories;
+using Filtration.Services;
using Filtration.ThemeEditor.Providers;
using Filtration.ThemeEditor.Services;
using Filtration.ThemeEditor.ViewModels;
@@ -36,6 +40,8 @@ namespace Filtration.ViewModels
private readonly ISettingsPageViewModel _settingsPageViewModel;
private readonly IThemeProvider _themeProvider;
private readonly IThemeService _themeService;
+ private readonly IUpdateCheckService _updateCheckService;
+ private readonly IUpdateAvailableViewModel _updateAvailableViewModel;
public MainWindowViewModel(IItemFilterScriptRepository itemFilterScriptRepository,
IItemFilterScriptTranslator itemFilterScriptTranslator,
@@ -43,7 +49,9 @@ namespace Filtration.ViewModels
IAvalonDockWorkspaceViewModel avalonDockWorkspaceViewModel,
ISettingsPageViewModel settingsPageViewModel,
IThemeProvider themeProvider,
- IThemeService themeService)
+ IThemeService themeService,
+ IUpdateCheckService updateCheckService,
+ IUpdateAvailableViewModel updateAvailableViewModel)
{
_itemFilterScriptRepository = itemFilterScriptRepository;
_itemFilterScriptTranslator = itemFilterScriptTranslator;
@@ -52,6 +60,8 @@ namespace Filtration.ViewModels
_settingsPageViewModel = settingsPageViewModel;
_themeProvider = themeProvider;
_themeService = themeService;
+ _updateCheckService = updateCheckService;
+ _updateAvailableViewModel = updateAvailableViewModel;
NewScriptCommand = new RelayCommand(OnNewScriptCommand);
CopyScriptCommand = new RelayCommand(OnCopyScriptCommand, () => ActiveDocumentIsScript);
@@ -128,6 +138,7 @@ namespace Filtration.ViewModels
}
}
});
+ CheckForUpdates();
}
public RelayCommand OpenScriptCommand { get; private set; }
@@ -162,6 +173,37 @@ namespace Filtration.ViewModels
public RelayCommand ClearFiltersCommand { get; private set; }
+ public async void CheckForUpdates()
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var assemblyVersion = FileVersionInfo.GetVersionInfo(assembly.Location);
+ var currentVersion = Convert.ToDecimal(assemblyVersion.FileMajorPart + "." + assemblyVersion.FileMinorPart);
+
+ var result = await _updateCheckService.GetUpdateData();
+
+ try
+ {
+ if (result.CurrentVersion > currentVersion)
+ {
+ if (Settings.Default.SuppressUpdates == false ||
+ Settings.Default.SuppressUpdatesUpToVersion < result.CurrentVersion)
+ {
+ Settings.Default.SuppressUpdates = false;
+ Settings.Default.Save();
+ var updateAvailableView = new UpdateAvailableView {DataContext = _updateAvailableViewModel};
+ _updateAvailableViewModel.Initialise(result, currentVersion);
+ _updateAvailableViewModel.OnRequestClose += (s, e) => updateAvailableView.Close();
+ updateAvailableView.ShowDialog();
+ }
+ }
+ }
+ catch (Exception)
+ {
+ // We don't care if the update check fails, because it could fail for multiple reasons
+ // including the user blocking Filtration in their firewall.
+ }
+ }
+
public ImageSource Icon { get; private set; }
public IAvalonDockWorkspaceViewModel AvalonDockWorkspaceViewModel
diff --git a/Filtration/ViewModels/SettingsPageViewModel.cs b/Filtration/ViewModels/SettingsPageViewModel.cs
index 2526017..ac99b2a 100644
--- a/Filtration/ViewModels/SettingsPageViewModel.cs
+++ b/Filtration/ViewModels/SettingsPageViewModel.cs
@@ -22,11 +22,13 @@ namespace Filtration.ViewModels
DefaultFilterDirectory = Settings.Default.DefaultFilterDirectory;
ExtraLineBetweenBlocks = Settings.Default.ExtraLineBetweenBlocks;
+ SuppressUpdateNotifications = Settings.Default.SuppressUpdates;
}
public RelayCommand SaveCommand { get; private set; }
public string DefaultFilterDirectory { get; set; }
public bool ExtraLineBetweenBlocks { get; set; }
+ public bool SuppressUpdateNotifications { get; set; }
private void OnSaveCommand()
{
@@ -35,6 +37,7 @@ namespace Filtration.ViewModels
_itemFilterPersistenceService.SetItemFilterScriptDirectory(DefaultFilterDirectory);
Settings.Default.ExtraLineBetweenBlocks = ExtraLineBetweenBlocks;
+ Settings.Default.SuppressUpdates = SuppressUpdateNotifications;
}
catch (DirectoryNotFoundException)
{
diff --git a/Filtration/ViewModels/UpdateAvailableViewModel.cs b/Filtration/ViewModels/UpdateAvailableViewModel.cs
new file mode 100644
index 0000000..5cf6ed0
--- /dev/null
+++ b/Filtration/ViewModels/UpdateAvailableViewModel.cs
@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using Filtration.Models;
+using Filtration.Properties;
+using GalaSoft.MvvmLight.CommandWpf;
+
+namespace Filtration.ViewModels
+{
+ internal interface IUpdateAvailableViewModel
+ {
+ event EventHandler OnRequestClose;
+ void Initialise(UpdateData updateData, decimal currentVersion);
+ decimal CurrentVersion { get; }
+ decimal NewVersion { get; }
+ string ReleaseNotes { get; }
+ DateTime ReleaseDate { get; }
+ }
+
+ internal class UpdateAvailableViewModel : IUpdateAvailableViewModel
+ {
+ private UpdateData _updateData;
+ private decimal _currentVersion;
+
+ public UpdateAvailableViewModel()
+ {
+ DownloadCommand = new RelayCommand(OnDownloadCommand);
+ AskLaterCommand = new RelayCommand(OnAskLaterCommand);
+ NeverAskAgainCommand = new RelayCommand(OnNeverAskAgainCommand);
+ }
+
+ public event EventHandler OnRequestClose;
+
+ public RelayCommand NeverAskAgainCommand { get; private set; }
+ public RelayCommand AskLaterCommand { get; private set; }
+ public RelayCommand DownloadCommand { get; private set; }
+
+ public void Initialise(UpdateData updateData, decimal currentVersion)
+ {
+ _currentVersion = currentVersion;
+ _updateData = updateData;
+ }
+
+ public decimal CurrentVersion
+ {
+ get { return _currentVersion; }
+ }
+
+ public decimal NewVersion
+ {
+ get { return _updateData.CurrentVersion; }
+ }
+
+ public string ReleaseNotes
+ {
+ get { return _updateData.ReleaseNotes; }
+ }
+
+ public DateTime ReleaseDate
+ {
+ get { return _updateData.ReleaseDate; }
+ }
+
+ private void OnDownloadCommand()
+ {
+ Process.Start(_updateData.DownloadUrl);
+ }
+
+ private void OnNeverAskAgainCommand()
+ {
+ Settings.Default.SuppressUpdates = true;
+ Settings.Default.SuppressUpdatesUpToVersion = _updateData.CurrentVersion;
+ Settings.Default.Save();
+ CloseWindow();
+ }
+
+ private void OnAskLaterCommand()
+ {
+ CloseWindow();
+ }
+
+ private void CloseWindow()
+ {
+ if (OnRequestClose != null)
+ {
+ OnRequestClose(this, new EventArgs());
+ }
+ }
+ }
+
+}
+
diff --git a/Filtration/Views/MainWindow.xaml b/Filtration/Views/MainWindow.xaml
index 3e44721..ff8670e 100644
--- a/Filtration/Views/MainWindow.xaml
+++ b/Filtration/Views/MainWindow.xaml
@@ -28,8 +28,7 @@
- One day there will be something here! Maybe recent documents or something? For now here's a picture of Doge:
-
+ Recent Documents will go here in a future release.
diff --git a/Filtration/Views/SettingsPageView.xaml b/Filtration/Views/SettingsPageView.xaml
index 20a7c6e..a462640 100644
--- a/Filtration/Views/SettingsPageView.xaml
+++ b/Filtration/Views/SettingsPageView.xaml
@@ -25,11 +25,15 @@
+
+
Default Filter Directory:
- Add blank line between blocks when saving
-
+ Add blank line between blocks when saving
+
+ Suppress update notifications for current version
+
diff --git a/Filtration/Views/UpdateAvailableView.xaml b/Filtration/Views/UpdateAvailableView.xaml
new file mode 100644
index 0000000..a147587
--- /dev/null
+++ b/Filtration/Views/UpdateAvailableView.xaml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Filtration/Views/UpdateAvailableView.xaml.cs b/Filtration/Views/UpdateAvailableView.xaml.cs
new file mode 100644
index 0000000..23004c7
--- /dev/null
+++ b/Filtration/Views/UpdateAvailableView.xaml.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace Filtration.Views
+{
+ ///
+ /// Interaction logic for UpdateAvailableView.xaml
+ ///
+ public partial class UpdateAvailableView : Window
+ {
+ public UpdateAvailableView()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/Filtration/WindsorInstallers/ServicesInstaller.cs b/Filtration/WindsorInstallers/ServicesInstaller.cs
index 51058e7..107ffd4 100644
--- a/Filtration/WindsorInstallers/ServicesInstaller.cs
+++ b/Filtration/WindsorInstallers/ServicesInstaller.cs
@@ -24,6 +24,16 @@ namespace Filtration.WindsorInstallers
Component.For()
.ImplementedBy()
.LifeStyle.Singleton);
+
+ container.Register(
+ Component.For()
+ .ImplementedBy()
+ .LifeStyle.Singleton);
+
+ container.Register(
+ Component.For()
+ .ImplementedBy()
+ .LifeStyle.Singleton);
}
}
}
diff --git a/Filtration/WindsorInstallers/ViewModelsInstaller.cs b/Filtration/WindsorInstallers/ViewModelsInstaller.cs
index af96427..d002ac1 100644
--- a/Filtration/WindsorInstallers/ViewModelsInstaller.cs
+++ b/Filtration/WindsorInstallers/ViewModelsInstaller.cs
@@ -60,7 +60,12 @@ namespace Filtration.WindsorInstallers
Component.For()
.ImplementedBy()
.LifeStyle.Transient);
-
+
+ container.Register(
+ Component.For()
+ .ImplementedBy()
+ .LifeStyle.Transient);
+
container.Register(
Component.For().AsFactory());