diff --git a/Wino.Core.Domain/Interfaces/INotificationBuilder.cs b/Wino.Core.Domain/Interfaces/INotificationBuilder.cs
index ad3fdea2..b2be4c35 100644
--- a/Wino.Core.Domain/Interfaces/INotificationBuilder.cs
+++ b/Wino.Core.Domain/Interfaces/INotificationBuilder.cs
@@ -51,6 +51,11 @@ public interface INotificationBuilder
///
void CreateStoreUpdateNotification();
+ ///
+ /// Shows the one-time release migration notification.
+ ///
+ void CreateReleaseMigrationNotification();
+
///
/// Creates a calendar reminder toast for the specified calendar item.
///
diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json
index 247b881b..65f628ab 100644
--- a/Wino.Core.Domain/Translations/en_US/resources.json
+++ b/Wino.Core.Domain/Translations/en_US/resources.json
@@ -704,6 +704,8 @@
"Notifications_WinoUpdatedTitle": "Wino Mail has been updated.",
"Notifications_StoreUpdateAvailableTitle": "Update available",
"Notifications_StoreUpdateAvailableMessage": "A newer version of Wino Mail is ready to install from Microsoft Store.",
+ "Notifications_ReleaseMigrationTitle": "New Wino Mail & Calendar",
+ "Notifications_ReleaseMigrationMessage": "Wino Mail got updated to the next version. Please re-create your accounts and start using the next version including calendar, mail templates, shortcuts, and a bunch of other improvements.",
"OnlineSearchFailed_Message": "Failed to perform search\n{0}\n\nListing offline mails.",
"OnlineSearchTry_Line1": "Can't find what you are looking for?",
"OnlineSearchTry_Line2": "Try online search.",
diff --git a/Wino.Mail.WinUI/Package.appxmanifest b/Wino.Mail.WinUI/Package.appxmanifest
index 087d4689..48c4df34 100644
--- a/Wino.Mail.WinUI/Package.appxmanifest
+++ b/Wino.Mail.WinUI/Package.appxmanifest
@@ -23,7 +23,7 @@
+ Version="2.0.7.0" />
diff --git a/Wino.Mail.WinUI/Services/NotificationBuilder.cs b/Wino.Mail.WinUI/Services/NotificationBuilder.cs
index 5361d5e1..bb52ecf4 100644
--- a/Wino.Mail.WinUI/Services/NotificationBuilder.cs
+++ b/Wino.Mail.WinUI/Services/NotificationBuilder.cs
@@ -233,6 +233,17 @@ public class NotificationBuilder : INotificationBuilder
ShowNotification(builder, "store-update-available");
}
+ public void CreateReleaseMigrationNotification()
+ {
+ var builder = CreateBuilder();
+ builder.AddText(Translator.Notifications_ReleaseMigrationTitle);
+ builder.AddText(Translator.Notifications_ReleaseMigrationMessage);
+ builder.AddArgument(Constants.ToastModeKey, Constants.ToastModeMail);
+ builder.AddButton(CreateDismissButton());
+
+ ShowNotification(builder, "release-migration-v2");
+ }
+
public Task CreateCalendarReminderNotificationAsync(CalendarItem calendarItem, long reminderDurationInSeconds)
{
if (calendarItem == null)
diff --git a/Wino.Mail.WinUI/Services/ReleaseLocalAccountDataCleanupService.cs b/Wino.Mail.WinUI/Services/ReleaseLocalAccountDataCleanupService.cs
index 44edc737..fea775ff 100644
--- a/Wino.Mail.WinUI/Services/ReleaseLocalAccountDataCleanupService.cs
+++ b/Wino.Mail.WinUI/Services/ReleaseLocalAccountDataCleanupService.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Threading.Tasks;
using Serilog;
using Wino.Core.Domain.Interfaces;
@@ -14,13 +15,16 @@ public sealed class ReleaseLocalAccountDataCleanupService
private readonly IConfigurationService _configurationService;
private readonly IApplicationConfiguration _applicationConfiguration;
+ private readonly INotificationBuilder _notificationBuilder;
private readonly ILogger _logger = Log.ForContext();
public ReleaseLocalAccountDataCleanupService(IConfigurationService configurationService,
- IApplicationConfiguration applicationConfiguration)
+ IApplicationConfiguration applicationConfiguration,
+ INotificationBuilder notificationBuilder)
{
_configurationService = configurationService;
_applicationConfiguration = applicationConfiguration;
+ _notificationBuilder = notificationBuilder;
}
public async Task RunIfNeededAsync()
@@ -45,44 +49,70 @@ public sealed class ReleaseLocalAccountDataCleanupService
Path.Combine(publisherPath, LegacyDatabaseFileName)
};
+ var hadLegacyData = false;
+
foreach (var targetPath in cleanupTargets)
{
- await DeletePathIfExistsAsync(localFolderPath, targetPath).ConfigureAwait(false);
+ hadLegacyData |= await DeletePathIfExistsAsync(targetPath, localFolderPath, publisherPath).ConfigureAwait(false);
}
_configurationService.Set(CleanupCompletedSettingKey, true);
+
+ if (hadLegacyData)
+ {
+ _notificationBuilder.CreateReleaseMigrationNotification();
+ }
+
_logger.Information("Completed one-time local account data cleanup for release migration.");
}
- private async Task DeletePathIfExistsAsync(string localFolderPath, string targetPath)
+ private async Task DeletePathIfExistsAsync(string targetPath, params string[] allowedRootPaths)
{
try
{
var fullTargetPath = Path.GetFullPath(targetPath);
- var fullLocalFolderPath = Path.GetFullPath(localFolderPath);
-
- if (!fullTargetPath.StartsWith(fullLocalFolderPath, StringComparison.OrdinalIgnoreCase))
+ if (!allowedRootPaths.Any(rootPath => IsPathUnderAllowedRoot(fullTargetPath, rootPath)))
{
- _logger.Warning("Skipped startup cleanup for path outside local folder: {TargetPath}", fullTargetPath);
- return;
+ _logger.Warning("Skipped startup cleanup for path outside allowed roots: {TargetPath}", fullTargetPath);
+ return false;
}
+ var targetExists = Directory.Exists(fullTargetPath) || File.Exists(fullTargetPath);
+
if (Directory.Exists(fullTargetPath))
{
await Task.Run(() => Directory.Delete(fullTargetPath, recursive: true)).ConfigureAwait(false);
_logger.Information("Deleted legacy startup cleanup directory {TargetPath}", fullTargetPath);
- return;
+ return true;
}
if (File.Exists(fullTargetPath))
{
File.Delete(fullTargetPath);
_logger.Information("Deleted legacy startup cleanup file {TargetPath}", fullTargetPath);
+ return true;
}
+
+ return targetExists;
}
catch (Exception ex)
{
_logger.Warning(ex, "Failed to delete legacy startup cleanup path {TargetPath}", targetPath);
}
+
+ return false;
+ }
+
+ private static bool IsPathUnderAllowedRoot(string fullTargetPath, string rootPath)
+ {
+ if (string.IsNullOrWhiteSpace(rootPath))
+ return false;
+
+ var fullRootPath = Path.GetFullPath(rootPath);
+ var relativePath = Path.GetRelativePath(fullRootPath, fullTargetPath);
+
+ return relativePath != "." &&
+ !relativePath.StartsWith("..", StringComparison.Ordinal) &&
+ !Path.IsPathRooted(relativePath);
}
}