From 21af7ad7ff002abdb023feb48e25ccbf042ae65b Mon Sep 17 00:00:00 2001 From: ema Date: Wed, 18 Jun 2025 00:54:30 +0800 Subject: [PATCH] Prepare support .rpm --- .../InfoPanels/RpmInfoPanel.xaml | 241 ++++++++++++++++++ .../InfoPanels/RpmInfoPanel.xaml.cs | 93 +++++++ .../PackageParsers/Rpm/RpmInfo.cs | 43 ++++ .../PackageParsers/Rpm/RpmParser.cs | 41 +++ .../PackageParsers/Rpm/RpmReader.cs | 117 +++++++++ .../QuickLook.Plugin.AppViewer/Plugin.cs | 3 + .../Resources/rpm.png | Bin 0 -> 5935 bytes 7 files changed, 538 insertions(+) create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmInfo.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmParser.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmReader.cs create mode 100644 QuickLook.Plugin/QuickLook.Plugin.AppViewer/Resources/rpm.png diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml new file mode 100644 index 0000000..1605e82 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml.cs new file mode 100644 index 0000000..79cd9fe --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/InfoPanels/RpmInfoPanel.xaml.cs @@ -0,0 +1,93 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using QuickLook.Common.ExtensionMethods; +using QuickLook.Common.Helpers; +using QuickLook.Common.Plugin; +using QuickLook.Plugin.AppViewer.PackageParsers.AppImage; +using QuickLook.Plugin.AppViewer.PackageParsers.Rpm; +using System; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows.Controls; +using System.Windows.Media.Imaging; + +namespace QuickLook.Plugin.AppViewer.InfoPanels; + +public partial class RpmInfoPanel : UserControl, IAppInfoPanel +{ + private readonly ContextObject _context; + + public RpmInfoPanel(ContextObject context) + { + _context = context; + + DataContext = this; + InitializeComponent(); + + string translationFile = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Translations.config"); + applicationNameTitle.Text = TranslationHelper.Get("APP_NAME", translationFile); + versionTitle.Text = TranslationHelper.Get("APP_VERSION", translationFile); + architectureTitle.Text = TranslationHelper.Get("ARCHITECTURE", translationFile); + typeTitle.Text = TranslationHelper.Get("TYPE", translationFile); + terminalTitle.Text = TranslationHelper.Get("TERMINAL", translationFile); + totalSizeTitle.Text = TranslationHelper.Get("TOTAL_SIZE", translationFile); + modDateTitle.Text = TranslationHelper.Get("LAST_MODIFIED", translationFile); + environmentGroupBox.Header = TranslationHelper.Get("ENVIRONMENT", translationFile); + } + + public void DisplayInfo(string path) + { + var name = Path.GetFileName(path); + filename.Text = string.IsNullOrEmpty(name) ? path : name; + + _ = Task.Run(() => + { + if (File.Exists(path)) + { + var size = new FileInfo(path).Length; + RpmInfo rpmInfo = RpmParser.Parse(path); + var last = File.GetLastWriteTime(path); + + Dispatcher.Invoke(() => + { + applicationName.Text = rpmInfo.Name; + version.Text = rpmInfo.Version; + architectureName.Text = rpmInfo.Arch; + type.Text = rpmInfo.Type; + terminal.Text = rpmInfo.Terminal; + totalSize.Text = size.ToPrettySize(2); + modDate.Text = last.ToString(CultureInfo.CurrentCulture); + permissions.ItemsSource = rpmInfo.Env; + + if (rpmInfo.HasIcon) + { + image.Source = rpmInfo.Logo.ToBitmapSource(); + } + else + { + image.Source = new BitmapImage(new Uri("pack://application:,,,/QuickLook.Plugin.AppViewer;component/Resources/rpm.png")); + } + + _context.IsBusy = false; + }); + } + }); + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmInfo.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmInfo.cs new file mode 100644 index 0000000..8f65de7 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmInfo.cs @@ -0,0 +1,43 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using System.Drawing; + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Rpm; + +public class RpmInfo +{ + public string Arch { get; set; } + + public string Version { get; set; } + + public string Name { get; set; } + + public string Exec { get; set; } + + public string Icon { get; set; } + + public Bitmap Logo { get; set; } + + public bool HasIcon => !string.IsNullOrEmpty(Icon) && Logo != null; + + public string Type { get; set; } + + public string Terminal { get; set; } + + public string[] Env { get; set; } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmParser.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmParser.cs new file mode 100644 index 0000000..f3a4327 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmParser.cs @@ -0,0 +1,41 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using QuickLook.Plugin.AppViewer.PackageParsers.AppImage; + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Rpm; + +public class RpmParser +{ + public static RpmInfo Parse(string path) + { + var reader = new RpmReader(path); + + return new RpmInfo() + { + Arch = reader.Arch, + Version = reader.Version, + Name = reader.Name, + Exec = reader.Exec, + Icon = reader.Icon, + Logo = reader.Logo, + Type = reader.Type, + Terminal = reader.Terminal, + Env = reader.Env, + }; + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmReader.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmReader.cs new file mode 100644 index 0000000..0056a96 --- /dev/null +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/PackageParsers/Rpm/RpmReader.cs @@ -0,0 +1,117 @@ +// Copyright © 2017-2025 QL-Win Contributors +// +// This file is part of QuickLook program. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +using PureSharpCompress.Common; +using System; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Text; + +namespace QuickLook.Plugin.AppViewer.PackageParsers.Rpm; + +public class RpmReader +{ + public string Arch { get; set; } + + public string Version { get; set; } + + public string Name { get; set; } + + public string Exec { get; set; } + + public string Icon { get; set; } + + public Bitmap Logo { get; set; } + + public string Type { get; set; } + + public string Terminal { get; set; } + + public string[] Env { get; set; } + + public RpmReader(Stream stream) + { + Open(stream); + } + + public RpmReader(string path) + { + using FileStream fs = File.OpenRead(path); + Open(fs); + } + + private void Open(Stream stream) + { + using var br = new BinaryReader(stream); + + // Step 1: Read the lead (96 bytes) + byte[] lead = br.ReadBytes(96); + Debug.WriteLine($"[lead] 96 bytes read, magic: {BitConverter.ToString(lead, 0, 4)}"); + + // Step 2: Read signature header + RpmHeader sigHeader = ReadHeader(br); + Debug.WriteLine($"[signature header] IndexCount: {sigHeader.IndexCount}, DataSize: {sigHeader.DataSize}"); + + // Step 3: Read main header + RpmHeader mainHeader = ReadHeader(br); + Debug.WriteLine($"[main header] IndexCount: {mainHeader.IndexCount}, DataSize: {mainHeader.DataSize}"); + + // Step 4: Remaining is the payload (cpio archive + compression) + long payloadSize = stream.Length - stream.Position; + Debug.WriteLine($"[payload] Size: {payloadSize} bytes at offset: {stream.Position}"); + + // (Optional) Detect compression (e.g. gzip, xz, zstd) + byte[] magic = br.ReadBytes(6); + string type = magic[0] switch + { + 0x1F when magic[1] == 0x8B => "gzip", + 0xFD when magic[1] == 0x37 => "xz", + 0x28 when magic[1] == 0xB5 => "zstd", + _ => "unknown" + }; + Debug.WriteLine($"Detected payload compression: {type}"); + } + + private static RpmHeader ReadHeader(BinaryReader br) + { + // ed ab ee db 03 + byte[] magic = br.ReadBytes(3); + //if (Encoding.ASCII.GetString(magic) != "\x8e\xad\xe8") + //throw new InvalidDataException("Invalid RPM header magic"); + + byte version = br.ReadByte(); // Usually 1 + byte[] reserved = br.ReadBytes(4); + int indexCount = ReadBigEndianInt32(br); + int dataSize = ReadBigEndianInt32(br); + return new RpmHeader { IndexCount = indexCount, DataSize = dataSize }; + } + + private static int ReadBigEndianInt32(BinaryReader br) + { + byte[] b = br.ReadBytes(4); + if (b.Length < 4) throw new EndOfStreamException(); + return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + } + + private class RpmHeader + { + public int IndexCount { get; set; } + + public int DataSize { get; set; } + } +} diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs index 9e57119..eb9430e 100644 --- a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs +++ b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Plugin.cs @@ -51,6 +51,7 @@ public class Plugin : IViewer // Ubuntu ".deb", // Debian Package ".appimage", // AppImage Format + // ".rpm", // Red Hat Package Manager // Others ".wgt", ".wgtu", // UniApp Widget @@ -82,6 +83,7 @@ public class Plugin : IViewer ".deb" => new Size { Width = 600, Height = 345 }, ".dmg" => new Size { Width = 560, Height = 510 }, ".appimage" => new Size { Width = 600, Height = 300 }, + ".rpm" => new Size { Width = 600, Height = 300 }, ".wgt" or ".wgtu" => new Size { Width = 600, Height = 345 }, _ => throw new NotSupportedException("Extension is not supported."), }; @@ -105,6 +107,7 @@ public class Plugin : IViewer ".deb" => new DebInfoPanel(context), ".dmg" => new DmgInfoPanel(context), ".appimage" => new AppImageInfoPanel(context), + ".rpm" => new RpmInfoPanel(context), ".wgt" or ".wgtu" => new WgtInfoPanel(context), _ => throw new NotSupportedException("Extension is not supported."), }; diff --git a/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Resources/rpm.png b/QuickLook.Plugin/QuickLook.Plugin.AppViewer/Resources/rpm.png new file mode 100644 index 0000000000000000000000000000000000000000..b15397342f8a5d7a2a6b113b9186653e66f9bf79 GIT binary patch literal 5935 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 literal 0 HcmV?d00001