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)