383 lines
13 KiB
C#

// <copyright file="FileConverterExtension.cs" company="AAllard">License: http://www.gnu.org/licenses/gpl.html GPL version 3.</copyright>
namespace FileConverterExtension
{
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using SharpShell.Attributes;
using SharpShell.SharpContextMenu;
/// <summary>
/// File converter context menu extension class.
/// </summary>
[ComVisible(true), Guid("AF9B72B5-F4E4-44B0-A3D9-B55B748EFE90")]
[COMServerAssociation(AssociationType.AllFiles)]
public class FileConverterExtension : SharpContextMenu
{
private const int MaximumProcessArgumentsLength = 8000; // https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/command-line-string-limitation
private PresetReference[] presetReferences = null;
private List<MenuEntry> menuEntries = new List<MenuEntry>();
private HashSet<string> extensionCache = new HashSet<string>();
private class MenuEntry
{
public PresetReference PresetReference;
public bool Enabled;
public int ExtensionRefCount;
public MenuEntry(PresetReference presetReference)
{
this.PresetReference = presetReference;
this.Enabled = false;
this.ExtensionRefCount = 0;
}
}
private bool DisplayPresetIcons
{
get
{
string displayPresetIcons = PathHelpers.FileConverterRegistryKey.GetValue("DisplayPresetIcons") as string;
if (displayPresetIcons == null)
{
return false;
}
if (!bool.TryParse(displayPresetIcons, out bool value))
{
return false;
}
return value;
}
}
private PresetReference[] PresetReferences
{
get
{
this.LoadExtensionSettingsIfNecessary();
return this.presetReferences;
}
}
protected override bool CanShowMenu()
{
this.RefreshExtensionCacheFromSelectedItems();
PresetReference[] presets = this.PresetReferences;
foreach (string extension in this.extensionCache)
{
foreach (PresetReference presetReference in presets)
{
if (presetReference.InputTypes.Contains(extension))
{
return true;
}
}
}
return false;
}
protected override ContextMenuStrip CreateMenu()
{
this.RefreshPresetList();
bool displayPresetIcons = this.DisplayPresetIcons;
ContextMenuStrip menu = new ContextMenuStrip();
ToolStripMenuItem fileConverterItem = new ToolStripMenuItem
{
Text = "File Converter",
Image = new Icon(Properties.Resources.ApplicationIcon, SystemInformation.SmallIconSize).ToBitmap(),
};
foreach (MenuEntry menuEntry in this.menuEntries)
{
ToolStripMenuItem root = fileConverterItem;
if (menuEntry.PresetReference.Folders != null)
{
foreach (string folder in menuEntry.PresetReference.Folders)
{
ToolStripItem[] folderItems = root.DropDownItems.Find(folder, false);
if (folderItems.Length == 0)
{
ToolStripMenuItem folderItem = new ToolStripMenuItem
{
Name = folder,
Text = folder,
Image = new Icon(Properties.Resources.FolderIcon, SystemInformation.SmallIconSize).ToBitmap(),
};
root.DropDownItems.Add(folderItem);
root = folderItem;
}
else
{
root = folderItems[0] as ToolStripMenuItem;
}
if (root == null)
{
break;
}
}
}
if (root == null)
{
// Fallback when something went wrong during folder creation.
root = fileConverterItem;
}
ToolStripMenuItem subItem = new ToolStripMenuItem
{
Text = menuEntry.PresetReference.Name,
Enabled = menuEntry.Enabled
};
if (displayPresetIcons)
{
subItem.Image = new Icon(Properties.Resources.PresetIcon, SystemInformation.SmallIconSize).ToBitmap();
}
root.DropDownItems.Add(subItem);
subItem.Click += (sender, args) => this.ConvertFiles(menuEntry.PresetReference.FullName);
}
if (this.menuEntries.Count > 0)
{
fileConverterItem.DropDownItems.Add(new ToolStripSeparator());
}
{
ToolStripMenuItem subItem = new ToolStripMenuItem
{
Text = "Configure presets...",
Image = new Icon(Properties.Resources.SettingsIcon, SystemInformation.SmallIconSize).ToBitmap(),
};
fileConverterItem.DropDownItems.Add(subItem);
subItem.Click += (sender, args) => this.OpenSettings();
}
menu.Items.Add(fileConverterItem);
return menu;
}
private void RefreshExtensionCacheFromSelectedItems()
{
// Retrieve selected files extensions.
this.extensionCache.Clear();
foreach (string filePath in this.SelectedItemPaths)
{
string extension = Path.GetExtension(filePath);
if (string.IsNullOrEmpty(extension))
{
continue;
}
extension = extension.Substring(1).ToLowerInvariant();
this.extensionCache.Add(extension);
}
}
private void RefreshPresetList()
{
this.RefreshExtensionCacheFromSelectedItems();
// Activate compatible menu entries.
PresetReference[] presets = this.presetReferences;
this.menuEntries.Clear();
foreach (string extension in this.extensionCache)
{
foreach (PresetReference presetReference in presets)
{
if (!presetReference.InputTypes.Contains(extension))
{
continue;
}
MenuEntry menuEntry = this.menuEntries.Find(entry => entry.PresetReference.FullName == presetReference.FullName);
if (menuEntry == null)
{
menuEntry = new MenuEntry(presetReference);
this.menuEntries.Add(menuEntry);
}
menuEntry.ExtensionRefCount++;
}
}
// Enable presets compatible with all input files.
foreach (MenuEntry menuEntry in this.menuEntries)
{
menuEntry.Enabled = menuEntry.ExtensionRefCount == this.extensionCache.Count;
}
}
private void LoadExtensionSettingsIfNecessary()
{
if (this.presetReferences != null)
{
return;
}
if (File.Exists(PathHelpers.UserSettingsFilePath))
{
try
{
XmlHelpers.LoadFromFile("Settings", PathHelpers.UserSettingsFilePath, out this.presetReferences);
return;
}
catch
{
// Can't handle this error in the explorer extension.
}
}
try
{
XmlHelpers.LoadFromFile("Settings", PathHelpers.DefaultSettingsFilePath, out this.presetReferences);
}
catch
{
// Can't handle this error in the explorer extension.
}
}
private void OpenSettings()
{
if (string.IsNullOrEmpty(PathHelpers.FileConverterPath))
{
MessageBox.Show("Can't retrieve the file converter executable path. You should try to reinstall the application.");
return;
}
if (!File.Exists(PathHelpers.FileConverterPath))
{
MessageBox.Show($"Can't find the file converter executable ({PathHelpers.FileConverterPath}). You should try to reinstall the application.");
return;
}
ProcessStartInfo processStartInfo = new ProcessStartInfo(PathHelpers.FileConverterPath)
{
CreateNoWindow = false,
UseShellExecute = false,
RedirectStandardOutput = false,
};
// Build arguments string.
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("--settings");
processStartInfo.Arguments = stringBuilder.ToString();
Process exeProcess = Process.Start(processStartInfo);
}
private void ConvertFiles(string presetName)
{
if (string.IsNullOrEmpty(PathHelpers.FileConverterPath))
{
MessageBox.Show("Can't retrieve the file converter executable path. You should try to reinstall the application.");
return;
}
if (!File.Exists(PathHelpers.FileConverterPath))
{
MessageBox.Show($"Can't find the file converter executable ({PathHelpers.FileConverterPath}). You should try to reinstall the application.");
return;
}
void BuildConversionPresetArgument(StringBuilder sb)
{
sb.Append("--conversion-preset ");
sb.Append(" \"");
sb.Append(presetName);
sb.Append("\"");
}
// Build arguments string.
StringBuilder stringBuilder = new StringBuilder();
BuildConversionPresetArgument(stringBuilder);
string fileListPath = null;
foreach (var filePath in this.SelectedItemPaths)
{
stringBuilder.Append(" \"");
stringBuilder.Append(filePath);
stringBuilder.Append("\"");
if (stringBuilder.Length >= MaximumProcessArgumentsLength)
{
// Alternative way of passing arguments to not overflow the command line.
stringBuilder.Clear();
BuildConversionPresetArgument(stringBuilder);
// Store list of file to convert in a file in Temp folder.
fileListPath = Path.Combine(Path.GetTempPath(), "file-converter-input-list.txt");
int index = 1;
while (File.Exists(fileListPath))
{
fileListPath = Path.Combine(Path.GetTempPath(), $"file-converter-input-list-{index}.txt");
index++;
}
using (FileStream file = File.OpenWrite(fileListPath))
using (StreamWriter writer = new StreamWriter(file))
{
foreach (var path in this.SelectedItemPaths)
{
writer.WriteLine(path);
}
}
stringBuilder.Append(" --input-files ");
stringBuilder.Append(" \"");
stringBuilder.Append(fileListPath);
stringBuilder.Append("\"");
break;
}
}
var processStartInfo = new ProcessStartInfo(PathHelpers.FileConverterPath)
{
CreateNoWindow = false,
UseShellExecute = false,
RedirectStandardOutput = false,
Arguments = stringBuilder.ToString(),
};
Process exeProcess = Process.Start(processStartInfo);
exeProcess.EnableRaisingEvents = true;
exeProcess.Exited += (sender, args) =>
{
if (fileListPath != null)
{
try
{
File.Delete(fileListPath);
}
catch
{
}
}
};
}
}
}