Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow Desktop Shortcut Deletion #2985

Merged
merged 29 commits into from
Dec 7, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
791be5e
Add the deletable desktop shortcuts database
mrixner Nov 16, 2024
a57bffb
Add a function to get a list of shortcuts on the desktop
mrixner Nov 16, 2024
7781e73
Add the 'ask to delete new desktop shortcuts' setting
mrixner Nov 16, 2024
918cb9a
Fix formatting
mrixner Nov 23, 2024
acd0dc4
Avoid random System.NullReferenceException: 'Object reference not set…
mrixner Nov 24, 2024
15270d1
Add the actual shortcut deletion functionality
mrixner Nov 24, 2024
7c2693b
Change the name of the new desktop shortcuts listview
mrixner Nov 24, 2024
8eb40ec
Add a ResetDatabase function to the desktop shortcuts database
mrixner Nov 24, 2024
237952d
Add a `Reset` option to the shortcuts database
mrixner Nov 24, 2024
ca0d34c
Fix formatting
mrixner Nov 25, 2024
d0b9c7e
Allow shortcuts to be added as undeletable
mrixner Nov 25, 2024
5fb3583
Remove unneeded Grid.Column property
mrixner Nov 25, 2024
5473d2f
Allow for the management of existing desktop shortcut rules
mrixner Nov 25, 2024
1b8624c
Add notifications when a new desktop shortcut is detected
mrixner Nov 25, 2024
ba6ac6d
Merge branch main into delete-desktop-shortcuts
mrixner Nov 25, 2024
9d81299
Merge branch 'main' into delete-desktop-shortcuts
marticliment Nov 26, 2024
dfbacaa
Revert column definitions
mrixner Nov 26, 2024
6249ab4
Merge branch 'main' into delete-desktop-shortcuts
mrixner Dec 1, 2024
fdca728
Port the deletable desktop shortcuts database to the new complex sett…
mrixner Dec 1, 2024
ef83ac3
Delete desktop shortcuts on installation as well
mrixner Dec 1, 2024
35032dc
Revert commit that will be fixed by marticliment later (and better).
mrixner Dec 1, 2024
cd63431
Remove now unused CoreData.IgnoredUpdatesDatabaseFile
marticliment Dec 3, 2024
e8dd9d1
Merge branch 'main' into pr/2985
marticliment Dec 3, 2024
a665bee
Run shortcut manager on install operation also, move code block to De…
marticliment Dec 3, 2024
9e4ad52
Simplify some methods names, minor code changes
marticliment Dec 3, 2024
1232867
Fuse ShortcutManager and NewShortcutManager into one single dialog, A…
marticliment Dec 3, 2024
3342ede
Minor improvements
marticliment Dec 3, 2024
d46f3e2
fix duplicated icon
marticliment Dec 7, 2024
ed9937b
Merge branch 'main' into delete-desktop-shortcuts
marticliment Dec 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
marticliment marked this conversation as resolved.
Show resolved Hide resolved
{
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"))
marticliment marked this conversation as resolved.
Show resolved Hide resolved
{
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
Loading