diff --git a/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj b/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj
index 748867e..b93ddff 100644
--- a/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj
+++ b/DiscordChatExporter.Core/DiscordChatExporter.Core.csproj
@@ -14,7 +14,8 @@
-
+
+
diff --git a/DiscordChatExporter.Core/Services/IUpdateService.cs b/DiscordChatExporter.Core/Services/IUpdateService.cs
new file mode 100644
index 0000000..e79feb8
--- /dev/null
+++ b/DiscordChatExporter.Core/Services/IUpdateService.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Threading.Tasks;
+
+namespace DiscordChatExporter.Core.Services
+{
+ public interface IUpdateService
+ {
+ Task CheckForUpdatesAsync();
+
+ Task PrepareUpdateAsync();
+
+ Task ApplyUpdateAsync(bool restart = true);
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Core/Services/UpdateService.cs b/DiscordChatExporter.Core/Services/UpdateService.cs
new file mode 100644
index 0000000..b40e155
--- /dev/null
+++ b/DiscordChatExporter.Core/Services/UpdateService.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Threading.Tasks;
+using Onova;
+using Onova.Services;
+
+namespace DiscordChatExporter.Core.Services
+{
+ public class UpdateService : IUpdateService
+ {
+ private readonly UpdateManager _updateManager;
+
+ private Version _lastVersion;
+ private bool _applied;
+
+ public UpdateService()
+ {
+ _updateManager = new UpdateManager(
+ new GithubPackageResolver("Tyrrrz", "DiscordChatExporter", "DiscordChatExporter.zip"),
+ new ZipPackageExtractor());
+ }
+
+ public async Task CheckForUpdatesAsync()
+ {
+#if DEBUG
+ // Never update in DEBUG mode
+ return null;
+#endif
+
+ // Remove some junk left over from last update
+ _updateManager.Cleanup();
+
+ // Check for updates
+ var check = await _updateManager.CheckForUpdatesAsync();
+
+ // Return latest version or null if running latest version already
+ return check.CanUpdate ? _lastVersion = check.LastVersion : null;
+ }
+
+ public async Task PrepareUpdateAsync()
+ {
+ if (_lastVersion == null)
+ return;
+
+ // Download and prepare update
+ await _updateManager.PreparePackageAsync(_lastVersion);
+ }
+
+ public async Task ApplyUpdateAsync(bool restart = true)
+ {
+ if (_lastVersion == null)
+ return;
+ if (_applied)
+ return;
+
+ // Enqueue an update
+ await _updateManager.EnqueueApplyPackageAsync(_lastVersion, restart);
+
+ _applied = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Gui/Container.cs b/DiscordChatExporter.Gui/Container.cs
index 1e6a335..cb15941 100644
--- a/DiscordChatExporter.Gui/Container.cs
+++ b/DiscordChatExporter.Gui/Container.cs
@@ -28,9 +28,7 @@ namespace DiscordChatExporter.Gui
SimpleIoc.Default.Register();
SimpleIoc.Default.Register();
SimpleIoc.Default.Register();
-
- // Load settings
- Resolve().Load();
+ SimpleIoc.Default.Register();
// View models
SimpleIoc.Default.Register(true);
@@ -42,8 +40,6 @@ namespace DiscordChatExporter.Gui
public void Cleanup()
{
- // Save settings
- ServiceLocator.Current.GetInstance().Save();
}
}
}
\ No newline at end of file
diff --git a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
index e574b8c..2113850 100644
--- a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
+++ b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj
@@ -65,14 +65,12 @@
-
..\packages\MvvmLightLibs.5.3.0.0\lib\net45\System.Windows.Interactivity.dll
4.0
-
..\packages\Tyrrrz.Extensions.1.5.0\lib\net45\Tyrrrz.Extensions.dll
@@ -84,6 +82,7 @@
+
diff --git a/DiscordChatExporter.Gui/Messages/ShowNotificationMessage.cs b/DiscordChatExporter.Gui/Messages/ShowNotificationMessage.cs
new file mode 100644
index 0000000..9c2285a
--- /dev/null
+++ b/DiscordChatExporter.Gui/Messages/ShowNotificationMessage.cs
@@ -0,0 +1,25 @@
+using System;
+
+namespace DiscordChatExporter.Gui.Messages
+{
+ public class ShowNotificationMessage
+ {
+ public string Message { get; }
+
+ public string CallbackCaption { get; }
+
+ public Action Callback { get; }
+
+ public ShowNotificationMessage(string message)
+ {
+ Message = message;
+ }
+
+ public ShowNotificationMessage(string message, string callbackCaption, Action callback)
+ : this(message)
+ {
+ CallbackCaption = callbackCaption;
+ Callback = callback;
+ }
+ }
+}
\ No newline at end of file
diff --git a/DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs b/DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs
index e5e66b4..ad67e48 100644
--- a/DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs
+++ b/DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs
@@ -15,6 +15,8 @@ namespace DiscordChatExporter.Gui.ViewModels
Guild SelectedGuild { get; set; }
IReadOnlyList AvailableChannels { get; }
+ RelayCommand ViewLoadedCommand { get; }
+ RelayCommand ViewClosedCommand { get; }
RelayCommand PullDataCommand { get; }
RelayCommand ShowSettingsCommand { get; }
RelayCommand ShowAboutCommand { get; }
diff --git a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
index 0b222e7..b8d230e 100644
--- a/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
+++ b/DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
+using System.Windows;
using DiscordChatExporter.Core.Exceptions;
using DiscordChatExporter.Core.Models;
using DiscordChatExporter.Core.Services;
@@ -16,6 +17,7 @@ namespace DiscordChatExporter.Gui.ViewModels
public class MainViewModel : ViewModelBase, IMainViewModel
{
private readonly ISettingsService _settingsService;
+ private readonly IUpdateService _updateService;
private readonly IDataService _dataService;
private readonly IMessageGroupService _messageGroupService;
private readonly IExportService _exportService;
@@ -81,15 +83,18 @@ namespace DiscordChatExporter.Gui.ViewModels
private set => Set(ref _availableChannels, value);
}
+ public RelayCommand ViewLoadedCommand { get; }
+ public RelayCommand ViewClosedCommand { get; }
public RelayCommand PullDataCommand { get; }
public RelayCommand ShowSettingsCommand { get; }
public RelayCommand ShowAboutCommand { get; }
public RelayCommand ShowExportSetupCommand { get; }
- public MainViewModel(ISettingsService settingsService, IDataService dataService,
+ public MainViewModel(ISettingsService settingsService, IUpdateService updateService, IDataService dataService,
IMessageGroupService messageGroupService, IExportService exportService)
{
_settingsService = settingsService;
+ _updateService = updateService;
_dataService = dataService;
_messageGroupService = messageGroupService;
_exportService = exportService;
@@ -97,19 +102,54 @@ namespace DiscordChatExporter.Gui.ViewModels
_guildChannelsMap = new Dictionary>();
// Commands
+ ViewLoadedCommand = new RelayCommand(ViewLoaded);
+ ViewClosedCommand = new RelayCommand(ViewClosed);
PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy);
ShowSettingsCommand = new RelayCommand(ShowSettings);
ShowAboutCommand = new RelayCommand(ShowAbout);
ShowExportSetupCommand = new RelayCommand(ShowExportSetup, _ => !IsBusy);
// Messages
- MessengerInstance.Register(this, m =>
+ MessengerInstance.Register(this,
+ m => { Export(m.Channel, m.FilePath, m.Format, m.From, m.To); });
+ }
+
+ private async void ViewLoaded()
+ {
+ // Load settings
+ _settingsService.Load();
+
+ // Set last token
+ Token = _settingsService.LastToken;
+
+ // Check for updates
+ var lastVersion = await _updateService.CheckForUpdatesAsync();
+ if (lastVersion != null)
{
- Export(m.Channel, m.FilePath, m.Format, m.From, m.To);
- });
+ // Download updates
+ await _updateService.PrepareUpdateAsync();
+
+ // Notify user
+ MessengerInstance.Send(
+ new ShowNotificationMessage(
+ $"DiscordChatExporter v{lastVersion} has been downloaded. " +
+ "It will be installed once you exit.",
+ "INSTALL NOW",
+ async () =>
+ {
+ await _updateService.ApplyUpdateAsync();
+ Application.Current.Shutdown();
+ }));
+ }
+ }
+
+ private void ViewClosed()
+ {
+ // Save settings
+ _settingsService.Save();
- // Defaults
- _token = _settingsService.LastToken;
+ // Apply updates if available
+ _updateService.ApplyUpdateAsync(false);
}
private async void PullData()
diff --git a/DiscordChatExporter.Gui/Views/MainWindow.ammy b/DiscordChatExporter.Gui/Views/MainWindow.ammy
index 2368ad8..3162053 100644
--- a/DiscordChatExporter.Gui/Views/MainWindow.ammy
+++ b/DiscordChatExporter.Gui/Views/MainWindow.ammy
@@ -1,4 +1,5 @@
-using MaterialDesignThemes.Wpf
+using System.Windows.Interactivity;
+using MaterialDesignThemes.Wpf
using MaterialDesignThemes.Wpf.Transitions
Window "DiscordChatExporter.Gui.Views.MainWindow" {
@@ -19,15 +20,27 @@ Window "DiscordChatExporter.Gui.Views.MainWindow" {
UseLayoutRounding: true
WindowStartupLocation: CenterScreen
+ Interaction.Triggers: [
+ Interactivity.EventTrigger {
+ EventName: "Loaded"
+ InvokeCommandAction { Command: bind ViewLoadedCommand }
+ },
+ Interactivity.EventTrigger {
+ EventName: "Closed"
+ InvokeCommandAction { Command: bind ViewClosedCommand }
+ }
+ ]
+
DialogHost {
- DockPanel {
- IsEnabled: bind IsBusy
- convert (bool b) => b ? false : true
+ SnackbarMessageQueue: bind MessageQueue from "Snackbar"
+ DockPanel {
// Toolbar
Border {
DockPanel.Dock: Top
Background: resource dyn "PrimaryHueMidBrush"
+ IsEnabled: bind IsBusy
+ convert (bool b) => b ? false : true
TextElement.Foreground: resource dyn "SecondaryInverseTextBrush"
StackPanel {
Grid {
@@ -104,6 +117,8 @@ Window "DiscordChatExporter.Gui.Views.MainWindow" {
Grid {
DockPanel {
Background: resource dyn "MaterialDesignCardBackground"
+ IsEnabled: bind IsBusy
+ convert (bool b) => b ? false : true
Visibility: bind IsDataAvailable
convert (bool b) => b ? Visibility.Visible : Visibility.Hidden
@@ -265,6 +280,10 @@ Window "DiscordChatExporter.Gui.Views.MainWindow" {
}
}
}
+
+ // Snackbar
+ Snackbar "Snackbar" {
+ }
}
}
}
diff --git a/DiscordChatExporter.Gui/Views/MainWindow.ammy.cs b/DiscordChatExporter.Gui/Views/MainWindow.ammy.cs
index 9cca4ea..49c1a62 100644
--- a/DiscordChatExporter.Gui/Views/MainWindow.ammy.cs
+++ b/DiscordChatExporter.Gui/Views/MainWindow.ammy.cs
@@ -1,4 +1,5 @@
-using System.Reflection;
+using System;
+using System.Reflection;
using DiscordChatExporter.Gui.Messages;
using GalaSoft.MvvmLight.Messaging;
using MaterialDesignThemes.Wpf;
@@ -13,7 +14,13 @@ namespace DiscordChatExporter.Gui.Views
InitializeComponent();
Title += $" v{Assembly.GetExecutingAssembly().GetName().Version}";
- // Dialogs
+ Snackbar.MessageQueue = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
+
+ // Notification messages
+ Messenger.Default.Register(this,
+ m => Snackbar.MessageQueue.Enqueue(m.Message, m.CallbackCaption, m.Callback));
+
+ // Dialog messages
Messenger.Default.Register(this,
m => DialogHost.Show(new ErrorDialog()).Forget());
Messenger.Default.Register(this,
diff --git a/Readme.md b/Readme.md
index e71aa47..0f09973 100644
--- a/Readme.md
+++ b/Readme.md
@@ -39,6 +39,7 @@ DiscordChatExporter can be used to export message history from a [Discord](https
- [GalaSoft.MVVMLight](http://www.mvvmlight.net)
- [MaterialDesignInXamlToolkit](https://github.com/ButchersBoy/MaterialDesignInXamlToolkit)
- [Newtonsoft.Json](http://www.newtonsoft.com/json)
+- [Onova](https://github.com/Tyrrrz/Onova)
- [FluentCommandLineParser](https://github.com/fclp/fluent-command-line-parser)
- [Tyrrrz.Extensions](https://github.com/Tyrrrz/Extensions)
- [Tyrrrz.Settings](https://github.com/Tyrrrz/Settings)