mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 23:19:13 +08:00
[Peek]Support for special folders like Recycle Bin and My PC (#33310)
* Peek support for special folders * Renamed ThisComputer->ThisPC * Used different variable name to avoid spellcheck issues * Made label of empty fields hidden * Removed ThisPC special handling and last modified date of recycle bin
This commit is contained in:
parent
fc32fe29ba
commit
bc0811e6a1
@ -85,7 +85,7 @@
|
||||
<UsePrecompiledHeaders Condition="'$(TF_BUILD)' != ''">false</UsePrecompiledHeaders>
|
||||
|
||||
<!-- Change this to bust the cache -->
|
||||
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202310210737</MSBuildCacheCacheUniverse>
|
||||
<MSBuildCacheCacheUniverse Condition="'$(MSBuildCacheCacheUniverse)' == ''">202406130737</MSBuildCacheCacheUniverse>
|
||||
|
||||
<!--
|
||||
Visual Studio telemetry reads various ApplicationInsights.config files and other files after the project is finished, likely in a detached process.
|
||||
|
12
src/modules/peek/Peek.Common/Helpers/PathHelper.cs
Normal file
12
src/modules/peek/Peek.Common/Helpers/PathHelper.cs
Normal file
@ -0,0 +1,12 @@
|
||||
// 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;
|
||||
|
||||
namespace Peek.Common.Helpers;
|
||||
|
||||
public static class PathHelper
|
||||
{
|
||||
public static bool IsUncPath(string path) => Uri.TryCreate(path, UriKind.Absolute, out Uri? uri) && uri != null && uri.IsUnc;
|
||||
}
|
26
src/modules/peek/Peek.Common/Helpers/ThreadHelper.cs
Normal file
26
src/modules/peek/Peek.Common/Helpers/ThreadHelper.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// 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.Threading;
|
||||
|
||||
namespace Peek.Common.Helpers;
|
||||
|
||||
public static class ThreadHelper
|
||||
{
|
||||
public static void RunOnSTAThread(ThreadStart action)
|
||||
{
|
||||
if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
|
||||
{
|
||||
action();
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread staThread = new(action);
|
||||
staThread.SetApartmentState(ApartmentState.STA);
|
||||
staThread.Start();
|
||||
staThread.Join();
|
||||
}
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ namespace Peek.Common.Models
|
||||
|
||||
public string Name { get; init; }
|
||||
|
||||
public string ParsingName => string.Empty;
|
||||
|
||||
public string Path { get; init; }
|
||||
|
||||
public string Extension => System.IO.Path.GetExtension(Path).ToLower(CultureInfo.InvariantCulture);
|
||||
|
@ -11,19 +11,15 @@ using Windows.Storage;
|
||||
|
||||
namespace Peek.Common.Models
|
||||
{
|
||||
public class FolderItem : IFileSystemItem
|
||||
public class FolderItem(string path, string name, string parsingName) : IFileSystemItem
|
||||
{
|
||||
private StorageFolder? storageFolder;
|
||||
|
||||
public FolderItem(string path, string name)
|
||||
{
|
||||
Path = path;
|
||||
Name = name;
|
||||
}
|
||||
public string Name { get; init; } = name;
|
||||
|
||||
public string Name { get; init; }
|
||||
public string ParsingName { get; init; } = parsingName;
|
||||
|
||||
public string Path { get; init; }
|
||||
public string Path { get; init; } = path;
|
||||
|
||||
public string Extension => string.Empty;
|
||||
|
||||
@ -38,7 +34,7 @@ namespace Peek.Common.Models
|
||||
{
|
||||
try
|
||||
{
|
||||
storageFolder = await StorageFolder.GetFolderFromPathAsync(Path);
|
||||
storageFolder = string.IsNullOrEmpty(Path) ? null : await StorageFolder.GetFolderFromPathAsync(Path);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -17,25 +17,24 @@ namespace Peek.Common.Models
|
||||
{
|
||||
get
|
||||
{
|
||||
DateTime? dateModified;
|
||||
try
|
||||
{
|
||||
dateModified = System.IO.File.GetCreationTime(Path);
|
||||
return string.IsNullOrEmpty(Path) ? null : System.IO.File.GetCreationTime(Path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
dateModified = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
return dateModified;
|
||||
}
|
||||
}
|
||||
|
||||
public string Extension { get; }
|
||||
|
||||
public string Name { get; init; }
|
||||
public string Name { get; }
|
||||
|
||||
public string Path { get; init; }
|
||||
public string ParsingName { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public ulong FileSizeBytes => PropertyStoreHelper.TryGetUlongProperty(Path, PropertyKey.FileSizeBytes) ?? 0;
|
||||
|
||||
|
@ -0,0 +1,57 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.FilePreviewer.Controls.SpecialFolderInformationalPreviewControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid ColumnSpacing="24">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition x:Name="Icon" Width="Auto" />
|
||||
<ColumnDefinition x:Name="FileInfo" Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Image
|
||||
x:Name="FileIcon"
|
||||
Grid.Column="0"
|
||||
Width="180"
|
||||
Height="180"
|
||||
Source="{x:Bind Source.IconPreview, Mode=OneWay}" />
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="4">
|
||||
|
||||
<TextBlock
|
||||
MaxLines="3"
|
||||
Style="{StaticResource TitleTextBlockStyle}"
|
||||
Text="{x:Bind Source.FileName, Mode=OneWay}"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
TextWrapping="Wrap">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind Source.FileName, Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{x:Bind FormatFileType(Source.FileType), Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind FormatFileType(Source.FileType), Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{x:Bind FormatFileSize(Source.FileSize), Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind FormatFileSize(Source.FileSize), Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
<TextBlock Text="{x:Bind FormatFileDateModified(Source.DateModified), Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{x:Bind FormatFileDateModified(Source.DateModified), Mode=OneWay}" />
|
||||
</ToolTipService.ToolTip>
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -0,0 +1,41 @@
|
||||
// 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 Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Peek.Common.Helpers;
|
||||
using Peek.FilePreviewer.Models;
|
||||
|
||||
namespace Peek.FilePreviewer.Controls;
|
||||
|
||||
public sealed partial class SpecialFolderInformationalPreviewControl : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
nameof(Source),
|
||||
typeof(SpecialFolderPreviewData),
|
||||
typeof(SpecialFolderInformationalPreviewControl),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public SpecialFolderPreviewData? Source
|
||||
{
|
||||
get { return (SpecialFolderPreviewData)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public SpecialFolderInformationalPreviewControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string FormatFileType(string? fileType) => FormatField("UnsupportedFile_FileType", fileType);
|
||||
|
||||
public string FormatFileSize(string? fileSize) => FormatField("UnsupportedFile_FileSize", fileSize);
|
||||
|
||||
public string FormatFileDateModified(string? fileDateModified) => FormatField("UnsupportedFile_DateModified", fileDateModified);
|
||||
|
||||
private static string FormatField(string resourceId, string? fieldValue)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(fieldValue) ? string.Empty : ReadableStringHelper.FormatResourceString(resourceId, fieldValue);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
|
||||
<!-- Licensed under the MIT License. -->
|
||||
|
||||
<UserControl
|
||||
x:Class="Peek.FilePreviewer.Controls.SpecialFolderPreview"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:Peek.FilePreviewer.Controls"
|
||||
xmlns:conv="using:Peek.Common.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:prev="using:Peek.FilePreviewer.Previewers"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid
|
||||
Margin="24"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<controls:SpecialFolderInformationalPreviewControl
|
||||
x:Name="InformationPreview"
|
||||
Source="{x:Bind Source, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsVisibleIfStatesMatch(LoadingState, prev:PreviewState.Loaded), Mode=OneWay}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -0,0 +1,47 @@
|
||||
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Peek.Common.Converters;
|
||||
using Peek.FilePreviewer.Models;
|
||||
using Peek.FilePreviewer.Previewers;
|
||||
|
||||
namespace Peek.FilePreviewer.Controls;
|
||||
|
||||
[INotifyPropertyChanged]
|
||||
public sealed partial class SpecialFolderPreview : UserControl
|
||||
{
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
nameof(Source),
|
||||
typeof(SpecialFolderPreviewData),
|
||||
typeof(SpecialFolderPreview),
|
||||
new PropertyMetadata(null));
|
||||
|
||||
public static readonly DependencyProperty LoadingStateProperty = DependencyProperty.Register(
|
||||
nameof(LoadingState),
|
||||
typeof(PreviewState),
|
||||
typeof(SpecialFolderPreview),
|
||||
new PropertyMetadata(PreviewState.Uninitialized));
|
||||
|
||||
public SpecialFolderPreviewData? Source
|
||||
{
|
||||
get { return (SpecialFolderPreviewData)GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
}
|
||||
|
||||
public PreviewState? LoadingState
|
||||
{
|
||||
get { return (PreviewState)GetValue(LoadingStateProperty); }
|
||||
set { SetValue(LoadingStateProperty, value); }
|
||||
}
|
||||
|
||||
public SpecialFolderPreview()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public Visibility IsVisibleIfStatesMatch(PreviewState? a, PreviewState? b) => VisibilityConverter.Convert(a == b);
|
||||
}
|
@ -83,6 +83,12 @@
|
||||
Source="{x:Bind DrivePreviewer.Preview, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPreviewVisible(DrivePreviewer, Previewer.State), Mode=OneWay}" />
|
||||
|
||||
<controls:SpecialFolderPreview
|
||||
x:Name="SpecialFolderPreview"
|
||||
LoadingState="{x:Bind SpecialFolderPreviewer.State, Mode=OneWay}"
|
||||
Source="{x:Bind SpecialFolderPreviewer.Preview, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsPreviewVisible(SpecialFolderPreviewer, Previewer.State), Mode=OneWay}" />
|
||||
|
||||
<controls:UnsupportedFilePreview
|
||||
x:Name="UnsupportedFilePreview"
|
||||
LoadingState="{x:Bind UnsupportedFilePreviewer.State, Mode=OneWay}"
|
||||
|
@ -53,6 +53,7 @@ namespace Peek.FilePreviewer
|
||||
[NotifyPropertyChangedFor(nameof(ArchivePreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(ShellPreviewHandlerPreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(DrivePreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(SpecialFolderPreviewer))]
|
||||
[NotifyPropertyChangedFor(nameof(UnsupportedFilePreviewer))]
|
||||
private IPreviewer? previewer;
|
||||
|
||||
@ -105,6 +106,8 @@ namespace Peek.FilePreviewer
|
||||
|
||||
public IDrivePreviewer? DrivePreviewer => Previewer as IDrivePreviewer;
|
||||
|
||||
public ISpecialFolderPreviewer? SpecialFolderPreviewer => Previewer as ISpecialFolderPreviewer;
|
||||
|
||||
public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer;
|
||||
|
||||
public IFileSystemItem Item
|
||||
|
@ -0,0 +1,26 @@
|
||||
// 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 CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
|
||||
namespace Peek.FilePreviewer.Models;
|
||||
|
||||
public partial class SpecialFolderPreviewData : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private ImageSource? iconPreview;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileName;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileType;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? fileSize;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? dateModified;
|
||||
}
|
@ -21,6 +21,8 @@
|
||||
<None Remove="Controls\BrowserControl.xaml" />
|
||||
<None Remove="Controls\DriveControl.xaml" />
|
||||
<None Remove="Controls\ShellPreviewHandlerControl.xaml" />
|
||||
<None Remove="Controls\SpecialFolderPreview\SpecialFolderInformationalPreviewControl.xaml" />
|
||||
<None Remove="Controls\SpecialFolderPreview\SpecialFolderPreview.xaml" />
|
||||
<None Remove="Controls\UnsupportedFilePreview\FailedFallbackPreviewControl.xaml" />
|
||||
<None Remove="Controls\UnsupportedFilePreview\InformationalPreviewControl.xaml" />
|
||||
<None Remove="FilePreview.xaml" />
|
||||
@ -42,6 +44,27 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<COMReference Include="Shell32">
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>50a7e9b0-70ef-11d1-b75a-00a0c90564fe</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="SHDocVw">
|
||||
<VersionMinor>1</VersionMinor>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<Guid>eab22ac0-30c1-11cf-a7eb-0000c05bae0b</Guid>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>false</Isolated>
|
||||
<EmbedInteropTypes>true</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\Common.UI\Common.UI.csproj" />
|
||||
<ProjectReference Include="..\..\..\common\FilePreviewCommon\FilePreviewCommon.csproj" />
|
||||
@ -71,6 +94,9 @@
|
||||
<Page Update="Controls\ArchiveControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
<Page Update="Controls\SpecialFolderPreview\SpecialFolderInformationalPreviewControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -0,0 +1,12 @@
|
||||
// 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 Peek.FilePreviewer.Models;
|
||||
|
||||
namespace Peek.FilePreviewer.Previewers;
|
||||
|
||||
public interface ISpecialFolderPreviewer : IPreviewer
|
||||
{
|
||||
public SpecialFolderPreviewData? Preview { get; }
|
||||
}
|
@ -53,6 +53,10 @@ namespace Peek.FilePreviewer.Previewers
|
||||
{
|
||||
return new DrivePreviewer(item);
|
||||
}
|
||||
else if (SpecialFolderPreviewer.IsItemSupported(item))
|
||||
{
|
||||
return new SpecialFolderPreviewer(item);
|
||||
}
|
||||
|
||||
// Other previewer types check their supported file types here
|
||||
return CreateDefaultPreviewer(item);
|
||||
|
@ -0,0 +1,58 @@
|
||||
// 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.Linq;
|
||||
using Peek.Common;
|
||||
using Peek.Common.Models;
|
||||
|
||||
namespace Peek.FilePreviewer.Previewers;
|
||||
|
||||
public enum KnownSpecialFolder
|
||||
{
|
||||
None,
|
||||
RecycleBin,
|
||||
}
|
||||
|
||||
public static class KnownSpecialFolders
|
||||
{
|
||||
private static readonly Lazy<IReadOnlyDictionary<string, KnownSpecialFolder>> FoldersByParsingNameDict = new(GetFoldersByParsingName);
|
||||
|
||||
public static IReadOnlyDictionary<string, KnownSpecialFolder> FoldersByParsingName => FoldersByParsingNameDict.Value;
|
||||
|
||||
private static Dictionary<string, KnownSpecialFolder> GetFoldersByParsingName()
|
||||
{
|
||||
var folders = new (KnownSpecialFolder Folder, string? ParsingName)[]
|
||||
{
|
||||
(KnownSpecialFolder.RecycleBin, GetParsingName("shell:RecycleBinFolder")),
|
||||
};
|
||||
|
||||
return folders.Where(folder => !string.IsNullOrEmpty(folder.ParsingName))
|
||||
.ToDictionary(folder => folder.ParsingName!, folder => folder.Folder);
|
||||
}
|
||||
|
||||
private static string? GetParsingName(string shellName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return CreateShellItemFromShellName(shellName)?.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_DESKTOPABSOLUTEPARSING);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static IShellItem? CreateShellItemFromShellName(string shellName)
|
||||
{
|
||||
// Based on https://stackoverflow.com/a/42966899
|
||||
const string ShellItem = "43826d1e-e718-42ee-bc55-a1e261c37bfe";
|
||||
|
||||
Guid shellItem2Guid = new(ShellItem);
|
||||
int retCode = NativeMethods.SHCreateItemFromParsingName(shellName, IntPtr.Zero, ref shellItem2Guid, out IShellItem? nativeShellItem);
|
||||
|
||||
return retCode == 0 ? nativeShellItem : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
// 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 IconHelper.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();
|
||||
}
|
||||
}
|
@ -45,8 +45,6 @@ namespace Peek.FilePreviewer.Previewers
|
||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||
}
|
||||
|
||||
public bool IsPreviewLoaded => Preview.IconPreview != null;
|
||||
|
||||
private IFileSystemItem Item { get; }
|
||||
|
||||
private DispatcherQueue Dispatcher { get; }
|
||||
|
@ -7,8 +7,8 @@ using System.IO;
|
||||
using ManagedCommon;
|
||||
using Peek.Common.Models;
|
||||
|
||||
namespace Peek.UI.Extensions
|
||||
{
|
||||
namespace Peek.UI.Extensions;
|
||||
|
||||
public static class IShellItemExtensions
|
||||
{
|
||||
public static IFileSystemItem ToIFileSystemItem(this IShellItem shellItem)
|
||||
@ -16,40 +16,32 @@ namespace Peek.UI.Extensions
|
||||
string path = shellItem.GetPath();
|
||||
string name = shellItem.GetName();
|
||||
|
||||
return File.Exists(path) ? new FileItem(path, name) : new FolderItem(path, name);
|
||||
return File.Exists(path) ? new FileItem(path, name) : new FolderItem(path, name, shellItem.GetParsingName());
|
||||
}
|
||||
|
||||
private static string GetPath(this IShellItem shellItem)
|
||||
private static string GetPath(this IShellItem shellItem) =>
|
||||
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH, logError: false);
|
||||
|
||||
private static string GetName(this IShellItem shellItem) =>
|
||||
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_NORMALDISPLAY, logError: true);
|
||||
|
||||
private static string GetParsingName(this IShellItem shellItem) =>
|
||||
shellItem.GetNameCore(Windows.Win32.UI.Shell.SIGDN.SIGDN_DESKTOPABSOLUTEPARSING, logError: true);
|
||||
|
||||
private static string GetNameCore(this IShellItem shellItem, Windows.Win32.UI.Shell.SIGDN displayNameType, bool logError)
|
||||
{
|
||||
string path = string.Empty;
|
||||
try
|
||||
{
|
||||
path = shellItem.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_FILESYSPATH);
|
||||
return shellItem.GetDisplayName(displayNameType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// TODO: Handle cases that do not have a file system path like Recycle Bin.
|
||||
path = string.Empty;
|
||||
Logger.LogError("Getting path failed. " + ex.Message);
|
||||
if (logError)
|
||||
{
|
||||
Logger.LogError($"Getting {Enum.GetName(displayNameType)} failed. {ex.Message}");
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private static string GetName(this IShellItem shellItem)
|
||||
{
|
||||
string name = string.Empty;
|
||||
try
|
||||
{
|
||||
name = shellItem.GetDisplayName(Windows.Win32.UI.Shell.SIGDN.SIGDN_NORMALDISPLAY);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
name = string.Empty;
|
||||
Logger.LogError("Getting path failed. " + ex.Message);
|
||||
}
|
||||
|
||||
return name;
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user