QuickImage – Rev 19
?pathlinks?
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using System.Windows.Forms;
using Configuration;
using ImageMagick;
using ImageMagick.Factories;
using Microsoft.WindowsAPICodePack.Dialogs;
using MimeDetective.Storage;
using NetSparkleUpdater;
using NetSparkleUpdater.Enums;
using NetSparkleUpdater.SignatureVerifiers;
using NetSparkleUpdater.UI.WinForms;
using QuickImage.Database;
using QuickImage.ImageListViewSorters;
using QuickImage.Utilities;
using QuickImage.Utilities.Controls;
using QuickImage.Utilities.Extensions;
using QuickImage.Utilities.Serialization.Comma_Separated_Values;
using QuickImage.Utilities.Serialization.XML;
using Serilog;
using Shipwreck.Phash;
using Shipwreck.Phash.Bitmaps;
using Tesseract;
using ImageFormat = System.Drawing.Imaging.ImageFormat;
namespace QuickImage
{
public partial class Form1 : Form
{
private readonly CancellationToken _cancellationToken;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly ScheduledContinuation _changedConfigurationContinuation;
private readonly FileMutex _fileMutex;
private readonly TaskScheduler _formTaskScheduler;
private readonly ConcurrentDictionary<string, ListViewGroup> _imageListViewGroupDictionary;
private readonly SemaphoreSlim _imageListViewLock;
private readonly ImageTool _imageTool;
private readonly Progress<ImageListViewItemProgress<ListViewItem>> _listViewItemProgress;
private readonly MagicMime _magicMime;
private readonly MD5 _md5;
private readonly LogMemorySink _memorySink = new LogMemorySink();
private readonly QuickImageDatabase _quickImageDatabase;
private readonly Progress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>>
_quickImageListViewProgress;
private readonly Random _random;
private readonly ScheduledContinuation _searchScheduledContinuation;
private readonly ConcurrentDictionary<string, ListViewItem> _searchStore;
private readonly ScheduledContinuation _selectionScheduledContinuation;
private readonly ScheduledContinuation _sortScheduledContinuation;
private readonly SparkleUpdater _sparkle;
private AutoCompleteStringCollection _tagAutoCompleteStringCollection;
private TagListViewSorter _tagListViewSorter;
private readonly TagManager _tagManager;
private AboutForm _aboutForm;
private CancellationToken _combinedSearchSelectionCancellationToken;
private CancellationToken _combinedSelectionCancellationToken;
private EditorForm _editorForm;
private CancellationTokenSource _linkedSearchCancellationTokenSource;
private CancellationTokenSource _linkedSelectionCancellationTokenSource;
private PreviewForm _previewForm;
private QuickImageSearchParameters _quickImageSearchParameters;
private QuickImageSearchType _quickImageSearchType;
private RenameForm _renameForm;
private CancellationToken _searchCancellationToken;
private CancellationTokenSource _searchCancellationTokenSource;
private CancellationToken _selectionCancellationToken;
private CancellationTokenSource _selectionCancellationTokenSource;
private SettingsForm _settingsForm;
private ViewLogsForm _viewLogsForm;
/// <summary>
/// The operation to perform on the menu item and descendants.
/// </summary>
public enum MenuItemsToggleOperation
{
NONE,
ENABLE,
DISABLE
}
private Configuration.Configuration Configuration { get; set; }
public bool MemorySinkEnabled { get; set; } = true;
private Form1()
{
InitializeComponent();
_fileMutex = new FileMutex();
_magicMime = new MagicMime(_fileMutex);
_cancellationTokenSource = new CancellationTokenSource();
_cancellationToken = _cancellationTokenSource.Token;
_searchScheduledContinuation = new ScheduledContinuation();
_selectionScheduledContinuation = new ScheduledContinuation();
_sortScheduledContinuation = new ScheduledContinuation();
_md5 = new MD5CryptoServiceProvider();
_searchStore = new ConcurrentDictionary<string, ListViewItem>();
_imageListViewLock = new SemaphoreSlim(1, 1);
_quickImageSearchType = QuickImageSearchType.Any;
_formTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
_imageListViewGroupDictionary = new ConcurrentDictionary<string, ListViewGroup>();
_changedConfigurationContinuation = new ScheduledContinuation();
_random = new Random();
_listViewItemProgress = new Progress<ImageListViewItemProgress<ListViewItem>>();
_quickImageListViewProgress =
new Progress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>>();
_quickImageDatabase = new QuickImageDatabase(_cancellationToken);
_tagManager = new TagManager(_fileMutex);
_imageTool = new ImageTool();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Conditional(condition => MemorySinkEnabled, configureSink => configureSink.Sink(_memorySink))
.WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"),
rollingInterval: RollingInterval.Day)
.CreateLogger();
// Start application update.
var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName;
var icon = Icon.ExtractAssociatedIcon(manifestModuleName);
_sparkle = new SparkleUpdater("https://quickimage.grimore.org/update/appcast.xml",
new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY="))
{
UIFactory = new UIFactory(icon),
RelaunchAfterUpdate = true
};
_sparkle.StartLoop(true, true);
}
public Form1(Mutex mutex) : this()
{
}
private async Task SortImageListView(IComparer<ListViewItem> comparer)
{
var taskCompletionSources = new[] { new TaskCompletionSource<object>(), new TaskCompletionSource<object>() };
try
{
await _imageListViewLock.WaitAsync(_cancellationToken);
}
catch
{
return;
}
toolStripStatusLabel1.Text = "Sorting...";
toolStripProgressBar1.Style = ProgressBarStyle.Marquee;
toolStripProgressBar1.MarqueeAnimationSpeed = 30;
try
{
var images = imageListView.Items.OfType<ListViewItem>().ToList();
if (images.Count == 0) return;
imageListView.Items.Clear();
#pragma warning disable CS4014
Task.Factory.StartNew(() =>
#pragma warning restore CS4014
{
images.Sort(comparer);
taskCompletionSources[0].TrySetResult(new { });
}, _cancellationToken);
await taskCompletionSources[0].Task;
imageListView.InvokeIfRequired(view =>
{
view.BeginUpdate();
foreach (var item in images)
{
var directoryName = Path.GetDirectoryName(item.Name);
if (!_imageListViewGroupDictionary.TryGetValue(directoryName, out var group))
{
group = new ListViewGroup(directoryName, HorizontalAlignment.Left) { Name = directoryName };
_imageListViewGroupDictionary.TryAdd(directoryName, group);
}
item.Group = group;
}
view.Items.AddRange(images.ToArray());
view.EndUpdate();
taskCompletionSources[1].TrySetResult(new { });
});
await taskCompletionSources[1].Task;
}
catch (Exception exception)
{
Log.Error(exception, "Failed to sort.");
}
finally
{
this.InvokeIfRequired(form =>
{
toolStripStatusLabel1.Text = "Sorting complete.";
form.toolStripProgressBar1.MarqueeAnimationSpeed = 0;
});
_imageListViewLock.Release();
}
}
private async Task BalanceTags(IEnumerable<ListViewItem> items)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
var count = enumerable.Length;
var bufferBlock = new BufferBlock<(string Name, ConcurrentBag<string> Tags)>(new DataflowBlockOptions{ CancellationToken = _cancellationToken });
var broadcastBlock = new BroadcastBlock<(string Name, ConcurrentBag<string> Tags)>(x => x, new DataflowBlockOptions { CancellationToken = _cancellationToken });
var databaseActionBlock = new ActionBlock<(string Name, ConcurrentBag<string> Tags)>(
async file =>
{
await _quickImageDatabase.AddTagsAsync(file.Name, file.Tags, _cancellationToken);
await _tagManager.AddIptcKeywords(file.Name, file.Tags, _cancellationToken);
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
var taggingActionBlock = new ActionBlock<(string Name, ConcurrentBag<string> Tags)>(file =>
{
foreach (var tag in file.Tags)
{
if (tagListView.Items.ContainsKey(tag))
{
tagListView.BeginUpdate();
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
return Task.CompletedTask;
}
tagListView.BeginUpdate();
tagListView.Items.Add(new ListViewItem(tag) { Name = tag });
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
}
return Task.CompletedTask;
},
new ExecutionDataflowBlockOptions
{ CancellationToken = _cancellationToken, TaskScheduler = _formTaskScheduler });
using var bufferBroadcastLink =
bufferBlock.LinkTo(broadcastBlock, new DataflowLinkOptions { PropagateCompletion = true });
using var broadcastDatabaseLink = broadcastBlock.LinkTo(databaseActionBlock,
new DataflowLinkOptions { PropagateCompletion = true });
using var broadcastTaggingLink = broadcastBlock.LinkTo(taggingActionBlock,
new DataflowLinkOptions { PropagateCompletion = true });
try
{
this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = "Balancing tags..."; });
var tags = new ConcurrentBag<string>();
foreach (var item in enumerable)
{
await foreach (var tag in _quickImageDatabase.GetTags(item.Name, _cancellationToken).WithCancellation(_cancellationToken))
{
tags.Add(tag);
}
}
var tasks = new List<Task>();
foreach (var item in enumerable)
{
tasks.Add(bufferBlock.SendAsync((item.Name, Tags: tags), _cancellationToken));
}
await Task.WhenAll(tasks);
bufferBlock.Complete();
await bufferBlock.Completion;
await databaseActionBlock.Completion;
await taggingActionBlock.Completion;
this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = "Tags balanced."; });
}
catch (Exception exception)
{
Log.Warning(exception, "Could not balance tags.");
this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = "Error balancing tags..."; });
}
}
private void SelectTags(IEnumerable<ListViewItem> items)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
var count = enumerable.Length;
var bufferBlock = new BufferBlock<string>();
async void IdleHandler(object idleHandlerSender, EventArgs idleHandlerArgs)
{
try
{
if (_combinedSelectionCancellationToken.IsCancellationRequested)
{
toolStripStatusLabel1.Text = "Selection cancelled.";
Application.Idle -= IdleHandler;
return;
}
if (!await bufferBlock.OutputAvailableAsync(_combinedSelectionCancellationToken))
{
toolStripStatusLabel1.Text = "Items selected.";
Application.Idle -= IdleHandler;
return;
}
if (!bufferBlock.TryReceive(out var tag)) return;
if (tagListView.Items.ContainsKey(tag))
{
tagListView.BeginUpdate();
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
return;
}
tagListView.BeginUpdate();
tagListView.Items.Add(new ListViewItem(tag) { Name = tag });
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
}
catch (OperationCanceledException)
{
Application.Idle -= IdleHandler;
}
catch (Exception exception)
{
Application.Idle -= IdleHandler;
Log.Warning(exception, "Failed selecting tags for image.");
}
finally
{
_sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(50), () =>
{
tagListView.BeginUpdate();
tagListView.Sort();
tagListView.EndUpdate();
}, _formTaskScheduler, _cancellationToken);
}
}
if (count == 0)
{
if (_selectionCancellationTokenSource != null)
{
_selectionCancellationTokenSource.Cancel();
}
tagListView.BeginUpdate();
foreach (var item in tagListView.CheckedItems.OfType<ListViewItem>())
{
item.Checked = false;
}
tagListView.EndUpdate();
return;
}
if (_selectionCancellationTokenSource != null)
{
_selectionCancellationTokenSource.Cancel();
}
_selectionCancellationTokenSource = new CancellationTokenSource();
_selectionCancellationToken = _selectionCancellationTokenSource.Token;
_linkedSelectionCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _selectionCancellationToken);
_combinedSelectionCancellationToken = _linkedSelectionCancellationTokenSource.Token;
_combinedSelectionCancellationToken.Register(() => { Application.Idle -= IdleHandler; });
_selectionScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(50), async () =>
{
try
{
tagListView.InvokeIfRequired(view =>
{
view.BeginUpdate();
foreach (var item in view.CheckedItems.OfType<ListViewItem>())
{
item.Checked = false;
}
view.EndUpdate();
});
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = "Selecting items...";
Application.Idle += IdleHandler;
});
var tasks = new ConcurrentBag<Task>();
foreach (var item in enumerable)
{
await foreach (var tag in _quickImageDatabase.GetTags(item.Name, _combinedSelectionCancellationToken).WithCancellation(_combinedSelectionCancellationToken))
{
tasks.Add(bufferBlock.SendAsync(tag, _combinedSelectionCancellationToken));
}
}
await Task.WhenAll(tasks);
bufferBlock.Complete();
}
catch (Exception exception)
{
this.InvokeIfRequired(form =>
{
toolStripStatusLabel1.Text = "Failed selecting items.";
Application.Idle -= IdleHandler;
});
Log.Warning(exception, "Could not select items.");
}
}, _combinedSelectionCancellationToken);
}
private void RelocateTo(IEnumerable<ListViewItem> items, string destinationDirectory,
CancellationToken cancellationToken)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = enumerable.Length;
toolStripProgressBar1.Value = 0;
var transformBlock =
new TransformBlock<(ListViewItem Item, string File), (ListViewItem Item, Database.QuickImage Image)>(
async tuple =>
{
try
{
var (item, path) = tuple;
var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken);
var name = Path.GetFileName(item.Name);
var file = Path.Combine(path, $"{name}");
if (!await _quickImageDatabase.SetFile(item.Name, file, cancellationToken))
return (null, null);
var newImage = new Database.QuickImage(file, image.Hash, image.Tags, image.Thumbnail);
return (Item: item, Image: newImage);
}
catch
{
return (null, null);
}
}, new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken });
var actionBlock = new ActionBlock<(ListViewItem Item, Database.QuickImage Image)>(async tuple =>
{
imageListView.BeginUpdate();
try
{
var (item, image) = tuple;
if (item == null || image == null) return;
imageListView.Items.Remove(item);
var fileInfo = new FileInfo(image.File);
if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group))
{
group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left)
{ Name = fileInfo.DirectoryName };
_imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group);
imageListView.Groups.Add(group);
}
largeImageList.Images.RemoveByKey(item.Name);
largeImageList.Images.Add(image.File, image.Thumbnail);
var listViewItem = imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName)
{
Name = image.File,
ImageKey = image.File,
Text = fileInfo.Name,
Group = group
});
imageListView.EnsureVisible(listViewItem.Index);
toolStripStatusLabel1.Text = "Relocating image directory...";
toolStripProgressBar1.Increment(1);
}
catch
{
toolStripStatusLabel1.Text = "Failed to relocate image to directory...";
toolStripProgressBar1.Increment(1);
}
finally
{
imageListView.EndUpdate();
}
},
new ExecutionDataflowBlockOptions
{ CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler });
toolStripStatusLabel1.Text = "Relocating images...";
#pragma warning disable CS4014
Task.Factory.StartNew(async () =>
#pragma warning restore CS4014
{
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
try
{
using var _1 = transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
var tasks = new ConcurrentBag<Task>();
foreach (var item in enumerable)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
var task = transformBlock.SendAsync((Item: item, File: destinationDirectory), cancellationToken);
tasks.Add(task);
}
await Task.WhenAll(tasks);
transformBlock.Complete();
await actionBlock.Completion;
}
finally
{
_imageListViewLock.Release();
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
private void RemoveImages(IEnumerable<ListViewItem> items, CancellationToken cancellationToken)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = enumerable.Length;
toolStripProgressBar1.Value = 0;
var bufferBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions { CancellationToken = cancellationToken });
var actionBlock = new ActionBlock<ListViewItem>(listViewItem =>
{
toolStripStatusLabel1.Text = $"Unloading image {listViewItem.Name}";
imageListView.Items.Remove(listViewItem);
toolStripProgressBar1.Increment(1);
},
new ExecutionDataflowBlockOptions{ CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler });
toolStripStatusLabel1.Text = "Unloading images...";
#pragma warning disable CS4014
Task.Factory.StartNew(async () =>
#pragma warning restore CS4014
{
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
try
{
using var _ = bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
foreach (var item in enumerable)
{
if (cancellationToken.IsCancellationRequested) return;
try
{
if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken))
{
continue;
}
await bufferBlock.SendAsync(item, cancellationToken);
}
catch
{
// ignored
}
}
bufferBlock.Complete();
await actionBlock.Completion;
}
finally
{
_imageListViewLock.Release();
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
public async Task RemoveMissingAsync(IEnumerable<string> groups, CancellationToken cancellationToken)
{
var enumerable = groups as string[] ?? groups.ToArray();
var bufferBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions
{ CancellationToken = cancellationToken });
var transformBlock = new TransformBlock<ListViewItem, ListViewItem>(listViewItem =>
{
if (File.Exists(listViewItem.Name))
{
toolStripStatusLabel1.Text = $"Image {listViewItem.Name} exists.";
return null;
}
toolStripStatusLabel1.Text = $"Deleting image {listViewItem.Name}";
imageListView.Items.Remove(listViewItem);
return listViewItem;
},
new ExecutionDataflowBlockOptions
{ CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler });
var actionBlock = new ActionBlock<ListViewItem>(async item =>
{
if (item == null)
{
return;
}
try
{
await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken);
}
catch
{
// ignored
}
});
using var _1 = bufferBlock.LinkTo(transformBlock, new DataflowLinkOptions { PropagateCompletion = true });
using var _2 = transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
using var _3 = transformBlock.LinkTo(DataflowBlock.NullTarget<ListViewItem>());
toolStripStatusLabel1.Text = "Removing missing images...";
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
toolStripProgressBar1.Style = ProgressBarStyle.Marquee;
toolStripProgressBar1.MarqueeAnimationSpeed = 30;
try
{
var tasks = new ConcurrentBag<Task>();
foreach (var group in enumerable)
{
foreach (var item in imageListView.Items.OfType<ListViewItem>())
{
if (string.Equals(item.Group.Name, group, StringComparison.OrdinalIgnoreCase))
{
tasks.Add(bufferBlock.SendAsync(item, cancellationToken));
}
}
}
bufferBlock.Complete();
await Task.WhenAll(tasks);
await transformBlock.Completion.ContinueWith(_ => { actionBlock.Complete(); }, cancellationToken);
await actionBlock.Completion;
}
finally
{
toolStripProgressBar1.MarqueeAnimationSpeed = 0;
_imageListViewLock.Release();
toolStripStatusLabel1.Text = "Done removing images.";
}
}
private async Task LoadDataObjectAsync(IDataObject dataObject)
{
var inputBlock = new BufferBlock<(string File, Stream Data, Definition Mime)>(
new ExecutionDataflowBlockOptions
{ CancellationToken = _cancellationToken });
var transformBlock = new TransformBlock<(string File, Stream Data, Definition Mime), string>(async tuple =>
{
try
{
var (_, data, _) = tuple;
data.Position = 0L;
var path = Path.GetTempPath();
var name = Path.GetTempFileName();
using var memoryStream = new MemoryStream();
switch (Configuration.InboundDragDrop.DragDropConvertType)
{
case "image/jpeg":
{
using var convertStream =
await _imageTool.ConvertTo(data, MagickFormat.Jpeg, _cancellationToken);
await convertStream.CopyToAsync(memoryStream);
name = Path.ChangeExtension(name, "jpg");
break;
}
case "image/png":
{
using var convertStream =
await _imageTool.ConvertTo(data, MagickFormat.Png, _cancellationToken);
await convertStream.CopyToAsync(memoryStream);
name = Path.ChangeExtension(name, "png");
break;
}
case "image/bmp":
{
using var convertStream =
await _imageTool.ConvertTo(data, MagickFormat.Bmp, _cancellationToken);
await convertStream.CopyToAsync(memoryStream);
name = Path.ChangeExtension(name, "bmp");
break;
}
case "image/gif":
{
using var convertStream =
await _imageTool.ConvertTo(data, MagickFormat.Gif, _cancellationToken);
await convertStream.CopyToAsync(memoryStream);
name = Path.ChangeExtension(name, "gif");
break;
}
// create a copy for files that do not have to be converted
default:
throw new ArgumentException(
"Unsupported conversion type for image.");
}
var destinationFile = Path.Combine(path, name);
memoryStream.Position = 0L;
await Miscellaneous.CopyFileAsync(memoryStream, destinationFile, _cancellationToken);
return destinationFile;
}
catch (Exception exception)
{
Log.Warning(exception, "Unable to convert input file.");
return null;
}
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
var copyTransformBlock = new TransformBlock<(string File, Stream Data, Definition Mime), string>(
async tuple =>
{
try
{
var (_, data, mime) = tuple;
data.Position = 0L;
var path = Path.GetTempPath();
var name = Path.GetTempFileName();
var extension = mime.File.Extensions.FirstOrDefault();
name = Path.ChangeExtension(name, extension);
var destinationFile = Path.Combine(path, name);
await Miscellaneous.CopyFileAsync(data, destinationFile, _cancellationToken);
return destinationFile;
}
catch (Exception exception)
{
Log.Warning(exception, "Unable to create a copy of the file.");
return null;
}
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
var importTransformBlock =
new TransformBlock<(string File, Stream Data, Definition Mime), string>(
tuple => Task.FromResult(tuple.File),
new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
var outputBlock = new BufferBlock<string>(new ExecutionDataflowBlockOptions
{ CancellationToken = _cancellationToken });
using var _1 = inputBlock.LinkTo(transformBlock,
tuple => Configuration.InboundDragDrop.ConvertOnDragDrop &&
Configuration.SupportedFormats.IsSupportedImage(tuple.Mime.File.MimeType) &&
!Configuration.InboundDragDrop.DragDropConvertExclude
.IsExcludedImage(tuple.Mime.File.MimeType));
using var _2 = transformBlock.LinkTo(outputBlock, file => !string.IsNullOrEmpty(file));
using var _3 = transformBlock.LinkTo(DataflowBlock.NullTarget<string>());
using var _4 = inputBlock.LinkTo(copyTransformBlock, tuple => Configuration.InboundDragDrop.CopyOnDragDrop);
using var _5 = copyTransformBlock.LinkTo(outputBlock);
using var _6 = copyTransformBlock.LinkTo(DataflowBlock.NullTarget<string>());
using var _7 = inputBlock.LinkTo(importTransformBlock,
tuple => !Configuration.InboundDragDrop.ConvertOnDragDrop &&
!Configuration.InboundDragDrop.CopyOnDragDrop);
using var _8 = importTransformBlock.LinkTo(outputBlock);
using var _9 = importTransformBlock.LinkTo(DataflowBlock.NullTarget<string>());
var tasks = new List<Task>();
await foreach (var (file, data, mime) in GetDragDropFiles(dataObject, _magicMime, _cancellationToken))
{
if (!Configuration.SupportedFormats.IsSupported(mime.File.MimeType))
{
continue;
}
tasks.Add(inputBlock.SendAsync((File: file, Data: data, Mime: mime), _cancellationToken));
}
await Task.WhenAll(tasks);
inputBlock.Complete();
await inputBlock.Completion.ContinueWith(_ =>
{
transformBlock.Complete();
copyTransformBlock.Complete();
importTransformBlock.Complete();
}, _cancellationToken);
await Task.WhenAll(transformBlock.Completion, copyTransformBlock.Completion, importTransformBlock.Completion).ContinueWith(_ => { outputBlock.Complete(); }, _cancellationToken);
var set = new HashSet<string>();
while (await outputBlock.OutputAvailableAsync(_cancellationToken))
{
if (!outputBlock.TryReceiveAll(out var items))
{
continue;
}
set.UnionWith(items);
}
await LoadFilesAsync(set, _magicMime, _cancellationToken);
}
private async Task LoadFilesAsync(IEnumerable<string> files, MagicMime magicMime,
CancellationToken cancellationToken)
{
var enumerable = files as string[] ?? files.ToArray();
var updateImageListViewActionBlock = new ActionBlock<Database.QuickImage>(
async tuple =>
{
try
{
var (file, tags, thumbnail) = (tuple.File, tuple.Tags, tuple.Thumbnail);
if (imageListView.Items.ContainsKey(file))
{
toolStripStatusLabel1.Text = $"File {file} already exits.";
return;
}
if (!largeImageList.Images.ContainsKey(file))
{
largeImageList.Images.Add(file, thumbnail);
}
var fileInfo = new FileInfo(file);
if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group))
{
group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) { Name = fileInfo.DirectoryName };
_imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group);
imageListView.Groups.Add(group);
}
var imageListViewItem = new ListViewItem(file) { Name = file, ImageKey = file, Text = fileInfo.Name, Group = group };
imageListView.Items.Add(imageListViewItem);
imageListView.EnsureVisible(imageListViewItem.Index);
toolStripStatusLabel1.Text = $"Added file {file} to the list.";
foreach (var tag in tags)
{
if (!_tagAutoCompleteStringCollection.Contains(tag))
{
_tagAutoCompleteStringCollection.Add(tag);
}
if (tagListView.Items.ContainsKey(tag))
{
continue;
}
tagListView.BeginUpdate();
tagListView.Items.Add(new ListViewItem(tag) { Name = tag });
tagListView.EndUpdate();
}
}
catch (Exception exception)
{
Log.Warning(exception, "Could not update image list view.");
}
},
new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler });
var fileInputBlock = new BufferBlock<string>(new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken });
var updateImageTagsTransformBlock = new TransformBlock<string, Database.QuickImage>(async file =>
{
try
{
var tags = new HashSet<string>();
var databaseTags = await _quickImageDatabase.GetTags(file, cancellationToken).ToArrayAsync(cancellationToken);
tags.UnionWith(databaseTags);
var mime = await magicMime.GetMimeType(file, cancellationToken);
if (Configuration.SupportedFormats.IsSupportedImage(mime))
{
await foreach (var iptcTag in _tagManager.GetIptcKeywords(file, cancellationToken))
{
tags.UnionWith(new[] { iptcTag });
}
}
await _quickImageDatabase.AddTagsAsync(file, tags, cancellationToken);
return await _quickImageDatabase.GetImageAsync(file, cancellationToken);
}
catch (Exception exception)
{
Log.Warning(exception, $"Could not add {file} to database.");
return null;
}
}, new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken });
var createImageTransformBlock = new TransformBlock<string, Database.QuickImage>(async file =>
{
try
{
var tags = Array.Empty<string>();
using var imageCollection = new MagickImageCollection(file, new MagickReadSettings { FrameIndex = 0, FrameCount = 1 });
var imageFrame = imageCollection[0];
var mime = await magicMime.GetMimeType(file, cancellationToken);
if (Configuration.SupportedFormats.IsSupportedImage(mime))
{
var iptcTags = _tagManager.GetIptcKeywords(imageFrame);
tags = iptcTags.ToArray();
}
var buffer = imageFrame.ToByteArray(MagickFormat.Bmp);
using var bitmapMemoryStream = new MemoryStream(buffer);
bitmapMemoryStream.Position = 0L;
using var hashBitmap = (Bitmap)Image.FromStream(bitmapMemoryStream);
var hash = ImagePhash.ComputeDigest(hashBitmap.ToBitmap().ToLuminanceImage());
bitmapMemoryStream.Position = 0L;
using var thumbnailBitmap = await CreateThumbnail(bitmapMemoryStream, 128, 128, cancellationToken);
var thumbnail = new Bitmap(thumbnailBitmap);
thumbnailBitmap.Dispose();
await _quickImageDatabase.AddImageAsync(file, hash, tags, thumbnail, cancellationToken);
return new Database.QuickImage(file, hash, tags, thumbnail);
}
catch (Exception exception)
{
Log.Warning(exception, $"Could not add {file} to database.");
return null;
}
});
using var _2 = fileInputBlock.LinkTo(updateImageTagsTransformBlock, file =>
{
try
{
return _quickImageDatabase.Exists(file, cancellationToken);
}
catch (Exception exception)
{
Log.Warning(exception, $"Could not query database for file {file}");
return false;
}
});
using var _4 = updateImageTagsTransformBlock.LinkTo(updateImageListViewActionBlock, new DataflowLinkOptions { PropagateCompletion = true });
using var _5 =
updateImageTagsTransformBlock.LinkTo(DataflowBlock.NullTarget<Database.QuickImage>(), image =>
{
var r = image == null;
return r;
});
using var _3 = fileInputBlock.LinkTo(createImageTransformBlock, new DataflowLinkOptions { PropagateCompletion = true });
using var _6 = createImageTransformBlock.LinkTo(updateImageListViewActionBlock, new DataflowLinkOptions { PropagateCompletion = true });
using var _7 =
createImageTransformBlock.LinkTo(DataflowBlock.NullTarget<Database.QuickImage>(), image =>
{
var r = image == null;
return r;
});
toolStripStatusLabel1.Text = "Loading images...";
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
try
{
toolStripProgressBar1.Style = ProgressBarStyle.Marquee;
toolStripProgressBar1.MarqueeAnimationSpeed = 30;
var tasks = new ConcurrentBag<Task>();
foreach (var item in enumerable)
{
await foreach (var entry in GetFilesAsync(item, Configuration, magicMime, cancellationToken).WithCancellation(cancellationToken))
{
tasks.Add(fileInputBlock.SendAsync(entry, cancellationToken));
}
}
await Task.WhenAll(tasks);
fileInputBlock.Complete();
await updateImageListViewActionBlock.Completion;
}
finally
{
toolStripProgressBar1.MarqueeAnimationSpeed = 0;
_imageListViewLock.Release();
toolStripStatusLabel1.Text = "Done loading images.";
}
}
private async Task OcrImagesAsync(IEnumerable<ListViewItem> items, CancellationToken cancellationToken)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = enumerable.Length;
toolStripProgressBar1.Value = 0;
void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess:
toolStripStatusLabel1.Text =
$"Added {imageListViewItemProgressSuccess.Tags.Count()} to image {imageListViewItemProgressSuccess.Item.Name} using OCR.";
foreach (var tag in imageListViewItemProgressSuccess.Tags)
{
if (tagListView.Items.ContainsKey(tag))
{
tagListView.BeginUpdate();
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
continue;
}
tagListView.BeginUpdate();
tagListView.Items.Add(new ListViewItem(tag) { Name = tag });
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
}
break;
case ImageListViewItemProgressFailure<ListViewItem> _:
break;
}
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
{
_listViewItemProgress.ProgressChanged -= QuickImageListViewItemProgress;
}
}
toolStripStatusLabel1.Text = "Settings text in images to tags using OCR...";
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
_listViewItemProgress.ProgressChanged += QuickImageListViewItemProgress;
try
{
await OcrImages(enumerable, _listViewItemProgress, cancellationToken);
}
catch (Exception exception)
{
Log.Error(exception, "Error while scanning text in images.");
_listViewItemProgress.ProgressChanged -= QuickImageListViewItemProgress;
_imageListViewLock.Release();
}
}
private async Task ConvertImagesAsync(IEnumerable<ListViewItem> items, string extension,
CancellationToken cancellationToken)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = enumerable.Length;
toolStripProgressBar1.Value = 0;
void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)> imageListViewItemProgressSuccess:
if (imageListViewItemProgressSuccess.Item is { } tuple)
{
var (item, image) = tuple;
imageListView.BeginUpdate();
try
{
imageListView.Items.Remove(item);
var fileInfo = new FileInfo(image.File);
if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group))
{
group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left)
{ Name = fileInfo.DirectoryName };
_imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group);
imageListView.Groups.Add(group);
}
largeImageList.Images.RemoveByKey(item.Name);
largeImageList.Images.Add(image.File, image.Thumbnail);
imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName)
{
Name = image.File,
ImageKey = image.File,
Text = fileInfo.Name,
Group = group
});
}
finally
{
imageListView.EndUpdate();
}
}
break;
case ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)> _:
break;
}
toolStripStatusLabel1.Text = "Converting images...";
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
{
_quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress;
_imageListViewLock.Release();
}
}
toolStripStatusLabel1.Text = "Converting images...";
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
_quickImageListViewProgress.ProgressChanged += QuickImageListViewItemProgress;
try
{
await ConvertImages(enumerable, extension, _quickImageListViewProgress, cancellationToken);
}
catch (Exception exception)
{
Log.Error(exception, "Error while converting images.");
_quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress;
_imageListViewLock.Release();
}
}
private async Task RenameImageAsync(ListViewItem item, string destinationFileName,
CancellationToken cancellationToken)
{
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = 1;
toolStripProgressBar1.Value = 0;
void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)>
imageListViewItemProgressSuccess:
if (imageListViewItemProgressSuccess.Item is { } tuple)
{
var (item, image) = tuple;
imageListView.BeginUpdate();
try
{
imageListView.Items.Remove(item);
var fileInfo = new FileInfo(image.File);
if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group))
{
group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) { Name = fileInfo.DirectoryName };
_imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group);
imageListView.Groups.Add(group);
}
largeImageList.Images.RemoveByKey(item.Name);
largeImageList.Images.Add(image.File, image.Thumbnail);
var listViewItem = imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName)
{
Name = image.File,
ImageKey = image.File,
Text = fileInfo.Name,
Group = group
});
imageListView.EnsureVisible(listViewItem.Index);
}
finally
{
imageListView.EndUpdate();
}
}
break;
case ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)> _:
break;
}
toolStripStatusLabel1.Text = "Renaming image...";
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
{
_quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress;
_imageListViewLock.Release();
}
}
toolStripStatusLabel1.Text = "Renaming image...";
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
_quickImageListViewProgress.ProgressChanged += QuickImageListViewItemProgress;
try
{
var directoryName = Path.GetDirectoryName(item.Name);
await RenameImage(item, Path.Combine(directoryName, destinationFileName), _quickImageListViewProgress, cancellationToken);
}
catch (Exception exception)
{
Log.Error(exception, "Error while renaming image.");
_quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress;
_imageListViewLock.Release();
}
}
private async Task MoveImagesAsync(IEnumerable<ListViewItem> items, string destinationDirectory,
CancellationToken cancellationToken)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = enumerable.Length;
toolStripProgressBar1.Value = 0;
void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)> imageListViewItemProgressSuccess:
if (imageListViewItemProgressSuccess.Item is { } tuple)
{
var (item, image) = tuple;
imageListView.BeginUpdate();
try
{
imageListView.Items.Remove(item);
var fileInfo = new FileInfo(image.File);
if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group))
{
group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left)
{ Name = fileInfo.DirectoryName };
_imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group);
imageListView.Groups.Add(group);
}
largeImageList.Images.RemoveByKey(item.Name);
largeImageList.Images.Add(image.File, image.Thumbnail);
var listViewItem = imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName)
{
Name = image.File,
ImageKey = image.File,
Text = fileInfo.Name,
Group = group
});
imageListView.EnsureVisible(listViewItem.Index);
}
finally
{
imageListView.EndUpdate();
}
}
break;
case ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)> _:
break;
}
toolStripStatusLabel1.Text = "Moving images...";
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
{
_quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress;
_imageListViewLock.Release();
}
}
toolStripStatusLabel1.Text = "Moving images...";
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
_quickImageListViewProgress.ProgressChanged += QuickImageListViewItemProgress;
try
{
await MoveImages(enumerable, destinationDirectory, _quickImageListViewProgress, cancellationToken);
}
catch
{
_quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress;
_imageListViewLock.Release();
}
}
private void DeleteImages(IEnumerable<ListViewItem> items, CancellationToken cancellationToken)
{
var enumerable = items as ListViewItem[] ?? items.ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = enumerable.Length;
toolStripProgressBar1.Value = 0;
var bufferBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions
{ CancellationToken = cancellationToken });
var actionBlock = new ActionBlock<ListViewItem>(listViewItem =>
{
toolStripStatusLabel1.Text = $"Deleting image {listViewItem.Name}";
imageListView.Items.Remove(listViewItem);
},
new ExecutionDataflowBlockOptions
{ CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler });
toolStripStatusLabel1.Text = "Deleting images...";
#pragma warning disable CS4014
Task.Factory.StartNew(async () =>
#pragma warning restore CS4014
{
try
{
await _imageListViewLock.WaitAsync(cancellationToken);
}
catch
{
return;
}
try
{
using var _ = bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
foreach (var item in enumerable)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
File.Delete(item.Name);
if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken))
{
continue;
}
await bufferBlock.SendAsync(item, cancellationToken);
}
catch
{
// ignored
}
}
bufferBlock.Complete();
await actionBlock.Completion;
}
finally
{
_imageListViewLock.Release();
}
});
}
private async Task BeginSearch(string text)
{
var keywords = new Csv(text).Select(tag => tag.Trim()).Where(tag => !string.IsNullOrEmpty(tag)).ToArray();
try
{
await _imageListViewLock.WaitAsync(_cancellationToken);
}
catch
{
return;
}
if (_selectionCancellationTokenSource != null)
{
_selectionCancellationTokenSource.Cancel();
}
imageListView.InvokeIfRequired(view => { imageListView.BeginUpdate(); });
try
{
var taskCompletionSource = new TaskCompletionSource<object>();
imageListView.InvokeIfRequired(view =>
{
foreach (var item in view.Items.OfType<ListViewItem>())
{
_searchStore.TryAdd(item.Name, item);
}
view.Items.Clear();
taskCompletionSource.TrySetResult(new { });
});
await taskCompletionSource.Task;
await foreach (var quickImage in _quickImageDatabase.Search(keywords, _quickImageSearchType, _quickImageSearchParameters, _combinedSearchSelectionCancellationToken).WithCancellation(_combinedSearchSelectionCancellationToken))
{
if (!_searchStore.TryGetValue(quickImage.File, out var item))
{
continue;
}
var directoryName = Path.GetDirectoryName(item.Name);
if (_imageListViewGroupDictionary.TryGetValue(directoryName, out var group))
{
item.Group = group;
}
imageListView.InvokeIfRequired(view => { view.Items.Add(item); });
}
}
catch (Exception exception)
{
Log.Error(exception, "Error while searching.");
}
finally
{
_imageListViewLock.Release();
imageListView.InvokeIfRequired(view => { imageListView.EndUpdate(); });
}
}
private async Task EndSearch()
{
try
{
await _imageListViewLock.WaitAsync(_combinedSearchSelectionCancellationToken);
}
catch
{
return;
}
if (_selectionCancellationTokenSource != null)
{
_selectionCancellationTokenSource.Cancel();
}
imageListView.InvokeIfRequired(view => { imageListView.BeginUpdate(); });
try
{
toolStripStatusLabel1.Text = "Restoring items.";
var taskCompletionSource = new TaskCompletionSource<object>();
imageListView.InvokeIfRequired(view =>
{
view.BeginUpdate();
view.Items.Clear();
var restore = new List<ListViewItem>();
foreach (var item in _searchStore)
{
var (name, listViewItem) = (item.Key, item.Value);
var directoryName = Path.GetDirectoryName(name);
if (!_imageListViewGroupDictionary.TryGetValue(directoryName, out var group))
{
group = new ListViewGroup(directoryName, HorizontalAlignment.Left) { Name = directoryName };
_imageListViewGroupDictionary.TryAdd(directoryName, group);
}
listViewItem.Group = group;
restore.Add(listViewItem);
}
view.Items.AddRange(restore.ToArray());
view.EndUpdate();
taskCompletionSource.TrySetResult(new { });
});
await taskCompletionSource.Task;
}
catch (Exception exception)
{
Log.Error(exception, "Unable to add back items after search.");
}
finally
{
_imageListViewLock.Release();
imageListView.InvokeIfRequired(view => { imageListView.EndUpdate(); });
}
}
private async Task OcrImages(IEnumerable<ListViewItem> items,
IProgress<ImageListViewItemProgress<ListViewItem>> progress,
CancellationToken cancellationToken)
{
using var engine = new TesseractEngine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tessdata"), "eng", EngineMode.Default);
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested) return;
try
{
using var img = Pix.LoadFromFile(item.Name);
using var page = engine.Process(img);
var text = page.GetText();
var tags = new HashSet<string>();
if (string.IsNullOrEmpty(text))
{
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags));
continue;
}
foreach (var word in Regex.Split(text, @"[^\w]+", RegexOptions.Compiled))
{
if (string.IsNullOrEmpty(word)) continue;
tags.UnionWith(new[] { word });
}
if (!tags.Any())
{
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags));
continue;
}
if (!await _quickImageDatabase.AddTagsAsync(item.Name, tags, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags));
}
catch (Exception exception)
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception));
}
}
}
private async Task ConvertImages(IEnumerable<ListViewItem> items, string extension,
IProgress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>> progress,
CancellationToken cancellationToken)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken);
var path = Path.GetDirectoryName(item.Name);
var name = Path.GetFileNameWithoutExtension(item.Name);
var file = Path.Combine(path, $"{name}.{extension}");
using var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write);
switch (extension)
{
case "jpg":
{
using var convertStream =
await _imageTool.ConvertTo(item.Name, MagickFormat.Jpeg, _cancellationToken);
await convertStream.CopyToAsync(fileStream);
break;
}
case "png":
{
using var convertStream =
await _imageTool.ConvertTo(item.Name, MagickFormat.Png, _cancellationToken);
await convertStream.CopyToAsync(fileStream);
break;
}
case "bmp":
{
using var convertStream =
await _imageTool.ConvertTo(item.Name, MagickFormat.Bmp, _cancellationToken);
await convertStream.CopyToAsync(fileStream);
break;
}
case "gif":
{
using var convertStream =
await _imageTool.ConvertTo(item.Name, MagickFormat.Gif, _cancellationToken);
await convertStream.CopyToAsync(fileStream);
break;
}
}
if (!await _quickImageDatabase.RemoveImageAsync(image, cancellationToken))
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
File.Delete(item.Name);
var newImage = new Database.QuickImage(file, image.Hash, image.Tags, image.Thumbnail);
if (!await _quickImageDatabase.AddImageAsync(newImage, cancellationToken))
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
progress.Report(
new ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)>((
Item: item, Image: newImage)));
}
catch (Exception exception)
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: null), exception));
}
}
}
private async Task RenameImage(ListViewItem item, string destinationFileName,
IProgress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>> progress,
CancellationToken cancellationToken)
{
try
{
await Miscellaneous.CopyFileAsync(item.Name, destinationFileName, cancellationToken);
File.Delete(item.Name);
var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken);
if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken))
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
return;
}
var destinationImage =
new Database.QuickImage(destinationFileName, image.Hash, image.Tags, image.Thumbnail);
if (!await _quickImageDatabase.AddImageAsync(destinationImage, cancellationToken))
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: destinationImage),
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
return;
}
progress.Report(
new ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: destinationImage)));
}
catch (Exception exception)
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: null), exception));
}
}
private async Task MoveImages(IEnumerable<ListViewItem> items, string destinationDirectory,
IProgress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>> progress,
CancellationToken cancellationToken)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
var fileName = Path.GetFileName(item.Name);
var destinationFile = Path.Combine(destinationDirectory, fileName);
await Miscellaneous.CopyFileAsync(item.Name, destinationFile, cancellationToken);
File.Delete(item.Name);
var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken);
if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken))
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
var destinationImage =
new Database.QuickImage(destinationFile, image.Hash, image.Tags, image.Thumbnail);
if (!await _quickImageDatabase.AddImageAsync(destinationImage, cancellationToken))
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: destinationImage),
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
progress.Report(
new ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: destinationImage)));
}
catch (Exception exception)
{
progress.Report(
new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>(
(Item: item, Image: null), exception));
}
}
}
private async Task GetTags(IReadOnlyList<ListViewItem> items,
IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
var tags = await _quickImageDatabase.GetTags(item.Name, cancellationToken)
.ToArrayAsync(cancellationToken);
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags));
}
catch (Exception exception)
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception));
}
}
}
private async Task BalanceImageTags(IReadOnlyList<ListViewItem> items, MagicMime magicMime,
IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
var tags = await _quickImageDatabase.GetTags(item.Name, cancellationToken).ToArrayAsync(cancellationToken);
var mime = await magicMime.GetMimeType(item.Name, cancellationToken);
if (Configuration.SupportedFormats.IsSupportedImage(mime))
{
if (!await _tagManager.AddIptcKeywords(item.Name, tags, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
}
var merge = new HashSet<string>(tags);
if (Configuration.SupportedFormats.Images.Image.Contains(mime))
await foreach (var iptcTag in _tagManager.GetIptcKeywords(item.Name, cancellationToken))
merge.UnionWith(new[] { iptcTag });
if (!await _quickImageDatabase.AddTagsAsync(item.Name, merge, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item,
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, merge));
}
catch (Exception exception)
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception));
}
}
}
private async Task AddTags(IEnumerable<ListViewItem> items, IReadOnlyList<string> keywords, MagicMime magicMime,
IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
var mime = await magicMime.GetMimeType(item.Name, cancellationToken);
if (Configuration.SupportedFormats.IsSupportedImage(mime))
{
if (!await _tagManager.AddIptcKeywords(item.Name, keywords, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
}
if (!await _quickImageDatabase.AddTagsAsync(item.Name, keywords, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item,
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, keywords, true));
}
catch (Exception exception)
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception));
}
}
}
private async Task StripTags(IEnumerable<ListViewItem> items, MagicMime magicMime,
IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
var mime = await magicMime.GetMimeType(item.Name, cancellationToken);
if (Configuration.SupportedFormats.IsSupportedImage(mime))
{
if (!await _tagManager.StripIptcProfile(item.Name, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item,
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
}
var tags = await _quickImageDatabase.GetTags(item.Name, cancellationToken).ToArrayAsync(cancellationToken);
if (tags.Length != 0)
if (!await _quickImageDatabase.StripTagsAsync(item.Name, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item,
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags, false));
}
catch (Exception exception)
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception));
}
}
}
private async Task RemoveTags(IEnumerable<ListViewItem> items, IReadOnlyList<string> keywords,
MagicMime magicMime, IProgress<ImageListViewItemProgress<ListViewItem>> progress,
CancellationToken cancellationToken)
{
foreach (var item in items)
{
if (cancellationToken.IsCancellationRequested)
{
return;
}
try
{
var mime = await magicMime.GetMimeType(item.Name, cancellationToken);
if (Configuration.SupportedFormats.IsSupportedImage(mime))
{
if (!await _tagManager.RemoveIptcKeywords(item.Name, keywords, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item,
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
}
if (!await _quickImageDatabase.RemoveTagsAsync(item.Name, keywords, cancellationToken))
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item,
new ArgumentException(MethodBase.GetCurrentMethod()?.Name)));
continue;
}
progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, keywords, false));
}
catch (Exception exception)
{
progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception));
}
}
}
private void collapseToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>();
var listViewItems = items as ListViewItem[] ?? items.ToArray();
foreach(var item in listViewItems)
{
var group = item.Group;
if (!imageListView.GetCollapsed(group))
{
imageListView.SetCollapsed(group, true);
}
}
}
private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e)
{
foreach (var group in _imageListViewGroupDictionary.Values)
{
if (!imageListView.GetCollapsed(group))
{
imageListView.SetCollapsed(group, true);
}
}
}
private void expandAllToolStripMenuItem_Click(object sender, EventArgs e)
{
foreach (var group in _imageListViewGroupDictionary.Values)
{
if (imageListView.GetCollapsed(group))
{
imageListView.SetCollapsed(group, false);
}
}
}
private void Form1_Closing(object sender, FormClosingEventArgs e)
{
}
private async void creationTimeAscendingSortMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(
new DateImageListViewSorter(SortOrder.Ascending, DateImageListViewSorterType.Creation));
}
private async void creationTimeDescendingSortMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(
new DateImageListViewSorter(SortOrder.Descending, DateImageListViewSorterType.Creation));
}
private async void accessTimeAscendingSortMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(
new DateImageListViewSorter(SortOrder.Ascending, DateImageListViewSorterType.Access));
}
private async void accessTimeDescendingSortMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(new DateImageListViewSorter(SortOrder.Descending,
DateImageListViewSorterType.Access));
}
private async void modificationTimeAscendingSortMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(new DateImageListViewSorter(SortOrder.Ascending,
DateImageListViewSorterType.Modification));
}
private async void modificationTimeDescendingSortMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(new DateImageListViewSorter(SortOrder.Descending,
DateImageListViewSorterType.Modification));
}
private void renameToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_renameForm != null)
{
return;
}
var item = imageListView.SelectedItems.OfType<ListViewItem>().FirstOrDefault();
if (item == null)
{
return;
}
_renameForm = new RenameForm(item);
_renameForm.Rename += RenameForm_Rename;
_renameForm.Closing += RenameForm_Closing;
_renameForm.Show();
}
private async void RenameForm_Rename(object sender, RenameForm.RenameEventArgs e)
{
await RenameImageAsync(e.ListViewItem, e.FileName, _cancellationToken);
}
private void RenameForm_Closing(object sender, CancelEventArgs e)
{
if (_renameForm == null)
{
return;
}
_renameForm.Closing -= RenameForm_Closing;
_renameForm.Dispose();
_renameForm = null;
}
private async void relocateToToolStropMenuItem_Click(object sender, EventArgs e)
{
var dialog = new CommonOpenFileDialog
{
AddToMostRecentlyUsedList = true,
Multiselect = false,
IsFolderPicker = true
};
var groupItems = new List<ListViewItem>();
if (dialog.ShowDialog() == CommonFileDialogResult.Ok)
{
foreach (var item in imageListView.SelectedItems.OfType<ListViewItem>())
{
foreach (var groupItem in item.Group.Items.OfType<ListViewItem>())
{
groupItems.Add(groupItem);
}
}
}
RelocateTo(groupItems, dialog.FileName, _cancellationToken);
}
private void imageListView_MouseDown(object sender, MouseEventArgs e)
{
}
private void checkBox2_CheckedChanged(object sender, EventArgs e)
{
var checkBox = (CheckBox)sender;
switch (checkBox.Checked)
{
case true:
_quickImageSearchParameters =
_quickImageSearchParameters | QuickImageSearchParameters.Metadata;
break;
case false:
_quickImageSearchParameters =
_quickImageSearchParameters & ~QuickImageSearchParameters.Metadata;
break;
}
var text = textBox1.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
if (_searchCancellationTokenSource != null)
{
_searchCancellationTokenSource.Cancel();
}
_searchCancellationTokenSource = new CancellationTokenSource();
_searchCancellationToken = _searchCancellationTokenSource.Token;
_linkedSearchCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken);
_combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token;
_searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken);
}
private void checkBox3_CheckedChanged(object sender, EventArgs e)
{
var checkBox = (CheckBox)sender;
switch (checkBox.Checked)
{
case true:
_quickImageSearchParameters =
_quickImageSearchParameters | QuickImageSearchParameters.Split;
break;
case false:
_quickImageSearchParameters =
_quickImageSearchParameters & ~QuickImageSearchParameters.Split;
break;
}
var text = textBox1.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
if (_searchCancellationTokenSource != null)
{
_searchCancellationTokenSource.Cancel();
}
_searchCancellationTokenSource = new CancellationTokenSource();
_searchCancellationToken = _searchCancellationTokenSource.Token;
_linkedSearchCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken);
_combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token;
_searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken);
}
private void checkBox2_VisibleChanged(object sender, EventArgs e)
{
var checkBox = (CheckBox)sender;
if (checkBox.Checked)
{
_quickImageSearchParameters = _quickImageSearchParameters | QuickImageSearchParameters.Metadata;
return;
}
_quickImageSearchParameters = _quickImageSearchParameters & ~QuickImageSearchParameters.Metadata;
}
private void checkBox3_VisibleChanged(object sender, EventArgs e)
{
var checkBox = (CheckBox)sender;
if (checkBox.Checked)
{
_quickImageSearchParameters = _quickImageSearchParameters | QuickImageSearchParameters.Split;
return;
}
_quickImageSearchParameters = _quickImageSearchParameters & ~QuickImageSearchParameters.Split;
}
private async void removeMissingToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>();
var listViewItems = items as ListViewItem[] ?? items.ToArray();
await RemoveMissingAsync(listViewItems.Select(item => item.Group.Name), _cancellationToken);
}
private async Task PerformUpgrade()
{
// Manually check for updates, this will not show a ui
var updateCheck = await _sparkle.CheckForUpdatesQuietly();
switch (updateCheck.Status)
{
case UpdateStatus.UserSkipped:
var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version;
updateCheck.Updates.Sort(UpdateComparer);
var latestVersion = updateCheck.Updates.FirstOrDefault();
if (latestVersion != null)
{
var availableVersion = new Version(latestVersion.Version);
if (availableVersion <= assemblyVersion)
{
return;
}
}
var decision = MessageBox.Show(
"Update available but it has been previously skipped. Should the update proceed anyway?",
Assembly.GetExecutingAssembly().GetName().Name, MessageBoxButtons.YesNo,
MessageBoxIcon.Asterisk,
MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
if (decision.HasFlag(DialogResult.No))
{
return;
}
goto default;
case UpdateStatus.UpdateNotAvailable:
MessageBox.Show("No updates available at this time.",
Assembly.GetExecutingAssembly().GetName().Name, MessageBoxButtons.OK,
MessageBoxIcon.Asterisk,
MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false);
break;
case UpdateStatus.CouldNotDetermine:
Log.Error("Could not determine the update availability status.");
break;
default:
_sparkle.ShowUpdateNeededUI();
break;
}
}
private static int UpdateComparer(AppCastItem x, AppCastItem y)
{
if (x == null) return 1;
if (y == null) return -1;
if (x == y) return 0;
return new Version(y.Version).CompareTo(new Version(x.Version));
}
private async void oCRTextToTagsToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>();
var listViewItems = items as ListViewItem[] ?? items.ToArray();
if (listViewItems.Length == 0)
{
return;
}
await OcrImagesAsync(listViewItems, _cancellationToken);
}
#region Static Methods
private static async IAsyncEnumerable<(string File, Stream Data, Definition Mime)> GetDragDropFiles(
IDataObject data, MagicMime magicMime, [EnumeratorCancellation] CancellationToken cancellationToken)
{
var files = (string[])data.GetData(DataFormats.FileDrop);
if (files != null)
{
foreach (var file in files)
{
var fileAttributes = File.GetAttributes(file);
if (fileAttributes.HasFlag(FileAttributes.Directory))
{
continue;
}
using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
var memoryStream = new MemoryStream();
await fileStream.CopyToAsync(memoryStream);
memoryStream.Position = 0L;
var mime = await magicMime.Identify(file, cancellationToken);
if (mime == null)
{
continue;
}
yield return (File: file, Data: memoryStream, Mime: mime.Definition);
}
yield break;
}
var fileNames = data.GetFileContentNames();
for (var i = 0; i < fileNames.Length; ++i)
{
var memoryStream = data.GetFileContent(i);
memoryStream.Position = 0L;
MagicMimeFile mime;
try
{
mime = magicMime.Identify(fileNames[i], memoryStream, cancellationToken);
if (mime == null)
{
continue;
}
}
catch(Exception exception)
{
Log.Error(exception, $"Could not determine mime type for file {fileNames[i]}.");
continue;
}
yield return (File: fileNames[0], Data: memoryStream, Mime: mime.Definition);
}
}
private static async IAsyncEnumerable<string> GetFilesAsync(string entry,
Configuration.Configuration configuration, MagicMime magicMime,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var bufferBlock = new BufferBlock<string>(new DataflowBlockOptions { CancellationToken = cancellationToken });
#pragma warning disable CS4014
Task.Run(async () =>
#pragma warning restore CS4014
{
try
{
var attributes = File.GetAttributes(entry);
if (attributes.HasFlag(FileAttributes.Directory))
{
var directoryFiles = Directory.GetFiles(entry);
foreach (var directoryFile in directoryFiles)
{
if (!File.Exists(directoryFile))
{
continue;
}
var fileMimeType = await magicMime.GetMimeType(directoryFile, cancellationToken);
if (!configuration.SupportedFormats.IsSupported(fileMimeType))
{
continue;
}
await bufferBlock.SendAsync(directoryFile, cancellationToken);
}
return;
}
var entryMimeType = await magicMime.GetMimeType(entry, cancellationToken);
if (!configuration.SupportedFormats.IsSupported(entryMimeType))
{
return;
}
await bufferBlock.SendAsync(entry, cancellationToken);
}
finally
{
bufferBlock.Complete();
}
}, cancellationToken);
while (await bufferBlock.OutputAvailableAsync(cancellationToken))
{
if (!bufferBlock.TryReceiveAll(out var files))
{
continue;
}
foreach (var file in files)
{
yield return file;
}
}
}
public static async Task SaveConfiguration(Configuration.Configuration configuration)
{
if (!Directory.Exists(Constants.UserApplicationDirectory))
Directory.CreateDirectory(Constants.UserApplicationDirectory);
switch (await Serialization.Serialize(configuration, Constants.ConfigurationFile, "Configuration",
"<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>",
CancellationToken.None))
{
case SerializationSuccess<Configuration.Configuration> _:
Log.Information("Configuration serialized successfully");
break;
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception.Message, "Configuration failed to serialize");
break;
}
}
public static async Task<Configuration.Configuration> LoadConfiguration()
{
if (!Directory.Exists(Constants.UserApplicationDirectory))
Directory.CreateDirectory(Constants.UserApplicationDirectory);
var deserializationResult =
await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile,
Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None);
switch (deserializationResult)
{
case SerializationSuccess<Configuration.Configuration> serializationSuccess:
return serializationSuccess.Result;
case SerializationFailure serializationFailure:
Log.Warning(serializationFailure.Exception, "Configuration failed to deserialize");
return new Configuration.Configuration();
default:
return new Configuration.Configuration();
}
}
private static async Task<Bitmap> CreateThumbnail(Stream file, uint width, uint height,
CancellationToken cancellationToken)
{
using var memoryStream = new MemoryStream();
using var imageCollection =
new MagickImageCollection(file, new MagickReadSettings { FrameIndex = 0, FrameCount = 1 });
var frame = imageCollection[0];
var scaleHeight = width / (float)frame.Height;
var scaleWidth = height / (float)frame.Width;
var scale = Math.Min(scaleHeight, scaleWidth);
width = (uint)(frame.Width * scale);
height = (uint)(frame.Height * scale);
var geometry = new MagickGeometry(width, height);
frame.Resize(geometry);
await frame.WriteAsync(memoryStream, MagickFormat.Bmp, cancellationToken);
using var image = new MagickImage(MagickColors.Transparent, 128, 128);
memoryStream.Position = 0L;
using var composite = await new MagickFactory().Image.CreateAsync(memoryStream, cancellationToken);
image.Composite(composite, Gravity.Center, CompositeOperator.Over);
using var outputStream = new MemoryStream();
await image.WriteAsync(outputStream, MagickFormat.Bmp, cancellationToken);
var optimizer = new ImageOptimizer { IgnoreUnsupportedFormats = true };
outputStream.Position = 0L;
optimizer.Compress(outputStream);
outputStream.Position = 0L;
return (Bitmap)Image.FromStream(outputStream, true);
}
private static int[] CreateHistogram(MemoryStream bitmapMemoryStream, CancellationToken cancellationToken,
int threads = 2)
{
using var bitmap = (Bitmap)Image.FromStream(bitmapMemoryStream);
var histogram = new int[0xFFFFFFFF];
histogram.Initialize();
var parallelOptions = new ParallelOptions
{ CancellationToken = cancellationToken, MaxDegreeOfParallelism = threads / 2 };
Parallel.For(0, bitmap.Width, parallelOptions, (x, state) =>
{
Parallel.For(0, bitmap.Height, parallelOptions, (y, state) =>
{
var value = bitmap.GetPixel(x, y).ToArgb();
histogram[value]++;
});
});
return histogram;
}
#endregion
#region Event Handlers
private void toolStripTextBox1_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode != Keys.Return)
{
return;
}
// Skip the beep.
e.Handled = true;
var toolStripTextBox = (ToolStripTextBox)sender;
var tagText = toolStripTextBox.Text;
toolStripTextBox.Clear();
if (string.IsNullOrEmpty(tagText))
{
return;
}
var keywords = new[] { tagText };
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
var count = items.Length;
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
toolStripProgressBar1.Value = 0;
void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess:
foreach (var tag in imageListViewItemProgressSuccess.Tags)
{
if (!_tagAutoCompleteStringCollection.Contains(tag))
_tagAutoCompleteStringCollection.Add(tag);
tagListView.BeginUpdate();
if (tagListView.Items.ContainsKey(tag))
{
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
_sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(1000), () =>
{
tagListView.BeginUpdate();
tagListView.Sort();
tagListView.EndUpdate();
}, _formTaskScheduler, _cancellationToken);
continue;
}
tagListView.Items.Add(new ListViewItem(tag) { Name = tag });
tagListView.Items[tag].Checked = true;
tagListView.EndUpdate();
_sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(1000), () =>
{
tagListView.BeginUpdate();
tagListView.Sort();
tagListView.EndUpdate();
}, _formTaskScheduler, _cancellationToken);
}
break;
case ImageListViewItemProgressFailure<ListViewItem> imageListViewItemProgressFailure:
break;
}
toolStripStatusLabel1.Text = "Adding tags...";
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
}
}
toolStripStatusLabel1.Text = "Adding tags...";
#pragma warning disable CS4014
Task.Factory.StartNew(async () =>
#pragma warning restore CS4014
{
_listViewItemProgress.ProgressChanged += ImageListViewItemProgress;
try
{
await AddTags(items, keywords, _magicMime, _listViewItemProgress, _cancellationToken);
}
catch
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
}
}, _cancellationToken);
}
private void contextMenuStrip1_Opened(object sender, EventArgs e)
{
tagTextBox.Focus();
}
private async void Form1_Load(object sender, EventArgs e)
{
JotFormTracker.Tracker.Track(this);
JotFormTracker.Tracker.Configure<Form1>()
.Properties(form => new { form.tabControl1.SelectedIndex });
JotFormTracker.Tracker.Configure<Form1>()
.Properties(form => new { form.splitContainer3.SplitterDistance });
JotFormTracker.Tracker.Configure<Form1>()
.Properties(form => new { form.radioButton1.Checked });
JotFormTracker.Tracker.Configure<Form1>()
.Properties(form => new { form.checkBox1.Checked, form.checkBox1.CheckState });
JotFormTracker.Tracker.Configure<Form1>()
.Properties(form => new { form.checkBox2.Checked, form.checkBox2.CheckState });
JotFormTracker.Tracker.Configure<Form1>()
.Properties(form => new { form.checkBox3.Checked, form.checkBox3.CheckState });
_tagListViewSorter = new TagListViewSorter();
tagListView.ListViewItemSorter = _tagListViewSorter;
_tagAutoCompleteStringCollection = new AutoCompleteStringCollection();
textBox1.AutoCompleteCustomSource = _tagAutoCompleteStringCollection;
tagTextBox.AutoCompleteCustomSource = _tagAutoCompleteStringCollection;
Configuration = await LoadConfiguration();
#pragma warning disable CS4014
PerformUpgrade();
#pragma warning restore CS4014
#pragma warning disable CS4014
Task.Factory.StartNew(async () =>
#pragma warning restore CS4014
{
try
{
await _imageListViewLock.WaitAsync(_cancellationToken);
}
catch
{
return;
}
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = "Loading images...";
form.toolStripProgressBar1.Style = ProgressBarStyle.Marquee;
form.toolStripProgressBar1.MarqueeAnimationSpeed = 30;
});
try
{
var imageListViewItems = new ConcurrentBag<ListViewItem>();
var tags = new HashSet<string>(StringComparer.Ordinal);
await foreach (var image in _quickImageDatabase.GetAll(_cancellationToken))
{
if (!largeImageList.Images.ContainsKey(image.File))
{
largeImageList.Images.Add(image.File, image.Thumbnail);
}
var fileInfo = new FileInfo(image.File);
if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group))
{
group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) { Name = fileInfo.DirectoryName };
_imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group);
imageListView.InvokeIfRequired(view =>
{
view.BeginUpdate();
view.Groups.Add(group);
view.EndUpdate();
});
}
tags.UnionWith(image.Tags);
var imageListViewItem = new ListViewItem(image.File)
{
Name = image.File,
ImageKey = image.File,
Text = fileInfo.Name,
Group = group
};
imageListViewItems.Add(imageListViewItem);
}
this.InvokeIfRequired(_ => { _tagAutoCompleteStringCollection.AddRange(tags.ToArray()); });
imageListView.InvokeIfRequired(view =>
{
view.BeginUpdate();
view.Items.AddRange(imageListViewItems.ToArray());
view.EndUpdate();
});
tagListView.InvokeIfRequired(view =>
{
view.BeginUpdate();
view.Items.AddRange(tags.Select(tag => new ListViewItem(tag) { Name = tag }).ToArray());
view.EndUpdate();
});
}
catch (Exception exception)
{
Log.Error(exception, "Unable to load images.");
}
finally
{
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = "Done loading images.";
form.toolStripProgressBar1.MarqueeAnimationSpeed = 0;
});
_imageListViewLock.Release();
}
}, _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
}
private async void tagListView_MouseDown(object sender, MouseEventArgs e)
{
var listView = (ListView)sender;
if (!listView.CheckBoxes)
{
return;
}
// Allow clicking anywhere on tag.
var hitTest = listView.HitTest(e.Location);
if (hitTest.Item == null)
{
return;
}
var item = hitTest.Item;
var tagText = item.Text;
var keywords = new[] { tagText };
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = items.Length;
toolStripProgressBar1.Value = 0;
void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess:
foreach (var tag in imageListViewItemProgressSuccess.Tags)
{
tagListView.BeginUpdate();
if (tagListView.Items.ContainsKey(tag))
{
tagListView.Items[tag].Checked = imageListViewItemProgressSuccess.Check;
tagListView.EndUpdate();
continue;
}
tagListView.Items.Add(new ListViewItem(tag) { Name = tag });
tagListView.Items[tag].Checked = imageListViewItemProgressSuccess.Check;
tagListView.EndUpdate();
}
break;
case ImageListViewItemProgressFailure<ListViewItem> imageListViewItemProgressFailure:
break;
}
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
}
}
if (item.Checked)
{
toolStripStatusLabel1.Text = "Removing tags...";
_listViewItemProgress.ProgressChanged += ImageListViewItemProgress;
try
{
await RemoveTags(items, keywords, _magicMime, _listViewItemProgress, _cancellationToken);
}
finally
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
}
switch(hitTest.Location)
{
case ListViewHitTestLocations.Label:
hitTest.Item.Checked = !hitTest.Item.Checked;
break;
}
return;
}
toolStripStatusLabel1.Text = "Adding tags...";
_listViewItemProgress.ProgressChanged += ImageListViewItemProgress;
try
{
await AddTags(items, keywords, _magicMime, _listViewItemProgress, _cancellationToken);
}
finally
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
}
switch(hitTest.Location)
{
case ListViewHitTestLocations.Label:
hitTest.Item.Checked = !hitTest.Item.Checked;
break;
}
}
private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_aboutForm != null)
{
return;
}
_aboutForm = new AboutForm();
_aboutForm.Closing += AboutForm_Closing;
_aboutForm.Show();
}
private void AboutForm_Closing(object sender, CancelEventArgs e)
{
if (_aboutForm == null)
{
return;
}
_aboutForm.Closing -= AboutForm_Closing;
_aboutForm.Dispose();
_aboutForm = null;
}
private async void updateToolStripMenuItem_Click(object sender, EventArgs e)
{
await PerformUpgrade();
}
private void viewLogsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_viewLogsForm != null)
{
return;
}
_viewLogsForm = new ViewLogsForm(this, _memorySink, _cancellationToken);
_viewLogsForm.Closing += ViewLogsForm_Closing;
_viewLogsForm.Show();
}
private void ViewLogsForm_Closing(object sender, CancelEventArgs e)
{
if (_viewLogsForm == null)
{
return;
}
_viewLogsForm.Closing -= ViewLogsForm_Closing;
_viewLogsForm.Close();
_viewLogsForm = null;
}
private void quitToolStripMenuItem_Click(object sender, EventArgs e)
{
_cancellationTokenSource.Cancel();
Close();
}
private async void removeToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
RemoveImages(items, _cancellationToken);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
var textBox = (TextBox)sender;
var text = textBox.Text;
if (_searchCancellationTokenSource != null) _searchCancellationTokenSource.Cancel();
_searchCancellationTokenSource = new CancellationTokenSource();
_searchCancellationToken = _searchCancellationTokenSource.Token;
_linkedSearchCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken);
_combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token;
if (string.IsNullOrEmpty(text))
{
_searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250),
async () => { await EndSearch(); }, _combinedSearchSelectionCancellationToken);
return;
}
_searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text,
async text => { await BeginSearch(text); }, _combinedSearchSelectionCancellationToken);
}
private void imageListView_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.A)
{
foreach (var item in imageListView.Items.OfType<ListViewItem>())
{
item.Selected = true;
}
}
}
private void imageListView_DragLeave(object sender, EventArgs e)
{
}
private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
DeleteImages(items, _cancellationToken);
}
private void selectAllToolStripMenuItem1_Click(object sender, EventArgs e)
{
foreach (var item in imageListView.SelectedItems.OfType<ListViewItem>())
{
foreach (var groupItem in item.Group.Items.OfType<ListViewItem>())
{
groupItem.Selected = true;
}
}
}
private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
{
foreach (var item in imageListView.Items.OfType<ListViewItem>())
{
item.Selected = true;
}
}
private void textBox1_KeyDown(object sender, KeyEventArgs e)
{
if (e.Control && e.KeyCode == Keys.A)
{
var textBox = (TextBox)sender;
textBox.SelectAll();
}
}
private void radioButton2_CheckedChanged(object sender, EventArgs e)
{
_quickImageSearchType = QuickImageSearchType.Any;
var text = textBox1.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
if (_searchCancellationTokenSource != null)
{
_searchCancellationTokenSource.Cancel();
}
_searchCancellationTokenSource = new CancellationTokenSource();
_searchCancellationToken = _searchCancellationTokenSource.Token;
_linkedSearchCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken);
_combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token;
_searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken);
}
private void radiobutton1_CheckedChanged(object sender, EventArgs e)
{
_quickImageSearchType = QuickImageSearchType.All;
var text = textBox1.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
if (_searchCancellationTokenSource != null) _searchCancellationTokenSource.Cancel();
_searchCancellationTokenSource = new CancellationTokenSource();
_searchCancellationToken = _searchCancellationTokenSource.Token;
_linkedSearchCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken);
_combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token;
_searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken);
}
private async void editToolStripMenuItem1_Click(object sender, EventArgs e)
{
var item = imageListView.SelectedItems.OfType<ListViewItem>().FirstOrDefault();
if (item == null)
{
return;
}
if (_editorForm != null)
{
return;
}
try
{
var mime = await _magicMime.GetMimeType(item.Name, _cancellationToken);
switch (mime)
{
case "IMAGE/JPEG":
case "IMAGE/PNG":
case "IMAGE/BMP":
break;
default:
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = $"Image format not supported for file {item.Name}";
});
return;
}
}
catch(Exception exception)
{
Log.Error(exception, $"Could not deterine image format for file {item.Name}");
return;
}
_editorForm = new EditorForm(item.Name, Configuration, _magicMime, _cancellationToken);
_editorForm.ImageSave += _editorForm_ImageSave;
_editorForm.ImageSaveAs += _editorForm_ImageSaveAs;
_editorForm.Closing += _editorForm_Closing;
_editorForm.Show();
}
private async void _editorForm_ImageSaveAs(object sender, EditorForm.ImageChangedEventArgs e)
{
using var imageMemoryStream = new MemoryStream(e.ImageBytes);
using (var fileStream = new FileStream(e.FileName, FileMode.OpenOrCreate, FileAccess.Write))
{
await imageMemoryStream.CopyToAsync(fileStream);
}
await LoadFilesAsync(new[] { e.FileName }, _magicMime, _cancellationToken);
}
private async void _editorForm_ImageSave(object sender, EditorForm.ImageChangedEventArgs e)
{
using var bitmapMemoryStream = new MemoryStream();
using var imageMemoryStream = new MemoryStream(e.ImageBytes);
using (var fileStream = new FileStream(e.FileName, FileMode.OpenOrCreate, FileAccess.Write))
{
await imageMemoryStream.CopyToAsync(fileStream);
}
imageMemoryStream.Position = 0L;
using var image = Image.FromStream(imageMemoryStream);
image.Save(bitmapMemoryStream, ImageFormat.Bmp);
bitmapMemoryStream.Position = 0L;
using var hashBitmap = Image.FromStream(bitmapMemoryStream);
var hash = ImagePhash.ComputeDigest(hashBitmap.ToBitmap().ToLuminanceImage());
bitmapMemoryStream.Position = 0L;
using var thumbnailBitmap = await CreateThumbnail(bitmapMemoryStream, 128, 128, _cancellationToken);
var thumbnail = new Bitmap(thumbnailBitmap);
thumbnailBitmap.Dispose();
try
{
if (!await _quickImageDatabase.RemoveImageAsync(e.FileName, _cancellationToken))
{
throw new ArgumentException($"Could not remove image {e.FileName} from database.");
}
var keywords = await _quickImageDatabase.GetTags(e.FileName, _cancellationToken).ToArrayAsync(_cancellationToken);
if (!await _quickImageDatabase.AddImageAsync(e.FileName, hash, keywords, thumbnail, _cancellationToken))
{
throw new ArgumentException($"Could not add image {e.FileName} to database.");
}
this.InvokeIfRequired(form =>
{
form.largeImageList.Images.RemoveByKey(e.FileName);
form.largeImageList.Images.Add(e.FileName, thumbnail);
});
}
catch (Exception exception)
{
Log.Error(exception, "Could not update image in database");
}
}
private void _editorForm_Closing(object sender, CancelEventArgs e)
{
if (_editorForm == null)
{
return;
}
_editorForm.ImageSave -= _editorForm_ImageSave;
_editorForm.ImageSaveAs -= _editorForm_ImageSaveAs;
_editorForm.Closing -= _editorForm_Closing;
_editorForm.Dispose();
_editorForm = null;
}
private async void synchronizeTagsToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = items.Length;
toolStripProgressBar1.Value = 0;
void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess:
if (!(imageListViewItemProgressSuccess.Item is { } listViewItem))
{
break;
}
toolStripStatusLabel1.Text = $"Synchronizing tags for {listViewItem.Name}";
break;
case ImageListViewItemProgressFailure<ListViewItem> imageListViewItemProgressFailure:
break;
}
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
_imageListViewLock.Release();
}
}
toolStripStatusLabel1.Text = "Synchronizing image tags with database tags...";
try
{
await _imageListViewLock.WaitAsync(_cancellationToken);
}
catch
{
return;
}
_listViewItemProgress.ProgressChanged += ImageListViewItemProgress;
try
{
await BalanceImageTags(items, _magicMime, _listViewItemProgress, _cancellationToken);
}
catch
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
_imageListViewLock.Release();
}
}
private void checkBox1_VisibleChanged(object sender, EventArgs e)
{
var checkBox = (CheckBox)sender;
if (checkBox.Checked)
{
_quickImageSearchParameters = _quickImageSearchParameters | QuickImageSearchParameters.CaseSensitive;
return;
}
_quickImageSearchParameters = _quickImageSearchParameters & ~QuickImageSearchParameters.CaseSensitive;
}
private void checkBox1_CheckedChanged(object sender, EventArgs e)
{
var checkBox = (CheckBox)sender;
switch (checkBox.Checked)
{
case true:
_quickImageSearchParameters =
_quickImageSearchParameters | QuickImageSearchParameters.CaseSensitive;
break;
case false:
_quickImageSearchParameters =
_quickImageSearchParameters & ~QuickImageSearchParameters.CaseSensitive;
break;
}
var text = textBox1.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
if (_searchCancellationTokenSource != null)
{
_searchCancellationTokenSource.Cancel();
}
_searchCancellationTokenSource = new CancellationTokenSource();
_searchCancellationToken = _searchCancellationTokenSource.Token;
_linkedSearchCancellationTokenSource =
CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken);
_combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token;
_searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken);
}
private void PreviewFormClosing(object sender, CancelEventArgs e)
{
if (_previewForm == null)
{
return;
}
_previewForm.Closing -= PreviewFormClosing;
_previewForm.Dispose();
_previewForm = null;
}
private async void perceptionToolStripMenuItem1_Click(object sender, EventArgs e)
{
await SortImageListView(new PerceptionImageListViewItemSorter(
imageListView.Items.OfType<ListViewItem>().ToList(),
_quickImageDatabase,
_cancellationToken));
}
private void openDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
var item = items.FirstOrDefault();
if (item == null)
{
return;
}
Process.Start("explorer.exe", $"/select, \"{item.Name}\"");
}
private void aboutToolStripMenuItem1_Click(object sender, EventArgs e)
{
if (_aboutForm != null)
{
return;
}
_aboutForm = new AboutForm();
_aboutForm.Closing += AboutForm_Closing;
_aboutForm.Show();
}
private async void imageListView_DragDrop(object sender, DragEventArgs e)
{
await LoadDataObjectAsync(e.Data);
}
private void imageListView_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.AllowedEffect;
}
private void imageListView_MouseDoubleClick(object sender, MouseEventArgs e)
{
var listView = (ListView)sender;
var info = listView.HitTest(e.X, e.Y);
switch (info.Location)
{
case ListViewHitTestLocations.AboveClientArea:
case ListViewHitTestLocations.BelowClientArea:
case ListViewHitTestLocations.LeftOfClientArea:
case ListViewHitTestLocations.RightOfClientArea:
case ListViewHitTestLocations.None:
return;
}
var item = info.Item;
if (item == null)
{
return;
}
try
{
if (_previewForm != null)
{
_previewForm.InvokeIfRequired(form =>
{
form.Close();
});
_previewForm = null;
}
}
catch(Exception exception)
{
Log.Warning(exception, "Error encoutnered while trying to close preview form.");
}
try
{
new Thread(() =>
{
_previewForm = new PreviewForm(item.Name, Configuration, _magicMime, _cancellationToken);
_previewForm.Closing += PreviewFormClosing;
_previewForm.ShowDialog();
}).Start();
}
catch (Exception exception)
{
Log.Error(exception, $"File {item.Name} could not be displayed due to the path not being accessible.");
}
}
private void imageListView_MouseUp(object sender, MouseEventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>();
SelectTags(items);
}
private void imageListView_KeyUp(object sender, KeyEventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
if (e.KeyCode == Keys.Delete)
{
RemoveImages(items, _cancellationToken);
return;
}
SelectTags(items);
}
private async void imageListView_ItemDrag(object sender, ItemDragEventArgs e)
{
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Value = 0;
toolStripProgressBar1.Maximum = 3;
var inputBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions { CancellationToken = _cancellationToken });
var fileTransformBlock =
new TransformBlock<ListViewItem, (string Source, string Path, string Name, string Mime, string Extension)>(async item =>
{
string mimeType = string.Empty;
try
{
mimeType = await _magicMime.GetMimeType(item.Name, _cancellationToken);
}
catch (Exception exception)
{
Log.Error(exception, $"Could not determine image type for file {item.Name}");
}
var extension = Path.GetExtension(item.Name);
var path = Path.GetTempPath();
var file = Path.GetFileNameWithoutExtension(item.Name);
if (Configuration.OutboundDragDrop.RenameOnDragDrop)
{
switch (Configuration.OutboundDragDrop.DragDropRenameMethod)
{
case DragDropRenameMethod.Random:
file = string.Join("", Enumerable.Repeat(0, 5).Select(n => (char)_random.Next('a', 'z' + 1)));
break;
case DragDropRenameMethod.Timestamp:
file = $"{DateTimeOffset.Now.ToUnixTimeSeconds()}";
break;
}
}
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = $"File {item.Name} scanned for drag and drop...";
});
return (Source: item.Name, Path: path, Name: file, Mime: mimeType, Extension: extension);
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
#pragma warning disable CS4014
fileTransformBlock.Completion.ContinueWith(_ =>
#pragma warning restore CS4014
{
toolStripStatusLabel1.Text = "All files scanned for drag and drop.";
toolStripProgressBar1.Increment(1);
}, _formTaskScheduler);
var noConvertTransformBlock =
new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>(async item =>
{
var destination = Path.Combine(item.Path, $"{item.Name}{item.Extension}");
await Miscellaneous.CopyFileAsync(item.Source, destination, _cancellationToken);
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text =
$"File {item.Source} does not need conversion for drag and drop...";
});
return destination;
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
#pragma warning disable CS4014
noConvertTransformBlock.Completion.ContinueWith(_ =>
#pragma warning restore CS4014
{
toolStripStatusLabel1.Text = "Conversion complete for drag and drop.";
toolStripProgressBar1.Increment(1);
}, _formTaskScheduler);
var jpegTransformBlock =
new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>(async file =>
{
using var imageStream =
await _imageTool.ConvertTo(file.Source, MagickFormat.Jpeg, _cancellationToken);
var jpegDestination = Path.Combine(file.Path, $"{file.Name}.jpg");
await Miscellaneous.CopyFileAsync(imageStream, jpegDestination, _cancellationToken);
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text =
$"File {file.Source} converted to JPEG for drag and drop...";
});
return jpegDestination;
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
#pragma warning disable CS4014
jpegTransformBlock.Completion.ContinueWith(_ =>
#pragma warning restore CS4014
{
toolStripStatusLabel1.Text = "Conversion complete for drag and drop.";
toolStripProgressBar1.Increment(1);
}, _formTaskScheduler);
var pngTransformBlock =
new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>(async file =>
{
using var imageStream = await _imageTool.ConvertTo(file.Source, MagickFormat.Png, _cancellationToken);
var pngDestination = Path.Combine(file.Path, $"{file.Name}.png");
await Miscellaneous.CopyFileAsync(imageStream, pngDestination, _cancellationToken);
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text =
$"File {file.Source} converted to PNG for drag and drop...";
});
return pngDestination;
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
#pragma warning disable CS4014
pngTransformBlock.Completion.ContinueWith(_ =>
#pragma warning restore CS4014
{
toolStripStatusLabel1.Text = "Conversion complete for drag and drop.";
toolStripProgressBar1.Increment(1);
}, _formTaskScheduler);
var bmpTransformBlock =
new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>( async file =>
{
using var imageStream = await _imageTool.ConvertTo(file.Source, MagickFormat.Jpeg, _cancellationToken);
var bmpDestination = Path.Combine(file.Path, $"{file.Name}.bmp");
await Miscellaneous.CopyFileAsync(imageStream, bmpDestination, _cancellationToken);
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text =
$"File {file.Source} converted to BMP for drag and drop...";
});
return bmpDestination;
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
#pragma warning disable CS4014
bmpTransformBlock.Completion.ContinueWith(_ =>
#pragma warning restore CS4014
{
toolStripStatusLabel1.Text = "Conversion complete for drag and drop.";
toolStripProgressBar1.Increment(1);
}, _formTaskScheduler);
var iptcStripTransformBlock = new TransformBlock<string, string>(async file =>
{
try
{
var mime = await _magicMime.GetMimeType(file, _cancellationToken);
if (Configuration.SupportedFormats.IsSupportedVideo(mime))
{
return file;
}
if (!Configuration.SupportedFormats.IsSupportedImage(mime))
{
throw new ArgumentException();
}
if (!await _tagManager.StripIptcProfile(file, _cancellationToken))
{
throw new ArgumentException();
}
}
catch
{
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text =
$"Failed to strip IPTC tags for file {file} for drag and drop...";
});
return string.Empty;
}
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = $"Stripped IPTC tags from file {file} for drag and drop...";
});
return file;
}, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken });
#pragma warning disable CS4014
iptcStripTransformBlock.Completion.ContinueWith(_ =>
#pragma warning restore CS4014
{
toolStripStatusLabel1.Text = "All tags stripped for drag and drop.";
toolStripProgressBar1.Increment(1);
}, _formTaskScheduler);
var outputBlock = new BufferBlock<string>(new DataflowBlockOptions
{ CancellationToken = _cancellationToken });
using var _1 =
inputBlock.LinkTo(fileTransformBlock, new DataflowLinkOptions { PropagateCompletion = true });
using var _2 = fileTransformBlock.LinkTo(jpegTransformBlock, item =>
Configuration.OutboundDragDrop.ConvertOnDragDrop &&
!Configuration.SupportedFormats.IsSupportedVideo(item.Mime) &&
!Configuration.OutboundDragDrop.DragDropConvertExclude.IsExcludedImage(item.Mime) &&
Configuration.SupportedFormats.IsSupported(item.Mime) &&
Configuration.OutboundDragDrop.DragDropConvertType == "image/jpeg" &&
item.Mime != "image/jpeg");
using var _3 =
jpegTransformBlock.LinkTo(iptcStripTransformBlock,
file => Configuration.OutboundDragDrop.StripTagsOnDragDrop);
using var _11 =
jpegTransformBlock.LinkTo(outputBlock);
using var _4 = fileTransformBlock.LinkTo(pngTransformBlock, item =>
Configuration.OutboundDragDrop.ConvertOnDragDrop &&
!Configuration.SupportedFormats.IsSupportedVideo(item.Mime) &&
!Configuration.OutboundDragDrop.DragDropConvertExclude.IsExcludedImage(item.Mime) &&
Configuration.SupportedFormats.IsSupported(item.Mime) &&
Configuration.OutboundDragDrop.DragDropConvertType == "image/png" &&
item.Mime != "image/png");
using var _5 =
pngTransformBlock.LinkTo(iptcStripTransformBlock,
file => Configuration.OutboundDragDrop.StripTagsOnDragDrop);
using var _12 =
pngTransformBlock.LinkTo(outputBlock);
using var _6 = fileTransformBlock.LinkTo(bmpTransformBlock, item =>
Configuration.OutboundDragDrop.ConvertOnDragDrop &&
!Configuration.SupportedFormats.IsSupportedVideo(item.Mime) &&
!Configuration.OutboundDragDrop.DragDropConvertExclude.IsExcludedImage(item.Mime) &&
Configuration.SupportedFormats.IsSupported(item.Mime) &&
Configuration.OutboundDragDrop.DragDropConvertType == "image/bmp" &&
item.Mime != "image/bmp");
using var _7 =
bmpTransformBlock.LinkTo(iptcStripTransformBlock,
file => Configuration.OutboundDragDrop.StripTagsOnDragDrop);
using var _13 =
bmpTransformBlock.LinkTo(outputBlock);
using var _8 = fileTransformBlock.LinkTo(noConvertTransformBlock);
using var _15 = fileTransformBlock.LinkTo(DataflowBlock
.NullTarget<(string Source, string Path, string Name, string Mime, string Extension)>());
using var _9 = noConvertTransformBlock.LinkTo(iptcStripTransformBlock,
file => Configuration.OutboundDragDrop.StripTagsOnDragDrop);
using var _10 =
iptcStripTransformBlock.LinkTo(outputBlock, file => !string.IsNullOrEmpty(file));
using var _16 = iptcStripTransformBlock.LinkTo(DataflowBlock.NullTarget<string>());
using var _14 =
noConvertTransformBlock.LinkTo(outputBlock, file => !string.IsNullOrEmpty(file));
using var _17 = noConvertTransformBlock.LinkTo(DataflowBlock.NullTarget<string>());
foreach (var item in imageListView.SelectedItems.OfType<ListViewItem>())
{
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
inputBlock.SendAsync(item, _cancellationToken);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
inputBlock.Complete();
await fileTransformBlock.Completion.ContinueWith(_ =>
{
jpegTransformBlock.Complete();
pngTransformBlock.Complete();
bmpTransformBlock.Complete();
noConvertTransformBlock.Complete();
iptcStripTransformBlock.Complete();
}, _cancellationToken);
await Task.WhenAll(
jpegTransformBlock.Completion,
pngTransformBlock.Completion,
bmpTransformBlock.Completion,
iptcStripTransformBlock.Completion,
noConvertTransformBlock.Completion).ContinueWith(_ =>
{
outputBlock.Complete();
}, _cancellationToken);
var files = new HashSet<string>();
while (await outputBlock.OutputAvailableAsync(_cancellationToken))
{
if (!outputBlock.TryReceiveAll(out var items))
{
continue;
}
files.UnionWith(items);
}
files.RemoveWhere(string.IsNullOrEmpty);
this.InvokeIfRequired(form =>
{
form.toolStripStatusLabel1.Text = $"{files.Count} ready for drag and drop...";
});
if (files.Count == 0)
{
return;
}
var data = new DataObject(DataFormats.FileDrop, files.ToArray());
this.InvokeIfRequired(_ =>
{
DoDragDrop(data, DragDropEffects.Copy);
});
}
private void imageListView_DragOver(object sender, DragEventArgs e)
{
}
private async void nameToolStripMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(new NameImageListViewItemSorter());
}
private async void ascendingToolStripMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(new SizeImageListViewItemSorter(SortOrder.Ascending));
}
private async void descendingToolStripMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(new SizeImageListViewItemSorter(SortOrder.Descending));
}
private async void typeToolStripMenuItem_Click(object sender, EventArgs e)
{
await SortImageListView(new TypeImageListViewItemSorter());
}
private async void importToolStripMenuItem_Click(object sender, EventArgs e)
{
var dialog = new CommonOpenFileDialog
{
AddToMostRecentlyUsedList = true,
Multiselect = true,
IsFolderPicker = false
};
switch (dialog.ShowDialog())
{
case CommonFileDialogResult.Ok:
await LoadFilesAsync(dialog.FileNames, _magicMime, _cancellationToken);
break;
}
}
private async void importDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
var dialog = new CommonOpenFileDialog
{
AddToMostRecentlyUsedList = true,
Multiselect = true,
IsFolderPicker = true
};
switch (dialog.ShowDialog())
{
case CommonFileDialogResult.Ok:
await LoadFilesAsync(dialog.FileNames, _magicMime, _cancellationToken);
break;
}
}
private void settingsToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_settingsForm != null)
{
return;
}
_settingsForm = new SettingsForm(Configuration, _cancellationToken);
_settingsForm.Closing += SettingsForm_Closing;
_settingsForm.Show();
}
private void SettingsForm_Closing(object sender, CancelEventArgs e)
{
if (_settingsForm == null)
{
return;
}
if (_settingsForm.SaveOnClose)
{
// Commit the configuration.
_changedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1),
async () => { await SaveConfiguration(Configuration); }, _cancellationToken);
}
_settingsForm.Closing -= SettingsForm_Closing;
_settingsForm.Dispose();
_settingsForm = null;
}
private async void removeTagsToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
var count = items.Length;
toolStripProgressBar1.Style = ProgressBarStyle.Continuous;
toolStripProgressBar1.Minimum = 0;
toolStripProgressBar1.Maximum = count;
toolStripProgressBar1.Value = 0;
void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e)
{
switch (e)
{
case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess:
if (!(imageListViewItemProgressSuccess.Item is { } imageListViewItem)) break;
foreach (var tag in imageListViewItemProgressSuccess.Tags)
{
tagListView.BeginUpdate();
if (tagListView.CheckedItems.ContainsKey(tag))
{
tagListView.Items[tag].Checked = false;
tagListView.EndUpdate();
continue;
}
tagListView.Items.Add(new ListViewItem(tag) { Name = tag });
tagListView.Items[tag].Checked = false;
tagListView.EndUpdate();
}
break;
case ImageListViewItemProgressFailure<ListViewItem> _:
break;
}
toolStripStatusLabel1.Text = "Stripping tags...";
toolStripProgressBar1.Increment(1);
if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum)
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
}
toolStripStatusLabel1.Text = "Stripping tags...";
_listViewItemProgress.ProgressChanged += ImageListViewItemProgress;
try
{
await StripTags(items, _magicMime, _listViewItemProgress, _cancellationToken);
}
catch
{
_listViewItemProgress.ProgressChanged -= ImageListViewItemProgress;
}
}
private async void balanceTagsToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>();
var listViewItems = items as ListViewItem[] ?? items.ToArray();
if (listViewItems.Length < 2)
{
return;
}
await BalanceTags(listViewItems);
}
private async void loadMissingToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>();
var listViewItems = items as ListViewItem[] ?? items.ToArray();
await LoadFilesAsync(listViewItems.Select(item => item.Group.Name), _magicMime, _cancellationToken);
}
private void moveToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
{
var menuItem = (ToolStripMenuItem)sender;
foreach (var group in _imageListViewGroupDictionary.Keys)
{
if (menuItem.DropDownItems.ContainsKey(group))
{
continue;
}
var toolStripMenuSubItem = new ToolStripButton(group) { Name = group };
toolStripMenuSubItem.Click += moveTargetToolStripMenuItem_Click;
menuItem.DropDownItems.Add(toolStripMenuSubItem);
}
}
private void moveToolStripMenuItem_DropDownClosed(object sender, EventArgs e)
{
var menuItem = (ToolStripMenuItem)sender;
var items = new ConcurrentBag<ToolStripButton>();
foreach(var toolStripMenuSubItem in menuItem.DropDownItems.OfType<ToolStripButton>())
{
toolStripMenuSubItem.Click -= moveTargetToolStripMenuItem_Click;
items.Add(toolStripMenuSubItem);
}
foreach(var toolStripMenuSubItem in items)
{
menuItem.DropDownItems.Remove(toolStripMenuSubItem);
}
}
private async void moveTargetToolStripMenuItem_Click(object sender, EventArgs e)
{
var menuItem = (ToolStripButton)sender;
var items = imageListView.SelectedItems.OfType<ListViewItem>();
await MoveImagesAsync(items, menuItem.Name, _cancellationToken);
}
private void imageListView_GroupHeaderClick(object sender, ListViewGroup e)
{
var listViewCollapsible = (ListViewCollapsible)sender;
var collapsed = listViewCollapsible.GetCollapsed(e);
if (collapsed)
{
listViewCollapsible.SetCollapsed(e, false);
return;
}
listViewCollapsible.SetCollapsed(e, true);
}
private async void convertToTypeToolStripMenuItem_Click(object sender, EventArgs e)
{
var toolStripMenuItem = (ToolStripMenuItem)sender;
var extension = toolStripMenuItem.Text;
var extensionToMime = new Dictionary<string, string>
{
{ "jpg", "image/jpeg" },
{ "png", "image/png" },
{ "bmp", "image/bmp" },
{ "gif", "image/gif" }
};
if (!Configuration.SupportedFormats.Images.Image.Contains(extensionToMime[extension]))
{
return;
}
var items = imageListView.SelectedItems.OfType<ListViewItem>();
await ConvertImagesAsync(items, extension, _cancellationToken);
}
#endregion
private void button1_Click(object sender, EventArgs e)
{
_sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(50), () =>
{
tagListView.BeginUpdate();
tagListView.Sort();
tagListView.EndUpdate();
}, _formTaskScheduler, _cancellationToken);
}
/// <summary>
/// Enable or disable menu items recursively.
/// </summary>
/// <param name="item">the menu item from where to start disabling or enabling menu items</param>
/// <param name="enable">whether to enable or disable the menu item
/// </param>
private void ToggleMenuItemsRecursive(ToolStripMenuItem item, MenuItemsToggleOperation menuItemsToggleOperation)
{
if (!item.HasDropDown)
{
return;
}
switch (menuItemsToggleOperation)
{
case MenuItemsToggleOperation.NONE:
throw new ArgumentException("Unknown menu toggle operation.");
case MenuItemsToggleOperation.ENABLE:
item.Enabled = true;
break;
case MenuItemsToggleOperation.DISABLE:
item.Enabled = false;
break;
}
foreach (var menuItem in item.DropDownItems.OfType<ToolStripMenuItem>())
{
ToggleMenuItemsRecursive(menuItem, menuItemsToggleOperation);
}
}
private void contextMenuStrip1_Closing(object sender, ToolStripDropDownClosingEventArgs e)
{
ToggleMenuItemsRecursive(directoryToolStripMenuItem, MenuItemsToggleOperation.ENABLE);
ToggleMenuItemsRecursive(fileToolStripMenuItem1, MenuItemsToggleOperation.ENABLE);
ToggleMenuItemsRecursive(imageToolStripMenuItem, MenuItemsToggleOperation.ENABLE);
ToggleMenuItemsRecursive(taggingToolStripMenuItem, MenuItemsToggleOperation.ENABLE);
}
private void contextMenuStrip1_Opening(object sender, CancelEventArgs e)
{
var mousePoint = imageListView.PointToClient(Cursor.Position);
var listViewHitTestInfo = imageListView.HitTest(mousePoint);
// check if the mouse was hovering over an item
if (listViewHitTestInfo.Item != null)
{
// hovering
ToggleMenuItemsRecursive(directoryToolStripMenuItem, MenuItemsToggleOperation.ENABLE);
ToggleMenuItemsRecursive(fileToolStripMenuItem1, MenuItemsToggleOperation.ENABLE);
ToggleMenuItemsRecursive(imageToolStripMenuItem, MenuItemsToggleOperation.ENABLE);
ToggleMenuItemsRecursive(taggingToolStripMenuItem, MenuItemsToggleOperation.ENABLE);
return;
}
// disable menu items not related to list view items
ToggleMenuItemsRecursive(directoryToolStripMenuItem, MenuItemsToggleOperation.DISABLE);
ToggleMenuItemsRecursive(fileToolStripMenuItem1, MenuItemsToggleOperation.DISABLE);
ToggleMenuItemsRecursive(imageToolStripMenuItem, MenuItemsToggleOperation.DISABLE);
ToggleMenuItemsRecursive(taggingToolStripMenuItem, MenuItemsToggleOperation.DISABLE);
}
private void copyCtrlToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
switch(items.Length)
{
case 0:
break;
default:
// copy file paths as the default for multiple files selected
var paths = new StringCollection();
foreach (var item in items)
{
paths.Add(item.Name);
}
Clipboard.SetFileDropList(paths);
break;
}
}
private async void copyAsMediaToolStripMenuItem_Click(object sender, EventArgs e)
{
var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray();
switch (items.Length)
{
case 1:
{
var file = items[0].Name;
using var memoryStream = new MemoryStream();
using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
await fileStream.CopyToAsync(memoryStream);
memoryStream.Position = 0L;
MagicMimeFile mime;
try
{
mime = _magicMime.Identify(file, memoryStream, _cancellationToken);
if (mime == null)
{
break;
}
}
catch (Exception exception)
{
Log.Error(exception, $"Could not determine mime type for file {file}.");
break;
}
if (Configuration.SupportedFormats.IsSupportedVideo(mime.Definition.File.MimeType))
{
Clipboard.SetDataObject(memoryStream);
break;
}
if (!Configuration.SupportedFormats.IsSupportedImage(mime.Definition.File.MimeType))
{
using var image = Image.FromFile(file);
Clipboard.SetImage(image);
break;
}
}
break;
case 0:
break;
default:
// copy file paths as the default for multiple files selected
var paths = new StringCollection();
foreach (var item in items)
{
paths.Add(item.Name);
}
Clipboard.SetFileDropList(paths);
break;
}
}
private async void pasteCtrlCToolStripMenuItem_Click(object sender, EventArgs e)
{
var dataObject = Clipboard.GetDataObject();
await LoadDataObjectAsync(dataObject);
}
}
}
Generated by GNU Enscript 1.6.5.90.