Skip to content

Commit

Permalink
Merge pull request #2985 from mrixner/delete-desktop-shortcuts
Browse files Browse the repository at this point in the history
Allow Desktop Shortcut Deletion
  • Loading branch information
marticliment authored Dec 7, 2024
2 parents 87153b0 + ed9937b commit d66ea57
Show file tree
Hide file tree
Showing 14 changed files with 591 additions and 25 deletions.
2 changes: 0 additions & 2 deletions src/UniGetUI.Core.Data.Tests/CoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ public void CheckOtherAttributes()
{
Assert.NotEmpty(CoreData.VersionName);
Assert.NotEqual(0, CoreData.BuildNumber);
Assert.True(File.Exists(CoreData.IgnoredUpdatesDatabaseFile), "The Ignored Updates database file does not exist, but it should have been created automatically.");

Assert.NotEqual(0, CoreData.UpdatesAvailableNotificationTag);

Assert.True(Directory.Exists(CoreData.UniGetUIExecutableDirectory), "Directory where the executable is located does not exist");
Expand Down
25 changes: 7 additions & 18 deletions src/UniGetUI.Core.Data/CoreData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,31 +145,20 @@ public static string UniGetUI_DefaultBackupDirectory
}
}

/// <summary>
/// The file where the screenshot metadata is stored. If the file does not exist, it will be created automatically.
/// </summary>
public static string IgnoredUpdatesDatabaseFile
{
get
{
// Calling the UniGetUIDataDirectory will create the directory if it does not exist
string file_path = Path.Join(UniGetUIDataDirectory, "IgnoredPackageUpdates.json");
if (!File.Exists(file_path))
{
File.WriteAllText(file_path, "{}");
}

return file_path;
}
}

public static bool IsDaemon;

/// <summary>
/// The ID of the notification that is used to inform the user that updates are available
/// </summary>
public const int UpdatesAvailableNotificationTag = 1234;
/// <summary>
/// The ID of the notification that is used to inform the user that UniGetUI can be updated
/// </summary>
public const int UniGetUICanBeUpdated = 1235;
/// <summary>
/// The ID of the notification that is used to inform the user that shortcuts are available for deletion
/// </summary>
public const int NewShortcutsNotificationTag = 1236;


/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ public static bool AreUpdatesNotificationsDisabled()
return AreNotificationsDisabled() || Get("DisableUpdatesNotifications");
}

/*public static bool AreShortcutsNotificationsDisabled()
{
return AreNotificationsDisabled() || Get("DisableShortcutNotifications");
}*/

public static bool AreErrorNotificationsDisabled()
{
return AreNotificationsDisabled() || Get("DisableErrorNotifications");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ public IEnumerable<Package> GetInstalledPackages_UnSafe()
foreach (var nativePackage in TaskRecycler<IEnumerable<CatalogPackage>>.RunOrAttach(GetLocalWinGetPackages, 15))
{
IManagerSource source;
if (nativePackage.DefaultInstallVersion is not null)
if (nativePackage.DefaultInstallVersion is not null && nativePackage.DefaultInstallVersion.PackageCatalog is not null)
{
source = Manager.SourcesHelper.Factory.GetSourceOrDefault(nativePackage.DefaultInstallVersion.PackageCatalog.Info.Name);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System.Collections.Concurrent;
using System.Data;
using System.Text.Json;
using Microsoft.VisualBasic.CompilerServices;
using UniGetUI.Core.Data;
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;

namespace UniGetUI.PackageEngine.Classes.Packages.Classes;

public static class DesktopShortcutsDatabase
{
public enum Status
{
Maintain, // The user has explicitly requested this shortcut not be deleted
Delete, // The user has allowed the shortcut to be deleted
Unknown, // The user has not said whether they want this shortcut to be deleted
}

private static List<string> UnknownShortcuts = [];

public static IReadOnlyDictionary<string, bool> GetDatabase()
{
return Settings.GetDictionary<string, bool>("DeletableDesktopShortcuts") ?? new Dictionary<string, bool>();
}

public static void ResetDatabase()
{
Settings.ClearDictionary("DeletableDesktopShortcuts");
}

/// <summary>
/// Adds a desktop shortcut to the deletable desktop shortcuts database
/// </summary>
/// <param name="shortcutPath">The path of the shortcut to delete</param>
/// <param name="deletable">Whether or not to mark this entry as deletable in the databse. Defaults to true</param>
public static void AddToDatabase(string shortcutPath, bool deletable = true)
{
Settings.SetDictionaryItem("DeletableDesktopShortcuts", shortcutPath, deletable);
}

/// <summary>
/// Attempts to remove the given shortcut path from the database
/// </summary>
/// <param name="shortcutPath">The path of the shortcut to delete</param>
/// <returns>True if the shortcut was removed, false if it was not there from the beginning</returns>
public static bool Remove(string shortcutPath)
{
// Remove the entry if present
if (Settings.DictionaryContainsKey<string, bool>("DeletableDesktopShortcuts", shortcutPath))
{
// Remove the entry and propagate changes to disk
Settings.SetDictionaryItem("DeletableDesktopShortcuts", shortcutPath, false);
return true;
}
else
{
// Do nothing if the entry was not there
Logger.Warn($"Attempted to remove from deletable desktop shortcuts a shortcut {{shortcutPath={shortcutPath}}} that was not found there");
return false;
}
}

/// <summary>
/// Attempts to reset the configuration of a given shortcut path from the database.
/// This will make it so the user is asked about it the next time it is discovered.
/// Different from `Remove` as Remove simply marks it as non-deletable, whereas this removes the configuration entirely.
/// </summary>
/// <param name="shortcutPath">The path of the shortcut to delete</param>
/// <returns>True if the shortcut was completely removed, false if it was not there from the beginning</returns>
public static bool ResetShortcut(string shortcutPath)
{
// Remove the entry if present
if (Settings.DictionaryContainsKey<string, bool>("DeletableDesktopShortcuts", shortcutPath))
{
// Remove the entry and propagate changes to disk
Settings.RemoveDictionaryKey<string, bool>("DeletableDesktopShortcuts", shortcutPath);
return true;
}
else
{
// Do nothing if the entry was not there
Logger.Warn($"Attempted to reset a deletable desktop shortcut {{shortcutPath={shortcutPath}}} that was not found there");
return false;
}
}

/// <summary>
/// Attempts to delete the given shortcut path off the disk
/// </summary>
/// <param name="shortcutPath">The path of the shortcut to delete</param>
/// <returns>True if the shortcut was deleted, false if it was not (or didn't exist)</returns>
public static bool DeleteFromDisk(string shortcutPath)
{
Logger.Info("Deleting shortcut " + shortcutPath);
try
{
File.Delete(shortcutPath);
return true;
}
catch (Exception e)
{
Logger.Error($"Failed to delete shortcut {{shortcutPath={shortcutPath}}}: {e.Message}");
return false;
}
}

/// <summary>
/// Checks whether a given shortcut can be deleted.
/// If a user has provided on opinion on whether or not the shortcut can be deleted, it will be returned.
/// Otherwise, `ShortcutDeletableStatus.Unknown` will be returned and the shortcut should not be deleted
/// until a choice is given to the user and they explicitly request that it be deleted.
/// </summary>
/// <param name="shortcutPath">The path of the shortcut to be deleted</param>
/// <returns>True if the package is ignored, false otherwhise</returns>
public static Status GetStatus(string shortcutPath)
{
// Check if the package is ignored
if (Settings.DictionaryContainsKey<string, bool>("DeletableDesktopShortcuts", shortcutPath))
{
bool canDelete = Settings.GetDictionaryItem<string, bool>("DeletableDesktopShortcuts", shortcutPath);
return canDelete ? Status.Delete : Status.Maintain;
}
else
{
return Status.Unknown;
}
}

/// <summary>
/// Get a list of shortcuts (.lnk files only) currently on the user's desktop
/// </summary>
/// <returns>A list of desktop shortcut paths</returns>
public static List<string> GetShortcuts()
{
List<string> shortcuts = new();
string UserDesktop = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string CommonDesktop = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
shortcuts.AddRange(Directory.EnumerateFiles(UserDesktop, "*.lnk"));
shortcuts.AddRange(Directory.EnumerateFiles(CommonDesktop, "*.lnk"));
return shortcuts;
}

/// <summary>
/// Remove a shortcut from the list of shortcuts whose deletion verdicts are unknown (as in, the user needs to be asked about deleting them when their operations finish)
/// </summary>
/// <param name="shortcutPath">The path of the shortcut to be asked about deletion</param>
/// <returns>True if it was found, false otherwise</returns>
public static bool RemoveFromUnknownShortcuts(string shortcutPath)
{
return UnknownShortcuts.Remove(shortcutPath);
}

/// <summary>
/// Get the list of shortcuts whose deletion verdicts are unknown (as in, the user needs to be asked about deleting them when their operations finish)
/// </summary>
/// <returns>The list of shortcuts awaiting verdicts</returns>
public static List<string> GetUnknownShortcuts()
{
return UnknownShortcuts;
}

/// <summary>
/// Will attempt to remove new desktop shortcuts, if applicable.
/// </summary>
/// <param name="PreviousShortCutList"></param>
public static void TryRemoveNewShortcuts(IEnumerable<string> PreviousShortCutList)
{
HashSet<string> ShortcutSet = PreviousShortCutList.ToHashSet();
List<string> CurrentShortcutList = DesktopShortcutsDatabase.GetShortcuts();
foreach (string shortcut in CurrentShortcutList)
{
if (ShortcutSet.Contains(shortcut)) continue;
switch (DesktopShortcutsDatabase.GetStatus(shortcut))
{
case Status.Delete:
DesktopShortcutsDatabase.DeleteFromDisk(shortcut);
break;
case Status.Maintain:
Logger.Debug("Refraining from deleting new shortcut " + shortcut + ": user disabled its deletion");
break;
case Status.Unknown:
if(UnknownShortcuts.Contains(shortcut)) continue;
Logger.Info("Marking the shortcut " + shortcut + " to be asked to be deleted");
UnknownShortcuts.Add(shortcut);
break;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
using UniGetUI.Core.Logging;
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.PackageEngine.Classes.Packages.Classes;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.Pages.DialogPages;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.
Expand Down Expand Up @@ -530,6 +532,11 @@ protected async Task MainThread()
break;
}

if (MainApp.Instance.OperationQueue.Count == 0 && DesktopShortcutsDatabase.GetUnknownShortcuts().Any() && Settings.Get("AskToDeleteNewDesktopShortcuts"))
{
await DialogHelper.HandleNewDesktopShortcuts();
}

List<string> rawOutput = RawProcessOutput.ToList();

rawOutput.Insert(0, " ");
Expand Down
23 changes: 23 additions & 0 deletions src/UniGetUI/Controls/OperationWidgets/PackageOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using UniGetUI.Core.SettingsEngine;
using UniGetUI.Core.Tools;
using UniGetUI.Interface.Enums;
using UniGetUI.PackageEngine.Classes.Packages.Classes;
using UniGetUI.PackageEngine.Enums;
using UniGetUI.PackageEngine.Interfaces;
using UniGetUI.PackageEngine.PackageClasses;
Expand All @@ -26,6 +27,8 @@ public OperationCanceledEventArgs(OperationStatus OldStatus)

public abstract class PackageOperation : AbstractOperation
{
protected List<string> DesktopShortcutsBeforeStart = [];

protected readonly IPackage Package;
protected readonly IInstallationOptions Options;
protected readonly OperationType Role;
Expand Down Expand Up @@ -282,6 +285,11 @@ protected override Task<AfterFinshAction> HandleSuccess()
new Dictionary<string, object?> { { "package", Package.Name } })
);

if (Settings.Get("AskToDeleteNewDesktopShortcuts"))
{
DesktopShortcutsDatabase.TryRemoveNewShortcuts(DesktopShortcutsBeforeStart);
}

return Task.FromResult(AfterFinshAction.TimeoutClose);
}

Expand All @@ -290,6 +298,11 @@ protected override async Task Initialize()
ONGOING_PROGRESS_STRING = CoreTools.Translate("{0} is being installed", Package.Name);
OperationTitle = CoreTools.Translate("{package} Installation", new Dictionary<string, object?> { { "package", Package.Name } });
IconSource = await Task.Run(Package.GetIconUrl);

if (Settings.Get("AskToDeleteNewDesktopShortcuts"))
{
DesktopShortcutsBeforeStart = DesktopShortcutsDatabase.GetShortcuts();
}
}
}

Expand Down Expand Up @@ -357,6 +370,11 @@ protected override async Task<AfterFinshAction> HandleSuccess()
new Dictionary<string, object?> { { "package", Package.Name } })
);

if (Settings.Get("AskToDeleteNewDesktopShortcuts"))
{
DesktopShortcutsDatabase.TryRemoveNewShortcuts(DesktopShortcutsBeforeStart);
}

return AfterFinshAction.TimeoutClose;
}

Expand All @@ -365,6 +383,11 @@ protected override async Task Initialize()
ONGOING_PROGRESS_STRING = CoreTools.Translate("{0} is being updated to version {1}", Package.Name, Package.NewVersion);
OperationTitle = CoreTools.Translate("{package} Update", new Dictionary<string, object?> { { "package", Package.Name } });
IconSource = await Task.Run(Package.GetIconUrl);

if (Settings.Get("AskToDeleteNewDesktopShortcuts"))
{
DesktopShortcutsBeforeStart = DesktopShortcutsDatabase.GetShortcuts();
}
}
}

Expand Down
Loading

0 comments on commit d66ea57

Please sign in to comment.