diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 24ff597723df..a3d30fb97a7e 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1087,6 +1087,7 @@ LIGHTORANGE LIGHTTURQUOISE lindex linkedin +LINKOVERLAY linq LINQTo listview @@ -1415,6 +1416,7 @@ nullonfailure numberbox NUMLOCK numpad +nupkg nwc Objbase OBJID @@ -1613,6 +1615,7 @@ psapi pscid PSECURITY psfgao +psfi Psr psrm psrree @@ -1851,6 +1854,7 @@ shellscalingapi SHFILEINFO SHGDNF SHGFI +shinfo Shl shldisp shlobj @@ -2056,6 +2060,7 @@ TEXCOORD textblock TEXTEXTRACTOR TEXTINCLUDE +tgz themeresources THH THICKFRAME @@ -2123,6 +2128,7 @@ ULONGLONG unapply unassign uncompilable +Uncompress UNCPRIORITY UNDNAME UNICODETEXT diff --git a/Directory.Packages.props b/Directory.Packages.props index ba9fd599160a..83bddb483595 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -45,6 +45,7 @@ + diff --git a/NOTICE.md b/NOTICE.md index fa87dd90ec0c..7ee76b5aa347 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -318,6 +318,7 @@ SOFTWARE. - NLog.Extensions.Logging 5.0.4 - NLog.Schema 5.0.4 - ScipBe.Common.Office.OneNote 3.0.1 +- SharpCompress 0.33.0 - StreamJsonRpc 2.14.24 - StyleCop.Analyzers 1.2.0-beta.435 - System.CommandLine 2.0.0-beta4.22272.1 diff --git a/src/modules/peek/Peek.Common/Converters/BytesToStringConverter.cs b/src/modules/peek/Peek.Common/Converters/BytesToStringConverter.cs new file mode 100644 index 000000000000..fa40547daf5d --- /dev/null +++ b/src/modules/peek/Peek.Common/Converters/BytesToStringConverter.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.UI.Xaml.Data; +using Peek.Common.Helpers; + +namespace Peek.Common.Converters +{ + public class BytesToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + if (value is ulong size) + { + return ReadableStringHelper.BytesToReadableString(size); + } + else + { + return value; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException(); + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/ArchiveControl.xaml b/src/modules/peek/Peek.FilePreviewer/Controls/ArchiveControl.xaml new file mode 100644 index 000000000000..346d0e82c851 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Controls/ArchiveControl.xaml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/peek/Peek.FilePreviewer/Controls/ArchiveControl.xaml.cs b/src/modules/peek/Peek.FilePreviewer/Controls/ArchiveControl.xaml.cs new file mode 100644 index 000000000000..591f9af7a477 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Controls/ArchiveControl.xaml.cs @@ -0,0 +1,81 @@ +// 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.Collections.ObjectModel; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Peek.FilePreviewer.Previewers; +using Peek.FilePreviewer.Previewers.Archives; +using Peek.FilePreviewer.Previewers.Archives.Models; + +namespace Peek.FilePreviewer.Controls +{ + public sealed partial class ArchiveControl : UserControl + { + public static readonly DependencyProperty SourceProperty = DependencyProperty.Register( + nameof(Source), + typeof(ObservableCollection), + typeof(ArchivePreviewer), + new PropertyMetadata(null)); + + public static readonly DependencyProperty LoadingStateProperty = DependencyProperty.Register( + nameof(LoadingState), + typeof(PreviewState), + typeof(ArchivePreviewer), + new PropertyMetadata(PreviewState.Uninitialized)); + + public static readonly DependencyProperty DirectoryCountProperty = DependencyProperty.Register( + nameof(DirectoryCount), + typeof(string), + typeof(ArchivePreviewer), + new PropertyMetadata(null)); + + public static readonly DependencyProperty FileCountProperty = DependencyProperty.Register( + nameof(FileCount), + typeof(string), + typeof(ArchivePreviewer), + new PropertyMetadata(null)); + + public static readonly DependencyProperty SizeProperty = DependencyProperty.Register( + nameof(Size), + typeof(string), + typeof(ArchivePreviewer), + new PropertyMetadata(null)); + + public ObservableCollection? Source + { + get { return (ObservableCollection)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + + public PreviewState? LoadingState + { + get { return (PreviewState)GetValue(LoadingStateProperty); } + set { SetValue(LoadingStateProperty, value); } + } + + public string? DirectoryCount + { + get { return (string)GetValue(DirectoryCountProperty); } + set { SetValue(DirectoryCountProperty, value); } + } + + public string? FileCount + { + get { return (string)GetValue(FileCountProperty); } + set { SetValue(FileCountProperty, value); } + } + + public string? Size + { + get { return (string)GetValue(SizeProperty); } + set { SetValue(SizeProperty, value); } + } + + public ArchiveControl() + { + this.InitializeComponent(); + } + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml index f5035fab7f54..9d1539cdd944 100644 --- a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml +++ b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml @@ -1,4 +1,4 @@ - + + + Previewer as IBrowserPreviewer; + public IArchivePreviewer? ArchivePreviewer => Previewer as IArchivePreviewer; + public IUnsupportedFilePreviewer? UnsupportedFilePreviewer => Previewer as IUnsupportedFilePreviewer; public IFileSystemItem Item @@ -145,6 +148,7 @@ private async Task OnItemPropertyChanged() ImagePreview.Visibility = Visibility.Collapsed; VideoPreview.Visibility = Visibility.Collapsed; BrowserPreview.Visibility = Visibility.Collapsed; + ArchivePreview.Visibility = Visibility.Collapsed; UnsupportedFilePreview.Visibility = Visibility.Collapsed; return; } @@ -207,6 +211,7 @@ partial void OnPreviewerChanging(IPreviewer? value) VideoPreview.Source = null; ImagePreview.Source = null; + ArchivePreview.Source = null; BrowserPreview.Source = null; if (Previewer != null) diff --git a/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj b/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj index 4b351beb7b50..aee052e58dd6 100644 --- a/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj +++ b/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj @@ -14,6 +14,7 @@ + @@ -25,6 +26,7 @@ + @@ -35,6 +37,12 @@ + + + MSBuild:Compile + + + MSBuild:Compile diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/ArchivePreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/ArchivePreviewer.cs new file mode 100644 index 000000000000..ccf8987b8205 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/ArchivePreviewer.cs @@ -0,0 +1,216 @@ +// 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.Collections.ObjectModel; +using System.Data; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.UI.Dispatching; +using Peek.Common.Extensions; +using Peek.Common.Helpers; +using Peek.Common.Models; +using Peek.FilePreviewer.Models; +using Peek.FilePreviewer.Previewers.Archives.Helpers; +using Peek.FilePreviewer.Previewers.Archives.Models; +using Peek.FilePreviewer.Previewers.Interfaces; +using SharpCompress.Archives; +using SharpCompress.Common; +using SharpCompress.Readers; +using Windows.ApplicationModel.Resources; + +namespace Peek.FilePreviewer.Previewers.Archives +{ + public partial class ArchivePreviewer : ObservableObject, IArchivePreviewer + { + private readonly IconCache _iconCache = new(); + private int _directoryCount; + private int _fileCount; + private ulong _size; + private ulong _extractedSize; + + [ObservableProperty] + private PreviewState state; + + [ObservableProperty] + private string? _directoryCountText; + + [ObservableProperty] + private string? _fileCountText; + + [ObservableProperty] + private string? _sizeText; + + private IFileSystemItem Item { get; } + + private DispatcherQueue Dispatcher { get; } + + public ObservableCollection Tree { get; } + + public ArchivePreviewer(IFileSystemItem file) + { + Item = file; + Dispatcher = DispatcherQueue.GetForCurrentThread(); + Tree = new ObservableCollection(); + } + + public async Task CopyAsync() + { + await Dispatcher.RunOnUiThread(async () => + { + var storageItem = await Item.GetStorageItemAsync(); + ClipboardHelper.SaveToClipboard(storageItem); + }); + } + + public Task GetPreviewSizeAsync(CancellationToken cancellationToken) + { + return Task.FromResult(new PreviewSize { MonitorSize = null }); + } + + public async Task LoadPreviewAsync(CancellationToken cancellationToken) + { + State = PreviewState.Loading; + using var stream = File.OpenRead(Item.Path); + + if (Item.Path.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase) || Item.Path.EndsWith(".tgz", StringComparison.OrdinalIgnoreCase)) + { + using var archive = ArchiveFactory.Open(stream); + _extractedSize = (ulong)archive.TotalUncompressSize; + stream.Seek(0, SeekOrigin.Begin); + + using var reader = ReaderFactory.Open(stream); + while (reader.MoveToNextEntry()) + { + cancellationToken.ThrowIfCancellationRequested(); + await AddEntryAsync(reader.Entry, cancellationToken); + } + } + else + { + using var archive = ArchiveFactory.Open(stream); + _extractedSize = (ulong)archive.TotalUncompressSize; + + foreach (var entry in archive.Entries) + { + cancellationToken.ThrowIfCancellationRequested(); + await AddEntryAsync(entry, cancellationToken); + } + } + + _size = (ulong)new FileInfo(Item.Path).Length; // archive.TotalSize isn't accurate + DirectoryCountText = string.Format(CultureInfo.CurrentCulture, ResourceLoader.GetForViewIndependentUse().GetString("Archive_Directory_Count"), _directoryCount); + FileCountText = string.Format(CultureInfo.CurrentCulture, ResourceLoader.GetForViewIndependentUse().GetString("Archive_File_Count"), _fileCount); + SizeText = string.Format(CultureInfo.CurrentCulture, ResourceLoader.GetForViewIndependentUse().GetString("Archive_Size"), ReadableStringHelper.BytesToReadableString(_size), ReadableStringHelper.BytesToReadableString(_extractedSize)); + + State = PreviewState.Loaded; + } + + public static bool IsFileTypeSupported(string fileExt) + { + return _supportedFileTypes.Contains(fileExt); + } + + public void Dispose() + { + GC.SuppressFinalize(this); + } + + private async Task AddEntryAsync(IEntry entry, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(entry, nameof(entry)); + + var levels = entry!.Key + .Split('/', '\\') + .Where(l => !string.IsNullOrWhiteSpace(l)) + .ToArray(); + + ArchiveItem? parent = null; + for (var i = 0; i < levels.Length; i++) + { + var type = (!entry.IsDirectory && i == levels.Length - 1) ? ArchiveItemType.File : ArchiveItemType.Directory; + + var icon = type == ArchiveItemType.Directory + ? await _iconCache.GetDirectoryIconAsync(cancellationToken) + : await _iconCache.GetFileExtIconAsync(entry.Key, cancellationToken); + + var item = new ArchiveItem(levels[i], type, icon); + + if (type == ArchiveItemType.Directory) + { + item.IsExpanded = parent == null; // Only the root level is expanded + } + else if (type == ArchiveItemType.File) + { + item.Size = (ulong)entry.Size; + } + + if (parent == null) + { + var existing = Tree.FirstOrDefault(e => e.Name == item.Name); + if (existing == null) + { + var index = GetIndex(Tree, item); + Tree.Insert(index, item); + CountItem(item); + } + + parent = existing ?? Tree.First(e => e.Name == item.Name); + } + else + { + var existing = parent.Children.FirstOrDefault(e => e.Name == item.Name); + if (existing == null) + { + var index = GetIndex(parent.Children, item); + parent.Children.Insert(index, item); + CountItem(item); + } + + parent = existing ?? parent.Children.First(e => e.Name == item.Name); + } + } + } + + private int GetIndex(ObservableCollection collection, ArchiveItem item) + { + for (var i = 0; i < collection.Count; i++) + { + if (item.Type == collection[i].Type && string.Compare(collection[i].Name, item.Name, StringComparison.OrdinalIgnoreCase) > 0) + { + return i; + } + } + + return item.Type switch + { + ArchiveItemType.Directory => collection.Count(e => e.Type == ArchiveItemType.Directory), + ArchiveItemType.File => collection.Count, + _ => 0, + }; + } + + private void CountItem(ArchiveItem item) + { + if (item.Type == ArchiveItemType.Directory) + { + _directoryCount++; + } + else if (item.Type == ArchiveItemType.File) + { + _fileCount++; + } + } + + private static readonly HashSet _supportedFileTypes = new() + { + ".zip", ".rar", ".7z", ".tar", ".nupkg", ".jar", ".gz", ".tar", ".tar.gz", ".tgz", + }; + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Helpers/IconCache.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Helpers/IconCache.cs new file mode 100644 index 000000000000..d9e3c009c98f --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Helpers/IconCache.cs @@ -0,0 +1,85 @@ +// 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.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.UI.Xaml.Media.Imaging; +using Peek.Common; +using Peek.Common.Helpers; +using Peek.Common.Models; +using Peek.FilePreviewer.Previewers.Helpers; + +namespace Peek.FilePreviewer.Previewers.Archives.Helpers +{ + public class IconCache + { + private readonly Dictionary _cache = new(); + + private BitmapSource? _directoryIconCache; + + public async Task GetFileExtIconAsync(string fileName, CancellationToken cancellationToken) + { + var extension = Path.GetExtension(fileName); + + if (_cache.TryGetValue(extension, out var cachedIcon)) + { + return cachedIcon; + } + + try + { + var shFileInfo = default(SHFILEINFO); + if (NativeMethods.SHGetFileInfo(fileName, NativeMethods.FILE_ATTRIBUTE_NORMAL, ref shFileInfo, (uint)Marshal.SizeOf(shFileInfo), NativeMethods.SHGFI_ICON | NativeMethods.SHGFI_SMALLICON | NativeMethods.SHGFI_USEFILEATTRIBUTES) != IntPtr.Zero) + { + var imageSource = await BitmapHelper.GetBitmapFromHIconAsync(shFileInfo.HIcon, cancellationToken); + _cache.Add(extension, imageSource); + return imageSource; + } + else + { + Logger.LogError($"Icon extraction for extension {extension} failed with error {Marshal.GetLastWin32Error()}"); + } + } + catch (Exception ex) + { + Logger.LogError($"Icon extraction for extension {extension} failed", ex); + } + + return null; + } + + public async Task GetDirectoryIconAsync(CancellationToken cancellationToken) + { + if (_directoryIconCache != null) + { + return _directoryIconCache; + } + + try + { + var shinfo = default(SHFILEINFO); + if (NativeMethods.SHGetFileInfo("directory", NativeMethods.FILE_ATTRIBUTE_DIRECTORY, ref shinfo, (uint)Marshal.SizeOf(shinfo), NativeMethods.SHGFI_ICON | NativeMethods.SHGFI_SMALLICON | NativeMethods.SHGFI_USEFILEATTRIBUTES) != IntPtr.Zero) + { + var imageSource = await BitmapHelper.GetBitmapFromHIconAsync(shinfo.HIcon, cancellationToken); + _directoryIconCache = imageSource; + return imageSource; + } + else + { + Logger.LogError($"Icon extraction for directory failed with error {Marshal.GetLastWin32Error()}"); + } + } + catch (Exception ex) + { + Logger.LogError($"Icon extraction for directory failed", ex); + } + + return null; + } + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItem.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItem.cs new file mode 100644 index 000000000000..2c34456da772 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItem.cs @@ -0,0 +1,38 @@ +// 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.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.UI.Xaml.Media; + +namespace Peek.FilePreviewer.Previewers.Archives.Models +{ + public partial class ArchiveItem : ObservableObject + { + [ObservableProperty] + private string _name; + + [ObservableProperty] + private ArchiveItemType _type; + + [ObservableProperty] + private ImageSource? _icon; + + [ObservableProperty] + private ulong _size; + + [ObservableProperty] + private bool _isExpanded; + + public ObservableCollection Children { get; } + + public ArchiveItem(string name, ArchiveItemType type, ImageSource? icon) + { + Name = name; + Type = type; + Icon = icon; + Children = new ObservableCollection(); + } + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItemTemplateSelector.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItemTemplateSelector.cs new file mode 100644 index 000000000000..38bd9766c047 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItemTemplateSelector.cs @@ -0,0 +1,27 @@ +// 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 Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace Peek.FilePreviewer.Previewers.Archives.Models +{ + public class ArchiveItemTemplateSelector : DataTemplateSelector + { + public DataTemplate? DirectoryTemplate { get; set; } + + public DataTemplate? FileTemplate { get; set; } + + protected override DataTemplate? SelectTemplateCore(object item) + { + if (item is ArchiveItem archiveItem) + { + return archiveItem.Type == ArchiveItemType.Directory ? DirectoryTemplate : FileTemplate; + } + + throw new ArgumentException("Item must be an ArchiveItem", nameof(item)); + } + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItemType.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItemType.cs new file mode 100644 index 000000000000..5abb4eb81b85 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Archives/Models/ArchiveItemType.cs @@ -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. + +namespace Peek.FilePreviewer.Previewers.Archives.Models +{ + public enum ArchiveItemType + { + Directory = 0, + File = 1, + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Helpers/BitmapHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Helpers/BitmapHelper.cs index 37ec5dd16fdf..476c2d9bf755 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/Helpers/BitmapHelper.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Helpers/BitmapHelper.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Threading; @@ -18,7 +19,7 @@ public static async Task GetBitmapFromHBitmapAsync(IntPtr hbitmap, { try { - var bitmap = System.Drawing.Image.FromHbitmap(hbitmap); + var bitmap = Image.FromHbitmap(hbitmap); if (isSupportingTransparency) { bitmap.MakeTransparent(); @@ -44,5 +45,32 @@ public static async Task GetBitmapFromHBitmapAsync(IntPtr hbitmap, NativeMethods.DeleteObject(hbitmap); } } + + public static async Task GetBitmapFromHIconAsync(IntPtr hicon, CancellationToken cancellationToken) + { + try + { + var icon = (Icon)Icon.FromHandle(hicon).Clone(); + var bitmap = icon.ToBitmap(); + + var bitmapImage = new BitmapImage(); + + using (var stream = new MemoryStream()) + { + bitmap.Save(stream, ImageFormat.Png); + stream.Position = 0; + + cancellationToken.ThrowIfCancellationRequested(); + await bitmapImage.SetSourceAsync(stream.AsRandomAccessStream()); + } + + return bitmapImage; + } + finally + { + // Delete HIcon to avoid memory leaks + _ = NativeMethods.DestroyIcon(hicon); + } + } } } diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IArchivePreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IArchivePreviewer.cs new file mode 100644 index 000000000000..0c813f60d3d8 --- /dev/null +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/Interfaces/IArchivePreviewer.cs @@ -0,0 +1,21 @@ +// 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.ObjectModel; +using Peek.FilePreviewer.Previewers.Archives.Models; + +namespace Peek.FilePreviewer.Previewers.Interfaces +{ + public interface IArchivePreviewer : IPreviewer, IDisposable + { + ObservableCollection Tree { get; } + + string? DirectoryCountText { get; } + + string? FileCountText { get; } + + string? SizeText { get; } + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/NativeMethods.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/NativeMethods.cs index fcba6abe923d..268f910dcfa9 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/NativeMethods.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/MediaPreviewer/Helpers/NativeMethods.cs @@ -10,6 +10,13 @@ namespace Peek.Common { public static class NativeMethods { + internal const uint SHGFI_ICON = 0x000000100; + internal const uint SHGFI_LINKOVERLAY = 0x000008000; + internal const uint SHGFI_SMALLICON = 0x000000001; + internal const uint SHGFI_USEFILEATTRIBUTES = 0x000000010; + internal const uint FILE_ATTRIBUTE_NORMAL = 0x00000080; + internal const uint FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] internal static extern int SHCreateItemFromParsingName( [MarshalAs(UnmanagedType.LPWStr)] string path, @@ -17,8 +24,14 @@ internal static extern int SHCreateItemFromParsingName( ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem); + [DllImport("User32.dll", SetLastError = true)] + internal static extern int DestroyIcon(IntPtr hIcon); + [DllImport("gdi32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DeleteObject(IntPtr hObject); + + [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + internal static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags); } } diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs index f03a8435bebd..3ac9ceeceec7 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs @@ -4,6 +4,7 @@ using Microsoft.PowerToys.Telemetry; using Peek.Common.Models; +using Peek.FilePreviewer.Previewers.Archives; using Peek.UI.Telemetry.Events; namespace Peek.FilePreviewer.Previewers @@ -24,6 +25,10 @@ public IPreviewer Create(IFileSystemItem file) { return new WebBrowserPreviewer(file); } + else if (ArchivePreviewer.IsFileTypeSupported(file.Extension)) + { + return new ArchivePreviewer(file); + } // Other previewer types check their supported file types here return CreateDefaultPreviewer(file); diff --git a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw index b940d7968f99..0d032785b17f 100644 --- a/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw +++ b/src/modules/peek/Peek.UI/Strings/en-us/Resources.resw @@ -221,4 +221,16 @@ An error occurred while previewing this file. Failed fallback preview text. + + {0} directories + {0} is the number of directories in the archive + + + {0} files + {0} is the number of files in the archive + + + {0} (extracted {1}) + {0} is the size of the archive, {1} is the extracted size + \ No newline at end of file