├── .gitmodules ├── QuickLook.Plugin.Metadata.Base.config ├── Scripts ├── pack-zip.ps1 └── update-version.ps1 ├── LICENSE.txt ├── Plugin.cs ├── README.md ├── Extensions.cs ├── Properties └── AssemblyInfo.cs ├── QuickLook.Plugin.TorrentViewer.sln ├── TorrentFileListView.xaml.cs ├── TorrentFileEntry.cs ├── TorrentInfoPanel.xaml ├── QuickLook.Plugin.TorrentViewer.csproj ├── Converters.cs ├── .gitignore ├── TorrentInfoPanel.xaml.cs ├── TorrentFileListView.xaml └── IconManager.cs /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "QuickLook.Common"] 2 | path = QuickLook.Common 3 | url = https://github.com/QL-Win/QuickLook.Common.git 4 | -------------------------------------------------------------------------------- /QuickLook.Plugin.Metadata.Base.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | QuickLook.Plugin.TorrentViewer 4 | 0 5 | Enable preview for torrent file. 6 | -------------------------------------------------------------------------------- /Scripts/pack-zip.ps1: -------------------------------------------------------------------------------- 1 | Remove-Item ..\QuickLook.Plugin.TorrentViewer.qlplugin -ErrorAction SilentlyContinue 2 | 3 | $files = Get-ChildItem -Path ..\bin\Release\ -Exclude *.pdb,*.xml 4 | Compress-Archive $files ..\QuickLook.Plugin.TorrentViewer.zip 5 | Move-Item ..\QuickLook.Plugin.TorrentViewer.zip ..\QuickLook.Plugin.TorrentViewer.qlplugin -------------------------------------------------------------------------------- /Scripts/update-version.ps1: -------------------------------------------------------------------------------- 1 | $tag = git describe --always --tags "--abbrev=0" 2 | $revision = git describe --always --tags 3 | 4 | $text = @" 5 | // This file is generated by update-version.ps1 6 | 7 | using System.Reflection; 8 | 9 | [assembly: AssemblyVersion("$tag")] 10 | [assembly: AssemblyInformationalVersion("$revision")] 11 | "@ 12 | 13 | $text | Out-File $PSScriptRoot\..\GitVersion.cs -Encoding utf8 14 | 15 | 16 | $xml = [xml](Get-Content $PSScriptRoot\..\QuickLook.Plugin.Metadata.Base.config) 17 | $xml.Metadata.Version="$revision" 18 | $xml.Save("$PSScriptRoot\..\QuickLook.Plugin.Metadata.config") -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Paddy Xu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Plugin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Windows; 4 | using System.Windows.Controls; 5 | using QuickLook.Common.Plugin; 6 | 7 | namespace QuickLook.Plugin.TorrentViewer 8 | { 9 | public class Plugin : IViewer 10 | { 11 | private TorrentInfoPanel _panel; 12 | 13 | public int Priority => 0; 14 | 15 | public void Init() 16 | { 17 | } 18 | 19 | public bool CanHandle(string path) 20 | { 21 | return File.Exists(path) && path.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase); 22 | } 23 | 24 | public void Prepare(string path, ContextObject context) 25 | { 26 | context.PreferredSize = new Size {Width = 800, Height = 400}; 27 | } 28 | 29 | public void View(string path, ContextObject context) 30 | { 31 | _panel = new TorrentInfoPanel(path); 32 | 33 | context.ViewerContent = _panel; 34 | context.Title = $"{Path.GetFileName(path)}"; 35 | 36 | context.IsBusy = false; 37 | } 38 | 39 | public void Cleanup() 40 | { 41 | _panel?.Dispose(); 42 | _panel = null; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![QuickLook icon](https://user-images.githubusercontent.com/1687847/29485863-8cd61b7c-84e2-11e7-97d5-eacc2ba10d28.png) 2 | 3 | # QuickLook.Plugin.TorrentViewer 4 | 5 | ## How-To-Use 6 | 7 | 1. Install plugin. 8 | 2. Modify `QuickLook.exe.config` and add the following lines into `configuration/runtime` section: 9 | 10 | ``` xml 11 | 12 | 13 | 14 | ... 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ... 24 | 25 | 26 | 27 | ``` 28 | 29 | Restart `QuickLook.exe`, it should support to preview torrent file now. 30 | 31 | ### Why the modify is necessary? 32 | 33 | Because of the `BencodeNET` is target to the `.Net Standard 2.0` (with `System.Buffers 4.0.2.0`), 34 | but the `QuickLook` is target to the `.Net Framework 4.6.2` (with `System.Buffers 4.0.3.0`); 35 | 36 | Also see: 37 | 38 | - https://github.com/dotnet/runtime/issues/1830 39 | - https://github.com/dotnet/runtime/issues/27774 40 | 41 | ### Where is the `QuickLook.exe.config`? 42 | 43 | It should nearby your `QuickLook.exe` file. 44 | 45 | ### Unable to modify the Microsoft store version of `QuickLook.exe.config` 46 | 47 | https://github.com/Cologler/QuickLook.Plugin.TorrentViewer/issues/3 reported this, 48 | but I have no idea how to solve it. ¯\\_(ツ)_/¯ 49 | 50 | -------------------------------------------------------------------------------- /Extensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Paddy Xu 2 | // 3 | // This file is part of QuickLook program. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Windows; 21 | using System.Windows.Media; 22 | 23 | namespace QuickLook.Plugin.TorrentViewer 24 | { 25 | public static class Extensions 26 | { 27 | public static void ForEach(this IEnumerable enumeration, Action action) 28 | { 29 | foreach (var item in enumeration) 30 | action(item); 31 | } 32 | 33 | public static T GetDescendantByType(this Visual element) where T : class 34 | { 35 | if (element == null) 36 | return default; 37 | if (element.GetType() == typeof(T)) 38 | return element as T; 39 | 40 | T foundElement = null; 41 | (element as FrameworkElement)?.ApplyTemplate(); 42 | 43 | for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) 44 | { 45 | var visual = VisualTreeHelper.GetChild(element, i) as Visual; 46 | foundElement = visual.GetDescendantByType(); 47 | if (foundElement != null) 48 | break; 49 | } 50 | 51 | return foundElement; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("QuickLook.Plugin.TorrentViewer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("pooi.moe")] 12 | [assembly: AssemblyProduct("QuickLook.Plugin.TorrentViewer")] 13 | [assembly: AssemblyCopyright("Copyright © Paddy Xu 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | //In order to begin building localizable applications, set 23 | //CultureYouAreCodingWith in your .csproj file 24 | //inside a . For example, if you are using US english 25 | //in your source files, set the to en-US. Then uncomment 26 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 27 | //the line below to match the UICulture setting in the project file. 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | 32 | [assembly: ThemeInfo( 33 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 34 | //(used if a resource is not found in the page, 35 | // or application resource dictionaries) 36 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 37 | //(used if a resource is not found in the page, 38 | // app, or any theme specific resource dictionaries) 39 | )] 40 | 41 | 42 | // Version information for an assembly consists of the following four values: 43 | // 44 | // Major Version 45 | // Minor Version 46 | // Build Number 47 | // Revision 48 | // 49 | // You can specify all the values or you can default the Build and Revision Numbers 50 | // by using the '*' as shown below: 51 | //[assembly: AssemblyVersion("1.0.*")] -------------------------------------------------------------------------------- /QuickLook.Plugin.TorrentViewer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28010.2003 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Plugin.TorrentViewer", "QuickLook.Plugin.TorrentViewer.csproj", "{863ECAAC-18D9-4256-A27D-0F308089FB47}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuickLook.Common", "QuickLook.Common\QuickLook.Common.csproj", "{85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x86 = Debug|x86 14 | Release|Any CPU = Release|Any CPU 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Debug|x86.ActiveCfg = Debug|x86 21 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Debug|x86.Build.0 = Debug|x86 22 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Release|x86.ActiveCfg = Release|x86 25 | {863ECAAC-18D9-4256-A27D-0F308089FB47}.Release|x86.Build.0 = Release|x86 26 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Debug|x86.ActiveCfg = Debug|Any CPU 29 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Debug|x86.Build.0 = Debug|Any CPU 30 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Release|x86.ActiveCfg = Release|Any CPU 33 | {85FDD6BA-871D-46C8-BD64-F6BB0CB5EA95}.Release|x86.Build.0 = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {D545EDE6-0533-4E3F-BB67-308B17E4EDAC} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /TorrentFileListView.xaml.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Paddy Xu 2 | // 3 | // This file is part of QuickLook program. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | using System; 19 | using System.Windows; 20 | using System.Windows.Controls; 21 | 22 | namespace QuickLook.Plugin.TorrentViewer 23 | { 24 | /// 25 | /// Interaction logic for ArchiveFileListView.xaml 26 | /// 27 | public partial class TorrentFileListView : UserControl, IDisposable 28 | { 29 | public TorrentFileListView() 30 | { 31 | InitializeComponent(); 32 | } 33 | 34 | public void Dispose() 35 | { 36 | GC.SuppressFinalize(this); 37 | 38 | IconManager.ClearCache(); 39 | } 40 | 41 | public void SetDataContext(object context) 42 | { 43 | treeGrid.DataContext = context; 44 | 45 | treeView.LayoutUpdated += (sender, e) => 46 | { 47 | // return when empty 48 | if (treeView.Items.Count == 0) 49 | return; 50 | 51 | // return when there are more than one root nodes 52 | if (treeView.Items.Count > 1) 53 | return; 54 | 55 | var root = (TreeViewItem) treeView.ItemContainerGenerator.ContainerFromItem(treeView.Items[0]); 56 | if (root == null) 57 | return; 58 | 59 | root.IsExpanded = true; 60 | }; 61 | } 62 | 63 | private void CopyMd5SumLower_Click(object sender, RoutedEventArgs e) 64 | { 65 | if ((sender as FrameworkElement)?.DataContext is TorrentFileEntry fileEntry && fileEntry.Md5Sum?.Length > 0) 66 | { 67 | try 68 | { 69 | Clipboard.SetText(fileEntry.Md5Sum.ToLower()); 70 | return; 71 | } 72 | catch (Exception) { } 73 | 74 | MessageBox.Show("Failed to copy Md5Sum."); 75 | } 76 | } 77 | 78 | private void CopyMd5SumUpper_Click(object sender, RoutedEventArgs e) 79 | { 80 | if ((sender as FrameworkElement)?.DataContext is TorrentFileEntry fileEntry && fileEntry.Md5Sum?.Length > 0) 81 | { 82 | try 83 | { 84 | Clipboard.SetText(fileEntry.Md5Sum.ToUpper()); 85 | return; 86 | } 87 | catch (Exception) { } 88 | 89 | MessageBox.Show("Failed to copy Md5Sum."); 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /TorrentFileEntry.cs: -------------------------------------------------------------------------------- 1 | // Copyright © 2017 Paddy Xu 2 | // 3 | // This file is part of QuickLook program. 4 | // 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with this program. If not, see . 17 | 18 | using System; 19 | using System.Collections.Generic; 20 | using System.Linq; 21 | 22 | namespace QuickLook.Plugin.TorrentViewer 23 | { 24 | public class TorrentFileEntry : IComparable 25 | { 26 | private readonly TorrentFileEntry _parent; 27 | private int _cachedDepth = -1; 28 | private int _cachedLevel = -1; 29 | 30 | public TorrentFileEntry(string name, bool isFolder, TorrentFileEntry parent = null, string md5Sum = null) 31 | { 32 | Name = name; 33 | IsFolder = isFolder; 34 | 35 | _parent = parent; 36 | _parent?.Children.Add(this); 37 | this.Md5Sum = md5Sum ?? string.Empty; 38 | } 39 | 40 | public List Children { get; } = new List(); 41 | 42 | public IList VisibileChildren => this.Children.Where(x => !x.IsPaddingFile).ToList(); 43 | 44 | public string Name { get; set; } 45 | 46 | public bool Encrypted { get; set; } 47 | 48 | public bool IsFolder { get; set; } 49 | 50 | public bool IsPaddingFile 51 | { 52 | get 53 | { 54 | return this.Name is string name && name.StartsWith("_____padding_file_"); 55 | } 56 | } 57 | 58 | public ulong Size { get; set; } 59 | 60 | public string Md5Sum { get; } 61 | 62 | public DateTime ModifiedDate { get; set; } 63 | 64 | /// 65 | /// Returns the maximum depth of all siblings 66 | /// 67 | public int Level 68 | { 69 | get 70 | { 71 | if (_cachedLevel != -1) 72 | return _cachedLevel; 73 | 74 | if (_parent == null) 75 | _cachedLevel = GetDepth(); 76 | else 77 | _cachedLevel = _parent.Level - 1; 78 | 79 | return _cachedLevel; 80 | } 81 | } 82 | 83 | public int CompareTo(TorrentFileEntry other) 84 | { 85 | if (IsFolder == other.IsFolder) 86 | return string.Compare(Name, other.Name, StringComparison.CurrentCulture); 87 | 88 | if (IsFolder) 89 | return -1; 90 | 91 | return 1; 92 | } 93 | 94 | /// 95 | /// Returns the number of nodes in the longest path to a leaf 96 | /// 97 | private int GetDepth() 98 | { 99 | if (_cachedDepth != -1) 100 | return _cachedDepth; 101 | 102 | var max = Children.Count == 0 ? 0 : Children.Max(r => r.GetDepth()); 103 | _cachedDepth = max + 1; 104 | return _cachedDepth; 105 | } 106 | 107 | public override string ToString() 108 | { 109 | if (IsFolder) 110 | return $"{Name}"; 111 | 112 | var en = Encrypted ? "*" : ""; 113 | return $"{Name}{en},{IsFolder},{Size},{ModifiedDate}"; 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /TorrentInfoPanel.xaml: -------------------------------------------------------------------------------- 1 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |