diff --git a/src/Files.App/BaseLayout.cs b/src/Files.App/BaseLayout.cs index 5bca9bfae7a1..9ff204fe1d01 100644 --- a/src/Files.App/BaseLayout.cs +++ b/src/Files.App/BaseLayout.cs @@ -218,7 +218,7 @@ internal set //if (!(value?.All(x => selectedItems?.Contains(x) ?? false) ?? value == selectedItems)) if (value != selectedItems) { - if (value?.FirstOrDefault() != selectedItems?.FirstOrDefault()) + if (value?.FirstOrDefault() != PreviewPaneViewModel.SelectedItem) { // Update preview pane properties PreviewPaneViewModel.IsItemSelected = value?.Count > 0; diff --git a/src/Files.App/DataModels/SidebarPinnedModel.cs b/src/Files.App/DataModels/SidebarPinnedModel.cs index 8ac8515cd2cf..f80ec9e3fd66 100644 --- a/src/Files.App/DataModels/SidebarPinnedModel.cs +++ b/src/Files.App/DataModels/SidebarPinnedModel.cs @@ -115,7 +115,9 @@ public async Task CreateLocationItemFromPathAsync(string path) { var iconData = await FileThumbnailHelper.LoadIconFromStorageItemAsync(res.Result, 96u, ThumbnailMode.ListView); locationItem.IconData = iconData; - locationItem.Icon = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync()); + + if (locationItem.IconData is not null) + locationItem.Icon = await App.Window.DispatcherQueue.EnqueueOrInvokeAsync(() => locationItem.IconData.ToBitmapAsync()); } if (locationItem.IconData is null) diff --git a/src/Files.App/Helpers/BitmapHelper.cs b/src/Files.App/Helpers/BitmapHelper.cs index 3422fa6e160e..253922d3b6dd 100644 --- a/src/Files.App/Helpers/BitmapHelper.cs +++ b/src/Files.App/Helpers/BitmapHelper.cs @@ -19,15 +19,22 @@ public static async Task ToBitmapAsync(this byte[]? data, int decod return null; } - using var ms = new MemoryStream(data); - var image = new BitmapImage(); - if (decodeSize > 0) + try + { + using var ms = new MemoryStream(data); + var image = new BitmapImage(); + if (decodeSize > 0) + { + image.DecodePixelWidth = decodeSize; + image.DecodePixelHeight = decodeSize; + } + await image.SetSourceAsync(ms.AsRandomAccessStream()); + return image; + } + catch (Exception) { - image.DecodePixelWidth = decodeSize; - image.DecodePixelHeight = decodeSize; + return null; } - await image.SetSourceAsync(ms.AsRandomAccessStream()); - return image; } /// diff --git a/src/Files.App/Helpers/FileOperationsHelpers.cs b/src/Files.App/Helpers/FileOperationsHelpers.cs index 4599da865a62..c96e01789ae5 100644 --- a/src/Files.App/Helpers/FileOperationsHelpers.cs +++ b/src/Files.App/Helpers/FileOperationsHelpers.cs @@ -522,7 +522,7 @@ public static void TryCancelOperation(string operationId) { Name = x.ProcessName, Pid = x.Id, - FileName = x.MainModule?.FileName, + FileName = SafetyExtensions.IgnoreExceptions(() => x.MainModule?.FileName), AppName = SafetyExtensions.IgnoreExceptions(() => x.MainModule?.FileVersionInfo?.FileDescription) }).ToList(); processes.ForEach(x => x.Dispose()); diff --git a/src/Files.App/Resources/PreviewPanePropertiesInformation.json b/src/Files.App/Resources/PreviewPanePropertiesInformation.json index 737bd95c5e6c..225415d52974 100644 --- a/src/Files.App/Resources/PreviewPanePropertiesInformation.json +++ b/src/Files.App/Resources/PreviewPanePropertiesInformation.json @@ -332,6 +332,7 @@ "SectionResource": "Document", "Property": "System.Document.TotalEditingTime", "IsReadOnly": true, + "DisplayFunctionName": "FormatDuration", "ID": null }, { diff --git a/src/Files.App/Resources/PropertiesInformation.json b/src/Files.App/Resources/PropertiesInformation.json index f617fe89efdc..1b20cdd97ee5 100644 --- a/src/Files.App/Resources/PropertiesInformation.json +++ b/src/Files.App/Resources/PropertiesInformation.json @@ -434,6 +434,7 @@ "SectionResource": "Document", "Property": "System.Document.TotalEditingTime", "IsReadOnly": true, + "DisplayFunctionName": "FormatDuration", "ID": null }, { diff --git a/src/Files.App/Shell/ContextMenu.cs b/src/Files.App/Shell/ContextMenu.cs index f1e406b63ae9..88cb0bd312d7 100644 --- a/src/Files.App/Shell/ContextMenu.cs +++ b/src/Files.App/Shell/ContextMenu.cs @@ -52,6 +52,11 @@ public async Task InvokeVerb(string? verb) if (string.IsNullOrEmpty(verb)) return false; + var item = Items.Where(x => x.CommandString == verb).FirstOrDefault(); + if (item is not null && item.ID >= 0) + // Prefer invocation by ID + return await InvokeItem(item.ID); + try { var currentWindows = Win32API.GetDesktopWindows(); @@ -76,10 +81,10 @@ public async Task InvokeVerb(string? verb) return false; } - public async Task InvokeItem(int itemID) + public async Task InvokeItem(int itemID) { if (itemID < 0) - return; + return false; try { @@ -93,13 +98,16 @@ public async Task InvokeItem(int itemID) pici.cbSize = (uint)Marshal.SizeOf(pici); await owningThread.PostMethod(() => cMenu.InvokeCommand(pici)); - Win32API.BringToForeground(currentWindows); + + return true; } catch (Exception ex) when (ex is COMException or UnauthorizedAccessException) { Debug.WriteLine(ex); } + + return false; } #region FactoryMethods diff --git a/src/Files.App/ViewModels/ItemViewModel.cs b/src/Files.App/ViewModels/ItemViewModel.cs index 5a365075abfc..394feb35568c 100644 --- a/src/Files.App/ViewModels/ItemViewModel.cs +++ b/src/Files.App/ViewModels/ItemViewModel.cs @@ -104,7 +104,7 @@ public ListedItem CurrentFolder public string WorkingDirectory { get; private set; } - private StorageFolderWithPath currentStorageFolder; + private StorageFolderWithPath? currentStorageFolder; private StorageFolderWithPath workingRoot; public delegate void WorkingDirectoryModifiedEventHandler(object sender, WorkingDirectoryModifiedEventArgs e); @@ -1400,7 +1400,7 @@ public async Task EnumerateItemsFromSpecialFolderAsync(string path) { var isFtp = FtpHelpers.IsFtpPath(path); - CurrentFolder = new ListedItem(null) + CurrentFolder = new ListedItem(null!) { PrimaryItemAttribute = StorageItemTypes.Folder, ItemPropertiesInitialized = true, @@ -1569,22 +1569,22 @@ await DialogDisplayHelper.ShowDialogAsync( if (enumFromStorageFolder) { var basicProps = await rootFolder?.GetBasicPropertiesAsync(); - var currentFolder = library ?? new ListedItem(rootFolder.FolderRelativeId) + var currentFolder = library ?? new ListedItem(rootFolder?.FolderRelativeId ?? string.Empty) { PrimaryItemAttribute = StorageItemTypes.Folder, ItemPropertiesInitialized = true, - ItemNameRaw = rootFolder.DisplayName, + ItemNameRaw = rootFolder?.DisplayName ?? string.Empty, ItemDateModifiedReal = basicProps.DateModified, - ItemType = rootFolder.DisplayType, + ItemType = rootFolder?.DisplayType ?? string.Empty, FileImage = null, LoadFileIcon = false, - ItemPath = string.IsNullOrEmpty(rootFolder.Path) ? currentStorageFolder.Path : rootFolder.Path, + ItemPath = string.IsNullOrEmpty(rootFolder?.Path) ? currentStorageFolder?.Path ?? string.Empty : rootFolder.Path, FileSize = null, FileSizeBytes = 0, }; if (library is null) - currentFolder.ItemDateCreatedReal = rootFolder.DateCreated; + currentFolder.ItemDateCreatedReal = rootFolder?.DateCreated ?? DateTimeOffset.Now; CurrentFolder = currentFolder; await EnumFromStorageFolderAsync(path, rootFolder, currentStorageFolder, cancellationToken); diff --git a/src/Files.App/ViewModels/Properties/FileProperty.cs b/src/Files.App/ViewModels/Properties/FileProperty.cs index 6fd366388b40..d655aa93d860 100644 --- a/src/Files.App/ViewModels/Properties/FileProperty.cs +++ b/src/Files.App/ViewModels/Properties/FileProperty.cs @@ -324,12 +324,15 @@ public async static Task> RetrieveAndInitializePropertiesAsyn private static readonly Dictionary> DisplayFuncs = new Dictionary>() { { "DivideBy1000", input => (((uint) input)/1000).ToString() }, - { "FormatDuration", input => new TimeSpan(Convert.ToInt64(input)).ToString("hh':'mm':'ss")}, + { "FormatDuration", input => TimeSpanToString(new TimeSpan(Convert.ToInt64(input)))}, { "Fraction" , input => ((double)input).ToFractions(2000)}, { "AddF" , input => $"f/{(double)input}"}, { "AddISO" , input => $"ISO-{(UInt16)input}"}, { "RoundDouble" , input => $"{Math.Round((double)input)}"}, { "UnitMM" , input => $"{(double)input} mm"}, }; + + private static string TimeSpanToString(TimeSpan t) + => t.Days > 0 ? (t.Days * 24 + t.Hours) + t.ToString("':'mm':'ss") : t.ToString("hh':'mm':'ss"); } } diff --git a/src/Files.Shared/Extensions/LinqExtensions.cs b/src/Files.Shared/Extensions/LinqExtensions.cs index 499e937bfc89..012537356483 100644 --- a/src/Files.Shared/Extensions/LinqExtensions.cs +++ b/src/Files.Shared/Extensions/LinqExtensions.cs @@ -22,17 +22,33 @@ public static class LinqExtensions /// public static bool IsEmpty(this IEnumerable enumerable) => enumerable is null || !enumerable.Any(); - public static TOut? Get(this IDictionary dictionary, TKey key, TOut? defaultValue = default) + public static TOut? Get(this IDictionary dictionary, TKey key, TOut? defaultValue = default) where TKey : notnull { if (dictionary is null || key is null) return defaultValue; - if (!dictionary.ContainsKey(key)) + if (dictionary is ConcurrentDictionary cDict) { - if (defaultValue is TValue value) - dictionary.Add(key, value); + if (!cDict.ContainsKey(key)) + { + if (defaultValue is TValue value) + cDict.TryAdd(key, value); - return defaultValue; + return defaultValue; + } + } + else + { + lock (dictionary) + { + if (!dictionary.ContainsKey(key)) + { + if (defaultValue is TValue value) + dictionary.Add(key, value); + + return defaultValue; + } + } } if (dictionary[key] is TOut o) @@ -46,22 +62,32 @@ public static class LinqExtensions if (dictionary is null || key is null) return defaultValueFunc(); - if (!dictionary.ContainsKey(key)) + if (dictionary is ConcurrentDictionary> cDict) { - var defaultValue = defaultValueFunc(); - if (defaultValue is Task value) + if (!cDict.ContainsKey(key)) { - if (dictionary is ConcurrentDictionary> cDict) - { + var defaultValue = defaultValueFunc(); + if (defaultValue is Task value) cDict.TryAdd(key, value); - } - else + + return defaultValue; + } + } + else + { + lock (dictionary) + { + if (!dictionary.ContainsKey(key)) { - dictionary.Add(key, value); + var defaultValue = defaultValueFunc(); + if (defaultValue is Task value) + dictionary.Add(key, value); + + return defaultValue; } } - return defaultValue; } + return dictionary[key]; }