Files
PowerToys/src/modules/peek/Peek.FilePreviewer/Previewers/SpecialFolderPreviewer/SpecialFolderPreviewer.cs
Dave Rayment 360b6d0ccf [Peek] Fix thumbnails being created and not used. Fix icon bitmaps leaking memory. Simplify ImagePreviewer. (#34544)
Consolidated IconHelper and ThumbnailHelper. Fixed icon memory leak. Fixed ImagePreviewer thumbnails being created and then not used. Refactored ImagePreviewer.
2024-09-23 17:00:34 +01:00

217 lines
6.8 KiB
C#

// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using ManagedCommon;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Imaging;
using Peek.Common.Extensions;
using Peek.Common.Helpers;
using Peek.Common.Models;
using Peek.FilePreviewer.Models;
using Peek.FilePreviewer.Previewers.Helpers;
using Windows.Foundation;
namespace Peek.FilePreviewer.Previewers;
public partial class SpecialFolderPreviewer : ObservableObject, ISpecialFolderPreviewer, IDisposable
{
private readonly DispatcherTimer _syncDetailsDispatcherTimer = new();
private ulong _folderSize;
private DateTime? _dateModified;
[ObservableProperty]
private SpecialFolderPreviewData preview = new();
[ObservableProperty]
private PreviewState state;
public SpecialFolderPreviewer(IFileSystemItem file)
{
_syncDetailsDispatcherTimer.Interval = TimeSpan.FromMilliseconds(500);
_syncDetailsDispatcherTimer.Tick += DetailsDispatcherTimer_Tick;
Item = file;
Preview.FileName = file.Name;
Dispatcher = DispatcherQueue.GetForCurrentThread();
}
public static bool IsItemSupported(IFileSystemItem item)
{
// Always allow know special folders.
bool isKnownSpecialFolder = KnownSpecialFolders.FoldersByParsingName.ContainsKey(item.ParsingName);
// Allow empty paths unless Unc; icons don't load correctly for Unc paths.
bool isEmptyNonUncPath = string.IsNullOrEmpty(item.Path) && !PathHelper.IsUncPath(item.ParsingName);
return isKnownSpecialFolder || isEmptyNonUncPath;
}
private IFileSystemItem Item { get; }
private DispatcherQueue Dispatcher { get; }
public void Dispose()
{
_syncDetailsDispatcherTimer.Tick -= DetailsDispatcherTimer_Tick;
GC.SuppressFinalize(this);
}
public Task<PreviewSize> GetPreviewSizeAsync(CancellationToken cancellationToken)
{
Size? size = new(680, 500);
var previewSize = new PreviewSize { MonitorSize = size, UseEffectivePixels = true };
return Task.FromResult(previewSize);
}
public async Task LoadPreviewAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
State = PreviewState.Loading;
var tasks = await Task.WhenAll(LoadIconPreviewAsync(cancellationToken), LoadDisplayInfoAsync(cancellationToken));
State = tasks.All(task => task) ? PreviewState.Loaded : PreviewState.Error;
}
public async Task CopyAsync()
{
await Dispatcher.RunOnUiThread(async () =>
{
var storageItem = await Item.GetStorageItemAsync();
ClipboardHelper.SaveToClipboard(storageItem);
});
}
public async Task<bool> LoadIconPreviewAsync(CancellationToken cancellationToken)
{
bool isIconValid = false;
var isTaskSuccessful = await TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
await Dispatcher.RunOnUiThread(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
var iconBitmap = await ThumbnailHelper.GetIconAsync(Item.ParsingName, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
isIconValid = iconBitmap != null;
Preview.IconPreview = iconBitmap ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
});
});
return isIconValid && isTaskSuccessful;
}
public async Task<bool> LoadDisplayInfoAsync(CancellationToken cancellationToken)
{
bool isDisplayValid = false;
var isTaskSuccessful = await TaskExtension.RunSafe(async () =>
{
cancellationToken.ThrowIfCancellationRequested();
var fileType = await Task.Run(Item.GetContentTypeAsync);
cancellationToken.ThrowIfCancellationRequested();
isDisplayValid = fileType != null;
await Dispatcher.RunOnUiThread(() =>
{
Preview.FileType = fileType;
return Task.CompletedTask;
});
RunUpdateDetailsWorkflow(cancellationToken);
});
return isDisplayValid && isTaskSuccessful;
}
private void RunUpdateDetailsWorkflow(CancellationToken cancellationToken)
{
Task.Run(
async () =>
{
try
{
await Dispatcher.RunOnUiThread(_syncDetailsDispatcherTimer.Start);
ComputeDetails(cancellationToken);
}
catch (OperationCanceledException)
{
}
catch (Exception ex)
{
Logger.LogError("Failed to update special folder details", ex);
}
finally
{
await Dispatcher.RunOnUiThread(_syncDetailsDispatcherTimer.Stop);
}
await Dispatcher.RunOnUiThread(SyncDetails);
},
cancellationToken);
}
private void ComputeDetails(CancellationToken cancellationToken)
{
_dateModified = Item.DateModified;
switch (KnownSpecialFolders.FoldersByParsingName.GetValueOrDefault(Item.ParsingName, KnownSpecialFolder.None))
{
case KnownSpecialFolder.None:
break;
case KnownSpecialFolder.RecycleBin:
ThreadHelper.RunOnSTAThread(() => { ComputeRecycleBinDetails(cancellationToken); });
cancellationToken.ThrowIfCancellationRequested();
break;
}
}
private void ComputeRecycleBinDetails(CancellationToken cancellationToken)
{
var shell = new Shell32.Shell();
var recycleBin = shell.NameSpace(10); // CSIDL_BITBUCKET
foreach (dynamic item in recycleBin.Items())
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
_folderSize += Convert.ToUInt64(item.Size);
}
}
private void SyncDetails()
{
Preview.FileSize = _folderSize == 0 ? string.Empty : ReadableStringHelper.BytesToReadableString(_folderSize);
Preview.DateModified = _dateModified?.ToString(CultureInfo.CurrentCulture) ?? string.Empty;
}
private void DetailsDispatcherTimer_Tick(object? sender, object e)
{
SyncDetails();
}
}