QuickImage – Blame information for rev 20
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
7 | office | 1 | using System; |
2 | using System.Collections.Concurrent; |
||
3 | using System.Collections.Generic; |
||
9 | office | 4 | using System.Collections.Specialized; |
7 | office | 5 | using System.ComponentModel; |
6 | using System.Diagnostics; |
||
7 | using System.Drawing; |
||
8 | using System.IO; |
||
9 | using System.Linq; |
||
10 | using System.Net; |
||
11 | using System.Reflection; |
||
12 | using System.Runtime.CompilerServices; |
||
13 | using System.Security.Cryptography; |
||
14 | using System.Text.RegularExpressions; |
||
15 | using System.Threading; |
||
16 | using System.Threading.Tasks; |
||
20 | office | 17 | using System.Threading.Tasks.Dataflow; |
7 | office | 18 | using System.Windows.Forms; |
19 | using Configuration; |
||
20 | using ImageMagick; |
||
21 | using ImageMagick.Factories; |
||
22 | using Microsoft.WindowsAPICodePack.Dialogs; |
||
23 | using MimeDetective.Storage; |
||
24 | using NetSparkleUpdater; |
||
25 | using NetSparkleUpdater.Enums; |
||
26 | using NetSparkleUpdater.SignatureVerifiers; |
||
27 | using NetSparkleUpdater.UI.WinForms; |
||
28 | using QuickImage.Database; |
||
29 | using QuickImage.ImageListViewSorters; |
||
30 | using QuickImage.Utilities; |
||
31 | using QuickImage.Utilities.Controls; |
||
32 | using QuickImage.Utilities.Extensions; |
||
33 | using QuickImage.Utilities.Serialization.Comma_Separated_Values; |
||
34 | using QuickImage.Utilities.Serialization.XML; |
||
35 | using Serilog; |
||
36 | using Shipwreck.Phash; |
||
37 | using Shipwreck.Phash.Bitmaps; |
||
38 | using Tesseract; |
||
20 | office | 39 | using ImageFormat = System.Drawing.Imaging.ImageFormat; |
40 | |||
7 | office | 41 | namespace QuickImage |
42 | { |
||
43 | public partial class Form1 : Form |
||
44 | { |
||
45 | private readonly CancellationToken _cancellationToken; |
||
46 | private readonly CancellationTokenSource _cancellationTokenSource; |
||
47 | private readonly ScheduledContinuation _changedConfigurationContinuation; |
||
48 | private readonly FileMutex _fileMutex; |
||
49 | private readonly TaskScheduler _formTaskScheduler; |
||
50 | private readonly ConcurrentDictionary<string, ListViewGroup> _imageListViewGroupDictionary; |
||
51 | private readonly SemaphoreSlim _imageListViewLock; |
||
52 | private readonly ImageTool _imageTool; |
||
53 | private readonly Progress<ImageListViewItemProgress<ListViewItem>> _listViewItemProgress; |
||
54 | private readonly MagicMime _magicMime; |
||
55 | private readonly MD5 _md5; |
||
8 | office | 56 | private readonly LogMemorySink _memorySink = new LogMemorySink(); |
7 | office | 57 | private readonly QuickImageDatabase _quickImageDatabase; |
58 | |||
59 | private readonly Progress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>> |
||
60 | _quickImageListViewProgress; |
||
61 | |||
62 | private readonly Random _random; |
||
63 | private readonly ScheduledContinuation _searchScheduledContinuation; |
||
64 | private readonly ConcurrentDictionary<string, ListViewItem> _searchStore; |
||
65 | private readonly ScheduledContinuation _selectionScheduledContinuation; |
||
66 | private readonly ScheduledContinuation _sortScheduledContinuation; |
||
67 | private readonly SparkleUpdater _sparkle; |
||
11 | office | 68 | private AutoCompleteStringCollection _tagAutoCompleteStringCollection; |
69 | private TagListViewSorter _tagListViewSorter; |
||
7 | office | 70 | |
71 | private readonly TagManager _tagManager; |
||
72 | private AboutForm _aboutForm; |
||
73 | private CancellationToken _combinedSearchSelectionCancellationToken; |
||
74 | private CancellationToken _combinedSelectionCancellationToken; |
||
75 | private EditorForm _editorForm; |
||
76 | private CancellationTokenSource _linkedSearchCancellationTokenSource; |
||
77 | private CancellationTokenSource _linkedSelectionCancellationTokenSource; |
||
78 | private PreviewForm _previewForm; |
||
79 | private QuickImageSearchParameters _quickImageSearchParameters; |
||
80 | private QuickImageSearchType _quickImageSearchType; |
||
81 | private RenameForm _renameForm; |
||
82 | private CancellationToken _searchCancellationToken; |
||
83 | private CancellationTokenSource _searchCancellationTokenSource; |
||
84 | private CancellationToken _selectionCancellationToken; |
||
85 | private CancellationTokenSource _selectionCancellationTokenSource; |
||
86 | private SettingsForm _settingsForm; |
||
87 | private ViewLogsForm _viewLogsForm; |
||
88 | |||
89 | /// <summary> |
||
90 | /// The operation to perform on the menu item and descendants. |
||
91 | /// </summary> |
||
92 | public enum MenuItemsToggleOperation |
||
93 | { |
||
94 | NONE, |
||
95 | ENABLE, |
||
96 | DISABLE |
||
8 | office | 97 | } |
98 | |||
99 | private Configuration.Configuration Configuration { get; set; } |
||
7 | office | 100 | |
8 | office | 101 | public bool MemorySinkEnabled { get; set; } = true; |
102 | |||
7 | office | 103 | private Form1() |
104 | { |
||
105 | InitializeComponent(); |
||
106 | |||
107 | _fileMutex = new FileMutex(); |
||
108 | _magicMime = new MagicMime(_fileMutex); |
||
109 | |||
110 | _cancellationTokenSource = new CancellationTokenSource(); |
||
111 | _cancellationToken = _cancellationTokenSource.Token; |
||
112 | _searchScheduledContinuation = new ScheduledContinuation(); |
||
113 | _selectionScheduledContinuation = new ScheduledContinuation(); |
||
114 | _sortScheduledContinuation = new ScheduledContinuation(); |
||
115 | _md5 = new MD5CryptoServiceProvider(); |
||
116 | _searchStore = new ConcurrentDictionary<string, ListViewItem>(); |
||
117 | _imageListViewLock = new SemaphoreSlim(1, 1); |
||
118 | _quickImageSearchType = QuickImageSearchType.Any; |
||
119 | _formTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); |
||
120 | _imageListViewGroupDictionary = new ConcurrentDictionary<string, ListViewGroup>(); |
||
121 | _changedConfigurationContinuation = new ScheduledContinuation(); |
||
122 | _random = new Random(); |
||
123 | |||
124 | _listViewItemProgress = new Progress<ImageListViewItemProgress<ListViewItem>>(); |
||
125 | _quickImageListViewProgress = |
||
11 | office | 126 | new Progress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>>(); |
8 | office | 127 | |
128 | _quickImageDatabase = new QuickImageDatabase(_cancellationToken); |
||
129 | _tagManager = new TagManager(_fileMutex); |
||
130 | _imageTool = new ImageTool(); |
||
7 | office | 131 | |
132 | Log.Logger = new LoggerConfiguration() |
||
133 | .MinimumLevel.Debug() |
||
134 | .WriteTo.Conditional(condition => MemorySinkEnabled, configureSink => configureSink.Sink(_memorySink)) |
||
135 | .WriteTo.File(Path.Combine(Constants.UserApplicationDirectory, "Logs", $"{Constants.AssemblyName}.log"), |
||
136 | rollingInterval: RollingInterval.Day) |
||
137 | .CreateLogger(); |
||
138 | |||
139 | // Start application update. |
||
140 | var manifestModuleName = Assembly.GetEntryAssembly().ManifestModule.FullyQualifiedName; |
||
141 | var icon = Icon.ExtractAssociatedIcon(manifestModuleName); |
||
142 | |||
143 | _sparkle = new SparkleUpdater("https://quickimage.grimore.org/update/appcast.xml", |
||
144 | new Ed25519Checker(SecurityMode.Strict, "LonrgxVjSF0GnY4hzwlRJnLkaxnDn2ikdmOifILzLJY=")) |
||
145 | { |
||
146 | UIFactory = new UIFactory(icon), |
||
8 | office | 147 | RelaunchAfterUpdate = true |
7 | office | 148 | }; |
8 | office | 149 | |
7 | office | 150 | _sparkle.StartLoop(true, true); |
151 | } |
||
152 | |||
153 | public Form1(Mutex mutex) : this() |
||
154 | { |
||
155 | } |
||
156 | |||
157 | private async Task SortImageListView(IComparer<ListViewItem> comparer) |
||
158 | { |
||
8 | office | 159 | var taskCompletionSources = new[] { new TaskCompletionSource<object>(), new TaskCompletionSource<object>() }; |
7 | office | 160 | |
161 | try |
||
162 | { |
||
163 | await _imageListViewLock.WaitAsync(_cancellationToken); |
||
164 | } |
||
165 | catch |
||
166 | { |
||
167 | return; |
||
168 | } |
||
169 | |||
170 | toolStripStatusLabel1.Text = "Sorting..."; |
||
171 | toolStripProgressBar1.Style = ProgressBarStyle.Marquee; |
||
172 | toolStripProgressBar1.MarqueeAnimationSpeed = 30; |
||
173 | |||
174 | try |
||
175 | { |
||
176 | var images = imageListView.Items.OfType<ListViewItem>().ToList(); |
||
177 | if (images.Count == 0) return; |
||
178 | |||
179 | imageListView.Items.Clear(); |
||
180 | |||
181 | #pragma warning disable CS4014 |
||
182 | Task.Factory.StartNew(() => |
||
183 | #pragma warning restore CS4014 |
||
184 | { |
||
185 | images.Sort(comparer); |
||
186 | taskCompletionSources[0].TrySetResult(new { }); |
||
187 | }, _cancellationToken); |
||
188 | |||
189 | await taskCompletionSources[0].Task; |
||
190 | |||
191 | imageListView.InvokeIfRequired(view => |
||
192 | { |
||
193 | view.BeginUpdate(); |
||
194 | foreach (var item in images) |
||
195 | { |
||
196 | var directoryName = Path.GetDirectoryName(item.Name); |
||
197 | if (!_imageListViewGroupDictionary.TryGetValue(directoryName, out var group)) |
||
198 | { |
||
199 | group = new ListViewGroup(directoryName, HorizontalAlignment.Left) { Name = directoryName }; |
||
200 | _imageListViewGroupDictionary.TryAdd(directoryName, group); |
||
201 | } |
||
202 | |||
203 | item.Group = group; |
||
204 | } |
||
205 | |||
206 | view.Items.AddRange(images.ToArray()); |
||
207 | view.EndUpdate(); |
||
208 | taskCompletionSources[1].TrySetResult(new { }); |
||
209 | }); |
||
210 | |||
211 | await taskCompletionSources[1].Task; |
||
212 | } |
||
213 | catch (Exception exception) |
||
214 | { |
||
215 | Log.Error(exception, "Failed to sort."); |
||
216 | } |
||
217 | finally |
||
218 | { |
||
219 | this.InvokeIfRequired(form => |
||
220 | { |
||
221 | toolStripStatusLabel1.Text = "Sorting complete."; |
||
8 | office | 222 | |
7 | office | 223 | form.toolStripProgressBar1.MarqueeAnimationSpeed = 0; |
224 | }); |
||
8 | office | 225 | |
7 | office | 226 | _imageListViewLock.Release(); |
227 | } |
||
228 | } |
||
229 | |||
230 | private async Task BalanceTags(IEnumerable<ListViewItem> items) |
||
231 | { |
||
232 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
233 | var count = enumerable.Length; |
||
234 | |||
8 | office | 235 | var bufferBlock = new BufferBlock<(string Name, ConcurrentBag<string> Tags)>(new DataflowBlockOptions{ CancellationToken = _cancellationToken }); |
236 | var broadcastBlock = new BroadcastBlock<(string Name, ConcurrentBag<string> Tags)>(x => x, new DataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
7 | office | 237 | var databaseActionBlock = new ActionBlock<(string Name, ConcurrentBag<string> Tags)>( |
238 | async file => |
||
239 | { |
||
9 | office | 240 | await _quickImageDatabase.AddTagsAsync(file.Name, file.Tags, _cancellationToken); |
241 | await _tagManager.AddIptcKeywords(file.Name, file.Tags, _cancellationToken); |
||
7 | office | 242 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
243 | var taggingActionBlock = new ActionBlock<(string Name, ConcurrentBag<string> Tags)>(file => |
||
244 | { |
||
245 | foreach (var tag in file.Tags) |
||
246 | { |
||
247 | if (tagListView.Items.ContainsKey(tag)) |
||
248 | { |
||
249 | tagListView.BeginUpdate(); |
||
250 | tagListView.Items[tag].Checked = true; |
||
251 | tagListView.EndUpdate(); |
||
252 | return Task.CompletedTask; |
||
253 | } |
||
254 | |||
255 | tagListView.BeginUpdate(); |
||
256 | tagListView.Items.Add(new ListViewItem(tag) { Name = tag }); |
||
257 | tagListView.Items[tag].Checked = true; |
||
258 | tagListView.EndUpdate(); |
||
259 | } |
||
260 | |||
261 | return Task.CompletedTask; |
||
262 | }, |
||
263 | new ExecutionDataflowBlockOptions |
||
264 | { CancellationToken = _cancellationToken, TaskScheduler = _formTaskScheduler }); |
||
265 | |||
266 | using var bufferBroadcastLink = |
||
267 | bufferBlock.LinkTo(broadcastBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||
268 | using var broadcastDatabaseLink = broadcastBlock.LinkTo(databaseActionBlock, |
||
269 | new DataflowLinkOptions { PropagateCompletion = true }); |
||
270 | using var broadcastTaggingLink = broadcastBlock.LinkTo(taggingActionBlock, |
||
271 | new DataflowLinkOptions { PropagateCompletion = true }); |
||
272 | |||
273 | try |
||
274 | { |
||
275 | this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = "Balancing tags..."; }); |
||
276 | |||
277 | var tags = new ConcurrentBag<string>(); |
||
8 | office | 278 | foreach (var item in enumerable) |
279 | { |
||
280 | await foreach (var tag in _quickImageDatabase.GetTags(item.Name, _cancellationToken).WithCancellation(_cancellationToken)) |
||
281 | { |
||
282 | tags.Add(tag); |
||
283 | } |
||
284 | } |
||
7 | office | 285 | |
286 | var tasks = new List<Task>(); |
||
8 | office | 287 | foreach (var item in enumerable) |
288 | { |
||
289 | tasks.Add(bufferBlock.SendAsync((item.Name, Tags: tags), _cancellationToken)); |
||
290 | } |
||
7 | office | 291 | |
292 | await Task.WhenAll(tasks); |
||
293 | |||
294 | bufferBlock.Complete(); |
||
8 | office | 295 | |
7 | office | 296 | await bufferBlock.Completion; |
297 | await databaseActionBlock.Completion; |
||
298 | await taggingActionBlock.Completion; |
||
9 | office | 299 | |
300 | this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = "Tags balanced."; }); |
||
7 | office | 301 | } |
302 | catch (Exception exception) |
||
303 | { |
||
304 | Log.Warning(exception, "Could not balance tags."); |
||
9 | office | 305 | |
306 | this.InvokeIfRequired(form => { form.toolStripStatusLabel1.Text = "Error balancing tags..."; }); |
||
7 | office | 307 | } |
308 | } |
||
309 | |||
310 | private void SelectTags(IEnumerable<ListViewItem> items) |
||
311 | { |
||
312 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
313 | var count = enumerable.Length; |
||
314 | |||
315 | var bufferBlock = new BufferBlock<string>(); |
||
316 | |||
317 | async void IdleHandler(object idleHandlerSender, EventArgs idleHandlerArgs) |
||
318 | { |
||
319 | try |
||
320 | { |
||
321 | if (_combinedSelectionCancellationToken.IsCancellationRequested) |
||
322 | { |
||
323 | toolStripStatusLabel1.Text = "Selection cancelled."; |
||
324 | Application.Idle -= IdleHandler; |
||
325 | return; |
||
326 | } |
||
327 | |||
328 | if (!await bufferBlock.OutputAvailableAsync(_combinedSelectionCancellationToken)) |
||
329 | { |
||
330 | toolStripStatusLabel1.Text = "Items selected."; |
||
331 | Application.Idle -= IdleHandler; |
||
332 | return; |
||
333 | } |
||
334 | |||
335 | if (!bufferBlock.TryReceive(out var tag)) return; |
||
336 | |||
337 | if (tagListView.Items.ContainsKey(tag)) |
||
338 | { |
||
339 | tagListView.BeginUpdate(); |
||
340 | tagListView.Items[tag].Checked = true; |
||
341 | tagListView.EndUpdate(); |
||
342 | return; |
||
343 | } |
||
344 | |||
345 | tagListView.BeginUpdate(); |
||
346 | tagListView.Items.Add(new ListViewItem(tag) { Name = tag }); |
||
347 | tagListView.Items[tag].Checked = true; |
||
348 | tagListView.EndUpdate(); |
||
349 | } |
||
350 | catch (OperationCanceledException) |
||
351 | { |
||
352 | Application.Idle -= IdleHandler; |
||
353 | } |
||
354 | catch (Exception exception) |
||
355 | { |
||
356 | Application.Idle -= IdleHandler; |
||
357 | Log.Warning(exception, "Failed selecting tags for image."); |
||
358 | } |
||
359 | finally |
||
360 | { |
||
361 | _sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(50), () => |
||
362 | { |
||
363 | tagListView.BeginUpdate(); |
||
364 | tagListView.Sort(); |
||
365 | tagListView.EndUpdate(); |
||
366 | }, _formTaskScheduler, _cancellationToken); |
||
367 | } |
||
368 | } |
||
369 | |||
370 | if (count == 0) |
||
371 | { |
||
8 | office | 372 | if (_selectionCancellationTokenSource != null) |
373 | { |
||
374 | _selectionCancellationTokenSource.Cancel(); |
||
375 | } |
||
7 | office | 376 | |
377 | tagListView.BeginUpdate(); |
||
378 | foreach (var item in tagListView.CheckedItems.OfType<ListViewItem>()) |
||
379 | { |
||
380 | item.Checked = false; |
||
381 | } |
||
382 | |||
383 | tagListView.EndUpdate(); |
||
384 | return; |
||
385 | } |
||
386 | |||
8 | office | 387 | if (_selectionCancellationTokenSource != null) |
388 | { |
||
389 | _selectionCancellationTokenSource.Cancel(); |
||
390 | } |
||
7 | office | 391 | |
392 | _selectionCancellationTokenSource = new CancellationTokenSource(); |
||
393 | _selectionCancellationToken = _selectionCancellationTokenSource.Token; |
||
394 | _linkedSelectionCancellationTokenSource = |
||
395 | CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _selectionCancellationToken); |
||
396 | _combinedSelectionCancellationToken = _linkedSelectionCancellationTokenSource.Token; |
||
397 | _combinedSelectionCancellationToken.Register(() => { Application.Idle -= IdleHandler; }); |
||
398 | |||
399 | _selectionScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(50), async () => |
||
400 | { |
||
401 | try |
||
402 | { |
||
403 | tagListView.InvokeIfRequired(view => |
||
404 | { |
||
405 | view.BeginUpdate(); |
||
8 | office | 406 | |
7 | office | 407 | foreach (var item in view.CheckedItems.OfType<ListViewItem>()) |
408 | { |
||
409 | item.Checked = false; |
||
410 | } |
||
411 | |||
412 | view.EndUpdate(); |
||
413 | }); |
||
414 | |||
415 | this.InvokeIfRequired(form => |
||
416 | { |
||
417 | form.toolStripStatusLabel1.Text = "Selecting items..."; |
||
418 | Application.Idle += IdleHandler; |
||
419 | }); |
||
420 | |||
8 | office | 421 | var tasks = new ConcurrentBag<Task>(); |
7 | office | 422 | foreach (var item in enumerable) |
423 | { |
||
424 | await foreach (var tag in _quickImageDatabase.GetTags(item.Name, _combinedSelectionCancellationToken).WithCancellation(_combinedSelectionCancellationToken)) |
||
425 | { |
||
426 | tasks.Add(bufferBlock.SendAsync(tag, _combinedSelectionCancellationToken)); |
||
427 | } |
||
428 | } |
||
429 | |||
430 | await Task.WhenAll(tasks); |
||
431 | bufferBlock.Complete(); |
||
432 | } |
||
433 | catch (Exception exception) |
||
434 | { |
||
435 | this.InvokeIfRequired(form => |
||
436 | { |
||
437 | toolStripStatusLabel1.Text = "Failed selecting items."; |
||
438 | Application.Idle -= IdleHandler; |
||
439 | }); |
||
440 | |||
441 | Log.Warning(exception, "Could not select items."); |
||
442 | } |
||
443 | }, _combinedSelectionCancellationToken); |
||
444 | } |
||
445 | |||
8 | office | 446 | private void RelocateTo(IEnumerable<ListViewItem> items, string destinationDirectory, |
7 | office | 447 | CancellationToken cancellationToken) |
448 | { |
||
449 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
450 | |||
451 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
452 | toolStripProgressBar1.Minimum = 0; |
||
453 | toolStripProgressBar1.Maximum = enumerable.Length; |
||
454 | toolStripProgressBar1.Value = 0; |
||
455 | |||
456 | var transformBlock = |
||
457 | new TransformBlock<(ListViewItem Item, string File), (ListViewItem Item, Database.QuickImage Image)>( |
||
458 | async tuple => |
||
459 | { |
||
460 | try |
||
461 | { |
||
462 | var (item, path) = tuple; |
||
463 | |||
464 | var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken); |
||
465 | |||
466 | var name = Path.GetFileName(item.Name); |
||
467 | var file = Path.Combine(path, $"{name}"); |
||
468 | |||
469 | if (!await _quickImageDatabase.SetFile(item.Name, file, cancellationToken)) |
||
470 | return (null, null); |
||
471 | |||
472 | var newImage = new Database.QuickImage(file, image.Hash, image.Tags, image.Thumbnail); |
||
473 | |||
474 | return (Item: item, Image: newImage); |
||
475 | } |
||
476 | catch |
||
477 | { |
||
478 | return (null, null); |
||
479 | } |
||
480 | }, new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken }); |
||
481 | |||
482 | var actionBlock = new ActionBlock<(ListViewItem Item, Database.QuickImage Image)>(async tuple => |
||
483 | { |
||
484 | imageListView.BeginUpdate(); |
||
485 | try |
||
486 | { |
||
487 | var (item, image) = tuple; |
||
488 | |||
489 | if (item == null || image == null) return; |
||
490 | |||
491 | imageListView.Items.Remove(item); |
||
492 | |||
493 | var fileInfo = new FileInfo(image.File); |
||
494 | |||
495 | if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group)) |
||
496 | { |
||
497 | group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) |
||
498 | { Name = fileInfo.DirectoryName }; |
||
499 | _imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group); |
||
500 | imageListView.Groups.Add(group); |
||
501 | } |
||
502 | |||
503 | largeImageList.Images.RemoveByKey(item.Name); |
||
504 | largeImageList.Images.Add(image.File, image.Thumbnail); |
||
505 | |||
506 | var listViewItem = imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName) |
||
507 | { |
||
508 | Name = image.File, |
||
509 | ImageKey = image.File, |
||
510 | Text = fileInfo.Name, |
||
511 | Group = group |
||
512 | }); |
||
513 | imageListView.EnsureVisible(listViewItem.Index); |
||
514 | |||
515 | toolStripStatusLabel1.Text = "Relocating image directory..."; |
||
516 | toolStripProgressBar1.Increment(1); |
||
517 | } |
||
518 | catch |
||
519 | { |
||
520 | toolStripStatusLabel1.Text = "Failed to relocate image to directory..."; |
||
521 | toolStripProgressBar1.Increment(1); |
||
522 | } |
||
523 | finally |
||
524 | { |
||
525 | imageListView.EndUpdate(); |
||
526 | } |
||
527 | }, |
||
528 | new ExecutionDataflowBlockOptions |
||
529 | { CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler }); |
||
530 | |||
531 | toolStripStatusLabel1.Text = "Relocating images..."; |
||
532 | |||
533 | #pragma warning disable CS4014 |
||
534 | Task.Factory.StartNew(async () => |
||
535 | #pragma warning restore CS4014 |
||
536 | { |
||
537 | try |
||
538 | { |
||
539 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
540 | } |
||
541 | catch |
||
542 | { |
||
543 | return; |
||
544 | } |
||
545 | |||
546 | try |
||
547 | { |
||
8 | office | 548 | using var _1 = transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
7 | office | 549 | |
8 | office | 550 | var tasks = new ConcurrentBag<Task>(); |
7 | office | 551 | foreach (var item in enumerable) |
552 | { |
||
8 | office | 553 | if (cancellationToken.IsCancellationRequested) |
554 | { |
||
555 | return; |
||
556 | } |
||
7 | office | 557 | |
8 | office | 558 | var task = transformBlock.SendAsync((Item: item, File: destinationDirectory), cancellationToken); |
559 | tasks.Add(task); |
||
7 | office | 560 | } |
561 | |||
8 | office | 562 | await Task.WhenAll(tasks); |
7 | office | 563 | transformBlock.Complete(); |
564 | await actionBlock.Completion; |
||
565 | } |
||
566 | finally |
||
567 | { |
||
568 | _imageListViewLock.Release(); |
||
569 | } |
||
570 | }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); |
||
571 | } |
||
572 | |||
8 | office | 573 | private void RemoveImages(IEnumerable<ListViewItem> items, CancellationToken cancellationToken) |
7 | office | 574 | { |
575 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
576 | |||
577 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
578 | toolStripProgressBar1.Minimum = 0; |
||
579 | toolStripProgressBar1.Maximum = enumerable.Length; |
||
580 | toolStripProgressBar1.Value = 0; |
||
581 | |||
8 | office | 582 | var bufferBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions { CancellationToken = cancellationToken }); |
583 | |||
7 | office | 584 | var actionBlock = new ActionBlock<ListViewItem>(listViewItem => |
585 | { |
||
586 | toolStripStatusLabel1.Text = $"Unloading image {listViewItem.Name}"; |
||
587 | imageListView.Items.Remove(listViewItem); |
||
588 | toolStripProgressBar1.Increment(1); |
||
589 | }, |
||
8 | office | 590 | new ExecutionDataflowBlockOptions{ CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler }); |
7 | office | 591 | |
592 | toolStripStatusLabel1.Text = "Unloading images..."; |
||
593 | |||
594 | #pragma warning disable CS4014 |
||
595 | Task.Factory.StartNew(async () => |
||
596 | #pragma warning restore CS4014 |
||
597 | { |
||
598 | try |
||
599 | { |
||
600 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
601 | } |
||
602 | catch |
||
603 | { |
||
604 | return; |
||
605 | } |
||
606 | |||
607 | try |
||
608 | { |
||
8 | office | 609 | using var _ = bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
7 | office | 610 | |
611 | foreach (var item in enumerable) |
||
612 | { |
||
613 | if (cancellationToken.IsCancellationRequested) return; |
||
614 | |||
615 | try |
||
616 | { |
||
8 | office | 617 | if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken)) |
618 | { |
||
619 | continue; |
||
620 | } |
||
7 | office | 621 | |
622 | await bufferBlock.SendAsync(item, cancellationToken); |
||
623 | } |
||
624 | catch |
||
625 | { |
||
626 | // ignored |
||
627 | } |
||
628 | } |
||
629 | |||
630 | bufferBlock.Complete(); |
||
631 | await actionBlock.Completion; |
||
632 | } |
||
633 | finally |
||
634 | { |
||
635 | _imageListViewLock.Release(); |
||
636 | } |
||
637 | }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); |
||
638 | } |
||
639 | |||
640 | public async Task RemoveMissingAsync(IEnumerable<string> groups, CancellationToken cancellationToken) |
||
641 | { |
||
642 | var enumerable = groups as string[] ?? groups.ToArray(); |
||
643 | |||
644 | var bufferBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions |
||
645 | { CancellationToken = cancellationToken }); |
||
646 | |||
647 | var transformBlock = new TransformBlock<ListViewItem, ListViewItem>(listViewItem => |
||
648 | { |
||
649 | if (File.Exists(listViewItem.Name)) |
||
650 | { |
||
651 | toolStripStatusLabel1.Text = $"Image {listViewItem.Name} exists."; |
||
652 | return null; |
||
653 | } |
||
654 | |||
655 | toolStripStatusLabel1.Text = $"Deleting image {listViewItem.Name}"; |
||
656 | imageListView.Items.Remove(listViewItem); |
||
657 | |||
658 | return listViewItem; |
||
659 | }, |
||
660 | new ExecutionDataflowBlockOptions |
||
661 | { CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler }); |
||
662 | |||
663 | var actionBlock = new ActionBlock<ListViewItem>(async item => |
||
664 | { |
||
8 | office | 665 | if (item == null) |
666 | { |
||
667 | return; |
||
668 | } |
||
7 | office | 669 | |
670 | try |
||
671 | { |
||
672 | await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken); |
||
673 | } |
||
674 | catch |
||
675 | { |
||
676 | // ignored |
||
677 | } |
||
678 | }); |
||
679 | |||
680 | using var _1 = bufferBlock.LinkTo(transformBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||
681 | using var _2 = transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||
682 | using var _3 = transformBlock.LinkTo(DataflowBlock.NullTarget<ListViewItem>()); |
||
683 | |||
684 | toolStripStatusLabel1.Text = "Removing missing images..."; |
||
685 | try |
||
686 | { |
||
687 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
688 | } |
||
689 | catch |
||
690 | { |
||
691 | return; |
||
692 | } |
||
693 | |||
694 | toolStripProgressBar1.Style = ProgressBarStyle.Marquee; |
||
695 | toolStripProgressBar1.MarqueeAnimationSpeed = 30; |
||
696 | |||
697 | try |
||
698 | { |
||
8 | office | 699 | var tasks = new ConcurrentBag<Task>(); |
700 | foreach (var group in enumerable) |
||
701 | { |
||
702 | foreach (var item in imageListView.Items.OfType<ListViewItem>()) |
||
703 | { |
||
704 | if (string.Equals(item.Group.Name, group, StringComparison.OrdinalIgnoreCase)) |
||
705 | { |
||
706 | tasks.Add(bufferBlock.SendAsync(item, cancellationToken)); |
||
707 | } |
||
708 | } |
||
709 | } |
||
7 | office | 710 | |
711 | bufferBlock.Complete(); |
||
712 | |||
713 | await Task.WhenAll(tasks); |
||
714 | |||
715 | await transformBlock.Completion.ContinueWith(_ => { actionBlock.Complete(); }, cancellationToken); |
||
716 | |||
717 | await actionBlock.Completion; |
||
718 | } |
||
719 | finally |
||
720 | { |
||
721 | toolStripProgressBar1.MarqueeAnimationSpeed = 0; |
||
722 | _imageListViewLock.Release(); |
||
723 | toolStripStatusLabel1.Text = "Done removing images."; |
||
724 | } |
||
725 | } |
||
9 | office | 726 | |
727 | private async Task LoadDataObjectAsync(IDataObject dataObject) |
||
728 | { |
||
729 | var inputBlock = new BufferBlock<(string File, Stream Data, Definition Mime)>( |
||
730 | new ExecutionDataflowBlockOptions |
||
731 | { CancellationToken = _cancellationToken }); |
||
7 | office | 732 | |
9 | office | 733 | var transformBlock = new TransformBlock<(string File, Stream Data, Definition Mime), string>(async tuple => |
734 | { |
||
735 | try |
||
736 | { |
||
737 | var (_, data, _) = tuple; |
||
738 | data.Position = 0L; |
||
739 | |||
740 | var path = Path.GetTempPath(); |
||
741 | var name = Path.GetTempFileName(); |
||
742 | |||
743 | using var memoryStream = new MemoryStream(); |
||
744 | switch (Configuration.InboundDragDrop.DragDropConvertType) |
||
745 | { |
||
746 | case "image/jpeg": |
||
747 | { |
||
748 | using var convertStream = |
||
749 | await _imageTool.ConvertTo(data, MagickFormat.Jpeg, _cancellationToken); |
||
750 | await convertStream.CopyToAsync(memoryStream); |
||
751 | name = Path.ChangeExtension(name, "jpg"); |
||
752 | break; |
||
753 | } |
||
754 | case "image/png": |
||
755 | { |
||
756 | using var convertStream = |
||
757 | await _imageTool.ConvertTo(data, MagickFormat.Png, _cancellationToken); |
||
758 | await convertStream.CopyToAsync(memoryStream); |
||
759 | name = Path.ChangeExtension(name, "png"); |
||
760 | break; |
||
761 | } |
||
762 | case "image/bmp": |
||
763 | { |
||
764 | using var convertStream = |
||
765 | await _imageTool.ConvertTo(data, MagickFormat.Bmp, _cancellationToken); |
||
766 | await convertStream.CopyToAsync(memoryStream); |
||
767 | name = Path.ChangeExtension(name, "bmp"); |
||
768 | break; |
||
769 | } |
||
770 | case "image/gif": |
||
771 | { |
||
772 | using var convertStream = |
||
773 | await _imageTool.ConvertTo(data, MagickFormat.Gif, _cancellationToken); |
||
774 | await convertStream.CopyToAsync(memoryStream); |
||
775 | name = Path.ChangeExtension(name, "gif"); |
||
776 | break; |
||
777 | } |
||
778 | // create a copy for files that do not have to be converted |
||
779 | default: |
||
780 | throw new ArgumentException( |
||
781 | "Unsupported conversion type for image."); |
||
782 | } |
||
783 | |||
784 | var destinationFile = Path.Combine(path, name); |
||
785 | |||
786 | memoryStream.Position = 0L; |
||
787 | await Miscellaneous.CopyFileAsync(memoryStream, destinationFile, _cancellationToken); |
||
788 | |||
789 | return destinationFile; |
||
790 | } |
||
791 | catch (Exception exception) |
||
792 | { |
||
793 | Log.Warning(exception, "Unable to convert input file."); |
||
794 | return null; |
||
795 | } |
||
796 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
797 | |||
798 | var copyTransformBlock = new TransformBlock<(string File, Stream Data, Definition Mime), string>( |
||
799 | async tuple => |
||
800 | { |
||
801 | try |
||
802 | { |
||
803 | var (_, data, mime) = tuple; |
||
804 | data.Position = 0L; |
||
805 | |||
806 | var path = Path.GetTempPath(); |
||
807 | var name = Path.GetTempFileName(); |
||
808 | var extension = mime.File.Extensions.FirstOrDefault(); |
||
809 | name = Path.ChangeExtension(name, extension); |
||
810 | var destinationFile = Path.Combine(path, name); |
||
811 | |||
812 | await Miscellaneous.CopyFileAsync(data, destinationFile, _cancellationToken); |
||
813 | |||
814 | return destinationFile; |
||
815 | } |
||
816 | catch (Exception exception) |
||
817 | { |
||
818 | Log.Warning(exception, "Unable to create a copy of the file."); |
||
819 | |||
820 | return null; |
||
821 | } |
||
822 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
823 | |||
824 | var importTransformBlock = |
||
825 | new TransformBlock<(string File, Stream Data, Definition Mime), string>( |
||
826 | tuple => Task.FromResult(tuple.File), |
||
827 | new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
828 | |||
829 | var outputBlock = new BufferBlock<string>(new ExecutionDataflowBlockOptions |
||
830 | { CancellationToken = _cancellationToken }); |
||
831 | |||
832 | using var _1 = inputBlock.LinkTo(transformBlock, |
||
833 | tuple => Configuration.InboundDragDrop.ConvertOnDragDrop && |
||
834 | Configuration.SupportedFormats.IsSupportedImage(tuple.Mime.File.MimeType) && |
||
835 | !Configuration.InboundDragDrop.DragDropConvertExclude |
||
836 | .IsExcludedImage(tuple.Mime.File.MimeType)); |
||
837 | using var _2 = transformBlock.LinkTo(outputBlock, file => !string.IsNullOrEmpty(file)); |
||
838 | using var _3 = transformBlock.LinkTo(DataflowBlock.NullTarget<string>()); |
||
839 | |||
840 | using var _4 = inputBlock.LinkTo(copyTransformBlock, tuple => Configuration.InboundDragDrop.CopyOnDragDrop); |
||
841 | using var _5 = copyTransformBlock.LinkTo(outputBlock); |
||
842 | using var _6 = copyTransformBlock.LinkTo(DataflowBlock.NullTarget<string>()); |
||
843 | |||
844 | using var _7 = inputBlock.LinkTo(importTransformBlock, |
||
845 | tuple => !Configuration.InboundDragDrop.ConvertOnDragDrop && |
||
846 | !Configuration.InboundDragDrop.CopyOnDragDrop); |
||
847 | using var _8 = importTransformBlock.LinkTo(outputBlock); |
||
848 | using var _9 = importTransformBlock.LinkTo(DataflowBlock.NullTarget<string>()); |
||
849 | |||
850 | var tasks = new List<Task>(); |
||
851 | await foreach (var (file, data, mime) in GetDragDropFiles(dataObject, _magicMime, _cancellationToken)) |
||
852 | { |
||
853 | if (!Configuration.SupportedFormats.IsSupported(mime.File.MimeType)) |
||
854 | { |
||
855 | continue; |
||
856 | } |
||
857 | |||
858 | tasks.Add(inputBlock.SendAsync((File: file, Data: data, Mime: mime), _cancellationToken)); |
||
859 | } |
||
860 | |||
861 | await Task.WhenAll(tasks); |
||
862 | inputBlock.Complete(); |
||
863 | |||
864 | await inputBlock.Completion.ContinueWith(_ => |
||
865 | { |
||
866 | transformBlock.Complete(); |
||
867 | copyTransformBlock.Complete(); |
||
868 | importTransformBlock.Complete(); |
||
869 | }, _cancellationToken); |
||
870 | |||
871 | await Task.WhenAll(transformBlock.Completion, copyTransformBlock.Completion, importTransformBlock.Completion).ContinueWith(_ => { outputBlock.Complete(); }, _cancellationToken); |
||
872 | |||
873 | var set = new HashSet<string>(); |
||
874 | while (await outputBlock.OutputAvailableAsync(_cancellationToken)) |
||
875 | { |
||
876 | if (!outputBlock.TryReceiveAll(out var items)) |
||
877 | { |
||
878 | continue; |
||
879 | } |
||
880 | |||
881 | set.UnionWith(items); |
||
882 | } |
||
883 | |||
884 | await LoadFilesAsync(set, _magicMime, _cancellationToken); |
||
885 | } |
||
886 | |||
7 | office | 887 | private async Task LoadFilesAsync(IEnumerable<string> files, MagicMime magicMime, |
888 | CancellationToken cancellationToken) |
||
889 | { |
||
890 | var enumerable = files as string[] ?? files.ToArray(); |
||
891 | |||
892 | var updateImageListViewActionBlock = new ActionBlock<Database.QuickImage>( |
||
893 | async tuple => |
||
894 | { |
||
895 | try |
||
896 | { |
||
897 | var (file, tags, thumbnail) = (tuple.File, tuple.Tags, tuple.Thumbnail); |
||
898 | |||
899 | if (imageListView.Items.ContainsKey(file)) |
||
900 | { |
||
901 | toolStripStatusLabel1.Text = $"File {file} already exits."; |
||
902 | return; |
||
903 | } |
||
904 | |||
8 | office | 905 | if (!largeImageList.Images.ContainsKey(file)) |
906 | { |
||
907 | largeImageList.Images.Add(file, thumbnail); |
||
908 | } |
||
7 | office | 909 | |
910 | var fileInfo = new FileInfo(file); |
||
911 | if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group)) |
||
912 | { |
||
8 | office | 913 | group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) { Name = fileInfo.DirectoryName }; |
7 | office | 914 | _imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group); |
915 | imageListView.Groups.Add(group); |
||
916 | } |
||
917 | |||
8 | office | 918 | var imageListViewItem = new ListViewItem(file) { Name = file, ImageKey = file, Text = fileInfo.Name, Group = group }; |
7 | office | 919 | imageListView.Items.Add(imageListViewItem); |
920 | imageListView.EnsureVisible(imageListViewItem.Index); |
||
921 | |||
922 | toolStripStatusLabel1.Text = $"Added file {file} to the list."; |
||
923 | |||
924 | foreach (var tag in tags) |
||
925 | { |
||
926 | if (!_tagAutoCompleteStringCollection.Contains(tag)) |
||
927 | { |
||
928 | _tagAutoCompleteStringCollection.Add(tag); |
||
929 | } |
||
930 | |||
931 | if (tagListView.Items.ContainsKey(tag)) |
||
932 | { |
||
933 | continue; |
||
934 | } |
||
935 | |||
936 | tagListView.BeginUpdate(); |
||
937 | tagListView.Items.Add(new ListViewItem(tag) { Name = tag }); |
||
938 | tagListView.EndUpdate(); |
||
939 | } |
||
940 | } |
||
941 | catch (Exception exception) |
||
942 | { |
||
943 | Log.Warning(exception, "Could not update image list view."); |
||
944 | } |
||
945 | }, |
||
8 | office | 946 | new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler }); |
7 | office | 947 | |
8 | office | 948 | var fileInputBlock = new BufferBlock<string>(new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken }); |
7 | office | 949 | |
950 | var updateImageTagsTransformBlock = new TransformBlock<string, Database.QuickImage>(async file => |
||
951 | { |
||
952 | try |
||
953 | { |
||
954 | var tags = new HashSet<string>(); |
||
955 | |||
8 | office | 956 | var databaseTags = await _quickImageDatabase.GetTags(file, cancellationToken).ToArrayAsync(cancellationToken); |
7 | office | 957 | |
958 | tags.UnionWith(databaseTags); |
||
959 | |||
960 | var mime = await magicMime.GetMimeType(file, cancellationToken); |
||
961 | |||
962 | if (Configuration.SupportedFormats.IsSupportedImage(mime)) |
||
963 | { |
||
964 | await foreach (var iptcTag in _tagManager.GetIptcKeywords(file, cancellationToken)) |
||
965 | { |
||
966 | tags.UnionWith(new[] { iptcTag }); |
||
967 | } |
||
968 | } |
||
969 | |||
970 | await _quickImageDatabase.AddTagsAsync(file, tags, cancellationToken); |
||
971 | |||
972 | return await _quickImageDatabase.GetImageAsync(file, cancellationToken); |
||
973 | } |
||
974 | catch (Exception exception) |
||
975 | { |
||
976 | Log.Warning(exception, $"Could not add {file} to database."); |
||
977 | |||
978 | return null; |
||
979 | } |
||
980 | }, new ExecutionDataflowBlockOptions { CancellationToken = cancellationToken }); |
||
981 | |||
982 | var createImageTransformBlock = new TransformBlock<string, Database.QuickImage>(async file => |
||
983 | { |
||
984 | try |
||
985 | { |
||
986 | var tags = Array.Empty<string>(); |
||
987 | |||
988 | using var imageCollection = new MagickImageCollection(file, new MagickReadSettings { FrameIndex = 0, FrameCount = 1 }); |
||
989 | |||
990 | var imageFrame = imageCollection[0]; |
||
991 | |||
992 | var mime = await magicMime.GetMimeType(file, cancellationToken); |
||
993 | |||
994 | if (Configuration.SupportedFormats.IsSupportedImage(mime)) |
||
995 | { |
||
996 | var iptcTags = _tagManager.GetIptcKeywords(imageFrame); |
||
8 | office | 997 | |
7 | office | 998 | tags = iptcTags.ToArray(); |
999 | } |
||
1000 | |||
1001 | var buffer = imageFrame.ToByteArray(MagickFormat.Bmp); |
||
1002 | using var bitmapMemoryStream = new MemoryStream(buffer); |
||
1003 | |||
1004 | bitmapMemoryStream.Position = 0L; |
||
1005 | using var hashBitmap = (Bitmap)Image.FromStream(bitmapMemoryStream); |
||
1006 | var hash = ImagePhash.ComputeDigest(hashBitmap.ToBitmap().ToLuminanceImage()); |
||
1007 | |||
1008 | bitmapMemoryStream.Position = 0L; |
||
1009 | using var thumbnailBitmap = await CreateThumbnail(bitmapMemoryStream, 128, 128, cancellationToken); |
||
1010 | var thumbnail = new Bitmap(thumbnailBitmap); |
||
1011 | thumbnailBitmap.Dispose(); |
||
1012 | |||
1013 | await _quickImageDatabase.AddImageAsync(file, hash, tags, thumbnail, cancellationToken); |
||
1014 | |||
1015 | return new Database.QuickImage(file, hash, tags, thumbnail); |
||
1016 | } |
||
1017 | catch (Exception exception) |
||
1018 | { |
||
1019 | Log.Warning(exception, $"Could not add {file} to database."); |
||
1020 | |||
1021 | return null; |
||
1022 | } |
||
1023 | }); |
||
1024 | |||
1025 | using var _2 = fileInputBlock.LinkTo(updateImageTagsTransformBlock, file => |
||
1026 | { |
||
1027 | try |
||
1028 | { |
||
1029 | return _quickImageDatabase.Exists(file, cancellationToken); |
||
1030 | } |
||
1031 | catch (Exception exception) |
||
1032 | { |
||
1033 | Log.Warning(exception, $"Could not query database for file {file}"); |
||
1034 | return false; |
||
1035 | } |
||
1036 | }); |
||
1037 | using var _4 = updateImageTagsTransformBlock.LinkTo(updateImageListViewActionBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||
1038 | using var _5 = |
||
1039 | updateImageTagsTransformBlock.LinkTo(DataflowBlock.NullTarget<Database.QuickImage>(), image => |
||
1040 | { |
||
1041 | var r = image == null; |
||
8 | office | 1042 | |
7 | office | 1043 | return r; |
1044 | }); |
||
1045 | |||
1046 | using var _3 = fileInputBlock.LinkTo(createImageTransformBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||
1047 | using var _6 = createImageTransformBlock.LinkTo(updateImageListViewActionBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||
1048 | using var _7 = |
||
1049 | createImageTransformBlock.LinkTo(DataflowBlock.NullTarget<Database.QuickImage>(), image => |
||
1050 | { |
||
1051 | var r = image == null; |
||
8 | office | 1052 | |
7 | office | 1053 | return r; |
1054 | }); |
||
1055 | |||
1056 | toolStripStatusLabel1.Text = "Loading images..."; |
||
1057 | try |
||
1058 | { |
||
1059 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
1060 | } |
||
1061 | catch |
||
1062 | { |
||
1063 | return; |
||
1064 | } |
||
1065 | |||
1066 | try |
||
1067 | { |
||
1068 | toolStripProgressBar1.Style = ProgressBarStyle.Marquee; |
||
1069 | toolStripProgressBar1.MarqueeAnimationSpeed = 30; |
||
1070 | |||
8 | office | 1071 | var tasks = new ConcurrentBag<Task>(); |
7 | office | 1072 | foreach (var item in enumerable) |
1073 | { |
||
1074 | await foreach (var entry in GetFilesAsync(item, Configuration, magicMime, cancellationToken).WithCancellation(cancellationToken)) |
||
1075 | { |
||
1076 | tasks.Add(fileInputBlock.SendAsync(entry, cancellationToken)); |
||
1077 | } |
||
1078 | } |
||
1079 | |||
1080 | await Task.WhenAll(tasks); |
||
1081 | |||
1082 | fileInputBlock.Complete(); |
||
1083 | |||
1084 | await updateImageListViewActionBlock.Completion; |
||
1085 | } |
||
1086 | finally |
||
1087 | { |
||
1088 | toolStripProgressBar1.MarqueeAnimationSpeed = 0; |
||
1089 | _imageListViewLock.Release(); |
||
8 | office | 1090 | |
7 | office | 1091 | toolStripStatusLabel1.Text = "Done loading images."; |
1092 | } |
||
1093 | } |
||
1094 | |||
1095 | private async Task OcrImagesAsync(IEnumerable<ListViewItem> items, CancellationToken cancellationToken) |
||
1096 | { |
||
1097 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
1098 | |||
1099 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
1100 | toolStripProgressBar1.Minimum = 0; |
||
1101 | toolStripProgressBar1.Maximum = enumerable.Length; |
||
1102 | toolStripProgressBar1.Value = 0; |
||
1103 | |||
1104 | void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e) |
||
1105 | { |
||
1106 | switch (e) |
||
1107 | { |
||
1108 | case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess: |
||
1109 | toolStripStatusLabel1.Text = |
||
1110 | $"Added {imageListViewItemProgressSuccess.Tags.Count()} to image {imageListViewItemProgressSuccess.Item.Name} using OCR."; |
||
1111 | foreach (var tag in imageListViewItemProgressSuccess.Tags) |
||
1112 | { |
||
1113 | if (tagListView.Items.ContainsKey(tag)) |
||
1114 | { |
||
1115 | tagListView.BeginUpdate(); |
||
1116 | tagListView.Items[tag].Checked = true; |
||
1117 | tagListView.EndUpdate(); |
||
1118 | continue; |
||
1119 | } |
||
1120 | |||
1121 | tagListView.BeginUpdate(); |
||
1122 | tagListView.Items.Add(new ListViewItem(tag) { Name = tag }); |
||
1123 | tagListView.Items[tag].Checked = true; |
||
1124 | tagListView.EndUpdate(); |
||
1125 | } |
||
1126 | |||
1127 | break; |
||
1128 | case ImageListViewItemProgressFailure<ListViewItem> _: |
||
1129 | break; |
||
1130 | } |
||
1131 | |||
1132 | toolStripProgressBar1.Increment(1); |
||
1133 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
||
1134 | { |
||
1135 | _listViewItemProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1136 | } |
||
1137 | } |
||
1138 | |||
1139 | toolStripStatusLabel1.Text = "Settings text in images to tags using OCR..."; |
||
1140 | try |
||
1141 | { |
||
1142 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
1143 | } |
||
1144 | catch |
||
1145 | { |
||
1146 | return; |
||
1147 | } |
||
1148 | |||
1149 | _listViewItemProgress.ProgressChanged += QuickImageListViewItemProgress; |
||
1150 | try |
||
1151 | { |
||
1152 | await OcrImages(enumerable, _listViewItemProgress, cancellationToken); |
||
1153 | } |
||
1154 | catch (Exception exception) |
||
1155 | { |
||
1156 | Log.Error(exception, "Error while scanning text in images."); |
||
1157 | |||
1158 | _listViewItemProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1159 | _imageListViewLock.Release(); |
||
1160 | } |
||
1161 | } |
||
1162 | |||
1163 | private async Task ConvertImagesAsync(IEnumerable<ListViewItem> items, string extension, |
||
1164 | CancellationToken cancellationToken) |
||
1165 | { |
||
1166 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
1167 | |||
1168 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
1169 | toolStripProgressBar1.Minimum = 0; |
||
1170 | toolStripProgressBar1.Maximum = enumerable.Length; |
||
1171 | toolStripProgressBar1.Value = 0; |
||
1172 | |||
8 | office | 1173 | void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)> e) |
7 | office | 1174 | { |
1175 | switch (e) |
||
1176 | { |
||
8 | office | 1177 | case ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)> imageListViewItemProgressSuccess: |
7 | office | 1178 | if (imageListViewItemProgressSuccess.Item is { } tuple) |
1179 | { |
||
1180 | var (item, image) = tuple; |
||
1181 | |||
1182 | imageListView.BeginUpdate(); |
||
1183 | try |
||
1184 | { |
||
1185 | imageListView.Items.Remove(item); |
||
1186 | |||
1187 | var fileInfo = new FileInfo(image.File); |
||
1188 | |||
1189 | if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group)) |
||
1190 | { |
||
1191 | group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) |
||
1192 | { Name = fileInfo.DirectoryName }; |
||
1193 | _imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group); |
||
1194 | imageListView.Groups.Add(group); |
||
1195 | } |
||
1196 | |||
1197 | largeImageList.Images.RemoveByKey(item.Name); |
||
8 | office | 1198 | |
7 | office | 1199 | largeImageList.Images.Add(image.File, image.Thumbnail); |
1200 | |||
1201 | imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName) |
||
1202 | { |
||
1203 | Name = image.File, |
||
1204 | ImageKey = image.File, |
||
1205 | Text = fileInfo.Name, |
||
1206 | Group = group |
||
1207 | }); |
||
1208 | } |
||
1209 | finally |
||
1210 | { |
||
1211 | imageListView.EndUpdate(); |
||
1212 | } |
||
1213 | } |
||
1214 | |||
1215 | break; |
||
1216 | case ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)> _: |
||
1217 | break; |
||
1218 | } |
||
1219 | |||
1220 | toolStripStatusLabel1.Text = "Converting images..."; |
||
1221 | toolStripProgressBar1.Increment(1); |
||
1222 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
||
1223 | { |
||
1224 | _quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1225 | _imageListViewLock.Release(); |
||
1226 | } |
||
1227 | } |
||
1228 | |||
1229 | toolStripStatusLabel1.Text = "Converting images..."; |
||
1230 | try |
||
1231 | { |
||
1232 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
1233 | } |
||
1234 | catch |
||
1235 | { |
||
1236 | return; |
||
1237 | } |
||
1238 | |||
1239 | _quickImageListViewProgress.ProgressChanged += QuickImageListViewItemProgress; |
||
1240 | try |
||
1241 | { |
||
1242 | await ConvertImages(enumerable, extension, _quickImageListViewProgress, cancellationToken); |
||
1243 | } |
||
1244 | catch (Exception exception) |
||
1245 | { |
||
1246 | Log.Error(exception, "Error while converting images."); |
||
1247 | |||
1248 | _quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1249 | _imageListViewLock.Release(); |
||
1250 | } |
||
1251 | } |
||
1252 | |||
1253 | private async Task RenameImageAsync(ListViewItem item, string destinationFileName, |
||
1254 | CancellationToken cancellationToken) |
||
1255 | { |
||
1256 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
1257 | toolStripProgressBar1.Minimum = 0; |
||
1258 | toolStripProgressBar1.Maximum = 1; |
||
1259 | toolStripProgressBar1.Value = 0; |
||
1260 | |||
8 | office | 1261 | void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)> e) |
7 | office | 1262 | { |
1263 | switch (e) |
||
1264 | { |
||
1265 | case ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)> |
||
1266 | imageListViewItemProgressSuccess: |
||
1267 | if (imageListViewItemProgressSuccess.Item is { } tuple) |
||
1268 | { |
||
1269 | var (item, image) = tuple; |
||
1270 | |||
1271 | imageListView.BeginUpdate(); |
||
8 | office | 1272 | |
7 | office | 1273 | try |
1274 | { |
||
1275 | imageListView.Items.Remove(item); |
||
1276 | |||
1277 | var fileInfo = new FileInfo(image.File); |
||
1278 | |||
1279 | if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group)) |
||
1280 | { |
||
8 | office | 1281 | group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) { Name = fileInfo.DirectoryName }; |
7 | office | 1282 | _imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group); |
1283 | imageListView.Groups.Add(group); |
||
1284 | } |
||
1285 | |||
1286 | largeImageList.Images.RemoveByKey(item.Name); |
||
1287 | largeImageList.Images.Add(image.File, image.Thumbnail); |
||
1288 | |||
1289 | var listViewItem = imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName) |
||
1290 | { |
||
1291 | Name = image.File, |
||
1292 | ImageKey = image.File, |
||
1293 | Text = fileInfo.Name, |
||
1294 | Group = group |
||
1295 | }); |
||
1296 | imageListView.EnsureVisible(listViewItem.Index); |
||
1297 | } |
||
1298 | finally |
||
1299 | { |
||
1300 | imageListView.EndUpdate(); |
||
1301 | } |
||
1302 | } |
||
1303 | |||
1304 | break; |
||
1305 | case ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)> _: |
||
1306 | break; |
||
1307 | } |
||
1308 | |||
1309 | toolStripStatusLabel1.Text = "Renaming image..."; |
||
1310 | toolStripProgressBar1.Increment(1); |
||
1311 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
||
1312 | { |
||
1313 | _quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1314 | _imageListViewLock.Release(); |
||
1315 | } |
||
1316 | } |
||
1317 | |||
1318 | toolStripStatusLabel1.Text = "Renaming image..."; |
||
1319 | try |
||
1320 | { |
||
1321 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
1322 | } |
||
1323 | catch |
||
1324 | { |
||
1325 | return; |
||
1326 | } |
||
1327 | |||
1328 | _quickImageListViewProgress.ProgressChanged += QuickImageListViewItemProgress; |
||
1329 | try |
||
1330 | { |
||
1331 | var directoryName = Path.GetDirectoryName(item.Name); |
||
1332 | |||
8 | office | 1333 | await RenameImage(item, Path.Combine(directoryName, destinationFileName), _quickImageListViewProgress, cancellationToken); |
7 | office | 1334 | } |
1335 | catch (Exception exception) |
||
1336 | { |
||
1337 | Log.Error(exception, "Error while renaming image."); |
||
1338 | |||
1339 | _quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1340 | _imageListViewLock.Release(); |
||
1341 | } |
||
1342 | } |
||
1343 | |||
1344 | private async Task MoveImagesAsync(IEnumerable<ListViewItem> items, string destinationDirectory, |
||
1345 | CancellationToken cancellationToken) |
||
1346 | { |
||
1347 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
1348 | |||
1349 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
1350 | toolStripProgressBar1.Minimum = 0; |
||
1351 | toolStripProgressBar1.Maximum = enumerable.Length; |
||
1352 | toolStripProgressBar1.Value = 0; |
||
1353 | |||
8 | office | 1354 | void QuickImageListViewItemProgress(object sender, ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)> e) |
7 | office | 1355 | { |
1356 | switch (e) |
||
1357 | { |
||
8 | office | 1358 | case ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)> imageListViewItemProgressSuccess: |
7 | office | 1359 | if (imageListViewItemProgressSuccess.Item is { } tuple) |
1360 | { |
||
1361 | var (item, image) = tuple; |
||
1362 | |||
1363 | imageListView.BeginUpdate(); |
||
1364 | try |
||
1365 | { |
||
1366 | imageListView.Items.Remove(item); |
||
1367 | |||
1368 | var fileInfo = new FileInfo(image.File); |
||
1369 | |||
1370 | if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group)) |
||
1371 | { |
||
1372 | group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) |
||
1373 | { Name = fileInfo.DirectoryName }; |
||
1374 | _imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group); |
||
1375 | imageListView.Groups.Add(group); |
||
1376 | } |
||
1377 | |||
1378 | largeImageList.Images.RemoveByKey(item.Name); |
||
1379 | largeImageList.Images.Add(image.File, image.Thumbnail); |
||
1380 | |||
1381 | var listViewItem = imageListView.Items.Add(new ListViewItem(fileInfo.DirectoryName) |
||
1382 | { |
||
1383 | Name = image.File, |
||
1384 | ImageKey = image.File, |
||
1385 | Text = fileInfo.Name, |
||
1386 | Group = group |
||
1387 | }); |
||
8 | office | 1388 | |
7 | office | 1389 | imageListView.EnsureVisible(listViewItem.Index); |
1390 | } |
||
1391 | finally |
||
1392 | { |
||
1393 | imageListView.EndUpdate(); |
||
1394 | } |
||
1395 | } |
||
1396 | |||
1397 | break; |
||
1398 | case ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)> _: |
||
1399 | break; |
||
1400 | } |
||
1401 | |||
1402 | toolStripStatusLabel1.Text = "Moving images..."; |
||
1403 | toolStripProgressBar1.Increment(1); |
||
1404 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
||
1405 | { |
||
1406 | _quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1407 | _imageListViewLock.Release(); |
||
1408 | } |
||
1409 | } |
||
1410 | |||
1411 | toolStripStatusLabel1.Text = "Moving images..."; |
||
1412 | try |
||
1413 | { |
||
1414 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
1415 | } |
||
1416 | catch |
||
1417 | { |
||
1418 | return; |
||
1419 | } |
||
1420 | |||
1421 | _quickImageListViewProgress.ProgressChanged += QuickImageListViewItemProgress; |
||
1422 | try |
||
1423 | { |
||
1424 | await MoveImages(enumerable, destinationDirectory, _quickImageListViewProgress, cancellationToken); |
||
1425 | } |
||
1426 | catch |
||
1427 | { |
||
1428 | _quickImageListViewProgress.ProgressChanged -= QuickImageListViewItemProgress; |
||
1429 | _imageListViewLock.Release(); |
||
1430 | } |
||
1431 | } |
||
1432 | |||
1433 | private void DeleteImages(IEnumerable<ListViewItem> items, CancellationToken cancellationToken) |
||
1434 | { |
||
1435 | var enumerable = items as ListViewItem[] ?? items.ToArray(); |
||
1436 | |||
1437 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
1438 | toolStripProgressBar1.Minimum = 0; |
||
1439 | toolStripProgressBar1.Maximum = enumerable.Length; |
||
1440 | toolStripProgressBar1.Value = 0; |
||
1441 | |||
1442 | var bufferBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions |
||
1443 | { CancellationToken = cancellationToken }); |
||
1444 | var actionBlock = new ActionBlock<ListViewItem>(listViewItem => |
||
1445 | { |
||
1446 | toolStripStatusLabel1.Text = $"Deleting image {listViewItem.Name}"; |
||
1447 | imageListView.Items.Remove(listViewItem); |
||
1448 | }, |
||
1449 | new ExecutionDataflowBlockOptions |
||
1450 | { CancellationToken = cancellationToken, TaskScheduler = _formTaskScheduler }); |
||
1451 | |||
1452 | toolStripStatusLabel1.Text = "Deleting images..."; |
||
1453 | |||
1454 | #pragma warning disable CS4014 |
||
1455 | Task.Factory.StartNew(async () => |
||
1456 | #pragma warning restore CS4014 |
||
1457 | { |
||
1458 | try |
||
1459 | { |
||
1460 | await _imageListViewLock.WaitAsync(cancellationToken); |
||
1461 | } |
||
1462 | catch |
||
1463 | { |
||
1464 | return; |
||
1465 | } |
||
1466 | |||
1467 | try |
||
1468 | { |
||
8 | office | 1469 | using var _ = bufferBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
7 | office | 1470 | |
1471 | foreach (var item in enumerable) |
||
1472 | { |
||
8 | office | 1473 | if (cancellationToken.IsCancellationRequested) |
1474 | { |
||
1475 | return; |
||
1476 | } |
||
7 | office | 1477 | |
1478 | try |
||
1479 | { |
||
1480 | File.Delete(item.Name); |
||
1481 | |||
8 | office | 1482 | if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken)) |
1483 | { |
||
1484 | continue; |
||
1485 | } |
||
7 | office | 1486 | |
1487 | await bufferBlock.SendAsync(item, cancellationToken); |
||
1488 | } |
||
1489 | catch |
||
1490 | { |
||
1491 | // ignored |
||
1492 | } |
||
1493 | } |
||
1494 | |||
1495 | bufferBlock.Complete(); |
||
1496 | await actionBlock.Completion; |
||
1497 | } |
||
1498 | finally |
||
1499 | { |
||
1500 | _imageListViewLock.Release(); |
||
1501 | } |
||
1502 | }); |
||
1503 | } |
||
1504 | |||
1505 | private async Task BeginSearch(string text) |
||
1506 | { |
||
1507 | var keywords = new Csv(text).Select(tag => tag.Trim()).Where(tag => !string.IsNullOrEmpty(tag)).ToArray(); |
||
1508 | |||
1509 | try |
||
1510 | { |
||
1511 | await _imageListViewLock.WaitAsync(_cancellationToken); |
||
1512 | } |
||
1513 | catch |
||
1514 | { |
||
1515 | return; |
||
1516 | } |
||
1517 | |||
8 | office | 1518 | if (_selectionCancellationTokenSource != null) |
1519 | { |
||
1520 | _selectionCancellationTokenSource.Cancel(); |
||
1521 | } |
||
7 | office | 1522 | |
1523 | imageListView.InvokeIfRequired(view => { imageListView.BeginUpdate(); }); |
||
8 | office | 1524 | |
7 | office | 1525 | try |
1526 | { |
||
1527 | var taskCompletionSource = new TaskCompletionSource<object>(); |
||
1528 | imageListView.InvokeIfRequired(view => |
||
1529 | { |
||
8 | office | 1530 | foreach (var item in view.Items.OfType<ListViewItem>()) |
1531 | { |
||
1532 | _searchStore.TryAdd(item.Name, item); |
||
1533 | } |
||
7 | office | 1534 | |
1535 | view.Items.Clear(); |
||
8 | office | 1536 | |
7 | office | 1537 | taskCompletionSource.TrySetResult(new { }); |
1538 | }); |
||
1539 | |||
1540 | await taskCompletionSource.Task; |
||
1541 | |||
8 | office | 1542 | await foreach (var quickImage in _quickImageDatabase.Search(keywords, _quickImageSearchType, _quickImageSearchParameters, _combinedSearchSelectionCancellationToken).WithCancellation(_combinedSearchSelectionCancellationToken)) |
7 | office | 1543 | { |
8 | office | 1544 | if (!_searchStore.TryGetValue(quickImage.File, out var item)) |
1545 | { |
||
1546 | continue; |
||
1547 | } |
||
7 | office | 1548 | |
1549 | var directoryName = Path.GetDirectoryName(item.Name); |
||
8 | office | 1550 | if (_imageListViewGroupDictionary.TryGetValue(directoryName, out var group)) |
1551 | { |
||
1552 | item.Group = group; |
||
1553 | } |
||
7 | office | 1554 | |
1555 | imageListView.InvokeIfRequired(view => { view.Items.Add(item); }); |
||
1556 | } |
||
1557 | } |
||
1558 | catch (Exception exception) |
||
1559 | { |
||
1560 | Log.Error(exception, "Error while searching."); |
||
1561 | } |
||
1562 | finally |
||
1563 | { |
||
1564 | _imageListViewLock.Release(); |
||
1565 | imageListView.InvokeIfRequired(view => { imageListView.EndUpdate(); }); |
||
1566 | } |
||
1567 | } |
||
1568 | |||
1569 | private async Task EndSearch() |
||
1570 | { |
||
1571 | try |
||
1572 | { |
||
1573 | await _imageListViewLock.WaitAsync(_combinedSearchSelectionCancellationToken); |
||
1574 | } |
||
1575 | catch |
||
1576 | { |
||
1577 | return; |
||
1578 | } |
||
1579 | |||
8 | office | 1580 | if (_selectionCancellationTokenSource != null) |
1581 | { |
||
1582 | _selectionCancellationTokenSource.Cancel(); |
||
1583 | } |
||
7 | office | 1584 | |
1585 | imageListView.InvokeIfRequired(view => { imageListView.BeginUpdate(); }); |
||
1586 | try |
||
1587 | { |
||
1588 | toolStripStatusLabel1.Text = "Restoring items."; |
||
1589 | |||
1590 | var taskCompletionSource = new TaskCompletionSource<object>(); |
||
8 | office | 1591 | |
7 | office | 1592 | imageListView.InvokeIfRequired(view => |
1593 | { |
||
1594 | view.BeginUpdate(); |
||
1595 | view.Items.Clear(); |
||
1596 | var restore = new List<ListViewItem>(); |
||
1597 | foreach (var item in _searchStore) |
||
1598 | { |
||
1599 | var (name, listViewItem) = (item.Key, item.Value); |
||
1600 | var directoryName = Path.GetDirectoryName(name); |
||
1601 | if (!_imageListViewGroupDictionary.TryGetValue(directoryName, out var group)) |
||
1602 | { |
||
1603 | group = new ListViewGroup(directoryName, HorizontalAlignment.Left) { Name = directoryName }; |
||
1604 | _imageListViewGroupDictionary.TryAdd(directoryName, group); |
||
1605 | } |
||
1606 | |||
1607 | listViewItem.Group = group; |
||
1608 | restore.Add(listViewItem); |
||
1609 | } |
||
1610 | |||
1611 | view.Items.AddRange(restore.ToArray()); |
||
1612 | view.EndUpdate(); |
||
1613 | taskCompletionSource.TrySetResult(new { }); |
||
1614 | }); |
||
1615 | |||
1616 | await taskCompletionSource.Task; |
||
1617 | } |
||
1618 | catch (Exception exception) |
||
1619 | { |
||
1620 | Log.Error(exception, "Unable to add back items after search."); |
||
1621 | } |
||
1622 | finally |
||
1623 | { |
||
1624 | _imageListViewLock.Release(); |
||
1625 | imageListView.InvokeIfRequired(view => { imageListView.EndUpdate(); }); |
||
1626 | } |
||
1627 | } |
||
1628 | |||
1629 | private async Task OcrImages(IEnumerable<ListViewItem> items, |
||
1630 | IProgress<ImageListViewItemProgress<ListViewItem>> progress, |
||
1631 | CancellationToken cancellationToken) |
||
1632 | { |
||
8 | office | 1633 | using var engine = new TesseractEngine(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "tessdata"), "eng", EngineMode.Default); |
7 | office | 1634 | foreach (var item in items) |
1635 | { |
||
1636 | if (cancellationToken.IsCancellationRequested) return; |
||
1637 | |||
1638 | try |
||
1639 | { |
||
1640 | using var img = Pix.LoadFromFile(item.Name); |
||
1641 | using var page = engine.Process(img); |
||
1642 | var text = page.GetText(); |
||
1643 | var tags = new HashSet<string>(); |
||
1644 | if (string.IsNullOrEmpty(text)) |
||
1645 | { |
||
1646 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags)); |
||
1647 | continue; |
||
1648 | } |
||
1649 | |||
1650 | foreach (var word in Regex.Split(text, @"[^\w]+", RegexOptions.Compiled)) |
||
1651 | { |
||
1652 | if (string.IsNullOrEmpty(word)) continue; |
||
1653 | |||
1654 | tags.UnionWith(new[] { word }); |
||
1655 | } |
||
1656 | |||
1657 | if (!tags.Any()) |
||
1658 | { |
||
1659 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags)); |
||
1660 | continue; |
||
1661 | } |
||
1662 | |||
1663 | if (!await _quickImageDatabase.AddTagsAsync(item.Name, tags, cancellationToken)) |
||
1664 | { |
||
8 | office | 1665 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
7 | office | 1666 | continue; |
1667 | } |
||
1668 | |||
1669 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags)); |
||
1670 | } |
||
1671 | catch (Exception exception) |
||
1672 | { |
||
1673 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception)); |
||
1674 | } |
||
1675 | } |
||
1676 | } |
||
1677 | |||
1678 | private async Task ConvertImages(IEnumerable<ListViewItem> items, string extension, |
||
1679 | IProgress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>> progress, |
||
1680 | CancellationToken cancellationToken) |
||
1681 | { |
||
1682 | foreach (var item in items) |
||
1683 | { |
||
8 | office | 1684 | if (cancellationToken.IsCancellationRequested) |
1685 | { |
||
1686 | return; |
||
1687 | } |
||
7 | office | 1688 | |
1689 | try |
||
1690 | { |
||
1691 | var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken); |
||
1692 | |||
1693 | var path = Path.GetDirectoryName(item.Name); |
||
1694 | var name = Path.GetFileNameWithoutExtension(item.Name); |
||
1695 | var file = Path.Combine(path, $"{name}.{extension}"); |
||
1696 | |||
1697 | using var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write); |
||
1698 | switch (extension) |
||
1699 | { |
||
1700 | case "jpg": |
||
1701 | { |
||
1702 | using var convertStream = |
||
1703 | await _imageTool.ConvertTo(item.Name, MagickFormat.Jpeg, _cancellationToken); |
||
1704 | await convertStream.CopyToAsync(fileStream); |
||
1705 | break; |
||
1706 | } |
||
1707 | case "png": |
||
1708 | { |
||
1709 | using var convertStream = |
||
1710 | await _imageTool.ConvertTo(item.Name, MagickFormat.Png, _cancellationToken); |
||
1711 | await convertStream.CopyToAsync(fileStream); |
||
1712 | break; |
||
1713 | } |
||
1714 | case "bmp": |
||
1715 | { |
||
1716 | using var convertStream = |
||
1717 | await _imageTool.ConvertTo(item.Name, MagickFormat.Bmp, _cancellationToken); |
||
1718 | await convertStream.CopyToAsync(fileStream); |
||
1719 | break; |
||
1720 | } |
||
1721 | case "gif": |
||
1722 | { |
||
1723 | using var convertStream = |
||
1724 | await _imageTool.ConvertTo(item.Name, MagickFormat.Gif, _cancellationToken); |
||
1725 | await convertStream.CopyToAsync(fileStream); |
||
1726 | break; |
||
1727 | } |
||
1728 | } |
||
1729 | |||
1730 | if (!await _quickImageDatabase.RemoveImageAsync(image, cancellationToken)) |
||
1731 | { |
||
1732 | progress.Report( |
||
1733 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1734 | (Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1735 | continue; |
||
1736 | } |
||
1737 | |||
1738 | File.Delete(item.Name); |
||
1739 | |||
1740 | var newImage = new Database.QuickImage(file, image.Hash, image.Tags, image.Thumbnail); |
||
1741 | if (!await _quickImageDatabase.AddImageAsync(newImage, cancellationToken)) |
||
1742 | { |
||
1743 | progress.Report( |
||
1744 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1745 | (Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1746 | continue; |
||
1747 | } |
||
1748 | |||
1749 | progress.Report( |
||
1750 | new ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)>(( |
||
1751 | Item: item, Image: newImage))); |
||
1752 | } |
||
1753 | catch (Exception exception) |
||
1754 | { |
||
1755 | progress.Report( |
||
1756 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1757 | (Item: item, Image: null), exception)); |
||
1758 | } |
||
1759 | } |
||
1760 | } |
||
1761 | |||
1762 | private async Task RenameImage(ListViewItem item, string destinationFileName, |
||
1763 | IProgress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>> progress, |
||
1764 | CancellationToken cancellationToken) |
||
1765 | { |
||
1766 | try |
||
1767 | { |
||
1768 | await Miscellaneous.CopyFileAsync(item.Name, destinationFileName, cancellationToken); |
||
8 | office | 1769 | |
7 | office | 1770 | File.Delete(item.Name); |
1771 | |||
1772 | var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken); |
||
1773 | |||
1774 | if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken)) |
||
1775 | { |
||
1776 | progress.Report( |
||
1777 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1778 | (Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1779 | return; |
||
1780 | } |
||
1781 | |||
1782 | var destinationImage = |
||
1783 | new Database.QuickImage(destinationFileName, image.Hash, image.Tags, image.Thumbnail); |
||
1784 | |||
1785 | if (!await _quickImageDatabase.AddImageAsync(destinationImage, cancellationToken)) |
||
1786 | { |
||
1787 | progress.Report( |
||
1788 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1789 | (Item: item, Image: destinationImage), |
||
1790 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1791 | return; |
||
1792 | } |
||
1793 | |||
1794 | progress.Report( |
||
1795 | new ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)>( |
||
1796 | (Item: item, Image: destinationImage))); |
||
1797 | } |
||
1798 | catch (Exception exception) |
||
1799 | { |
||
1800 | progress.Report( |
||
1801 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1802 | (Item: item, Image: null), exception)); |
||
1803 | } |
||
1804 | } |
||
1805 | |||
1806 | private async Task MoveImages(IEnumerable<ListViewItem> items, string destinationDirectory, |
||
1807 | IProgress<ImageListViewItemProgress<(ListViewItem Item, Database.QuickImage Image)>> progress, |
||
1808 | CancellationToken cancellationToken) |
||
1809 | { |
||
1810 | foreach (var item in items) |
||
1811 | { |
||
8 | office | 1812 | if (cancellationToken.IsCancellationRequested) |
1813 | { |
||
1814 | return; |
||
1815 | } |
||
7 | office | 1816 | |
1817 | try |
||
1818 | { |
||
1819 | var fileName = Path.GetFileName(item.Name); |
||
1820 | var destinationFile = Path.Combine(destinationDirectory, fileName); |
||
1821 | await Miscellaneous.CopyFileAsync(item.Name, destinationFile, cancellationToken); |
||
1822 | File.Delete(item.Name); |
||
1823 | |||
1824 | var image = await _quickImageDatabase.GetImageAsync(item.Name, cancellationToken); |
||
1825 | |||
1826 | if (!await _quickImageDatabase.RemoveImageAsync(item.Name, cancellationToken)) |
||
1827 | { |
||
1828 | progress.Report( |
||
1829 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1830 | (Item: item, Image: null), new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1831 | continue; |
||
1832 | } |
||
1833 | |||
1834 | var destinationImage = |
||
1835 | new Database.QuickImage(destinationFile, image.Hash, image.Tags, image.Thumbnail); |
||
1836 | |||
1837 | if (!await _quickImageDatabase.AddImageAsync(destinationImage, cancellationToken)) |
||
1838 | { |
||
1839 | progress.Report( |
||
1840 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1841 | (Item: item, Image: destinationImage), |
||
1842 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1843 | continue; |
||
1844 | } |
||
1845 | |||
1846 | progress.Report( |
||
1847 | new ImageListViewItemProgressSuccess<(ListViewItem Item, Database.QuickImage Image)>( |
||
1848 | (Item: item, Image: destinationImage))); |
||
1849 | } |
||
1850 | catch (Exception exception) |
||
1851 | { |
||
1852 | progress.Report( |
||
1853 | new ImageListViewItemProgressFailure<(ListViewItem Item, Database.QuickImage Image)>( |
||
1854 | (Item: item, Image: null), exception)); |
||
1855 | } |
||
1856 | } |
||
1857 | } |
||
1858 | |||
1859 | private async Task GetTags(IReadOnlyList<ListViewItem> items, |
||
1860 | IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken) |
||
1861 | { |
||
1862 | foreach (var item in items) |
||
1863 | { |
||
8 | office | 1864 | if (cancellationToken.IsCancellationRequested) |
1865 | { |
||
1866 | return; |
||
1867 | } |
||
7 | office | 1868 | |
1869 | try |
||
1870 | { |
||
1871 | var tags = await _quickImageDatabase.GetTags(item.Name, cancellationToken) |
||
1872 | .ToArrayAsync(cancellationToken); |
||
1873 | |||
1874 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags)); |
||
1875 | } |
||
1876 | catch (Exception exception) |
||
1877 | { |
||
1878 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception)); |
||
1879 | } |
||
1880 | } |
||
1881 | } |
||
1882 | |||
1883 | private async Task BalanceImageTags(IReadOnlyList<ListViewItem> items, MagicMime magicMime, |
||
1884 | IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken) |
||
1885 | { |
||
1886 | foreach (var item in items) |
||
1887 | { |
||
1888 | if (cancellationToken.IsCancellationRequested) |
||
1889 | { |
||
1890 | return; |
||
1891 | } |
||
1892 | |||
1893 | try |
||
1894 | { |
||
1895 | var tags = await _quickImageDatabase.GetTags(item.Name, cancellationToken).ToArrayAsync(cancellationToken); |
||
1896 | |||
1897 | var mime = await magicMime.GetMimeType(item.Name, cancellationToken); |
||
1898 | |||
1899 | if (Configuration.SupportedFormats.IsSupportedImage(mime)) |
||
1900 | { |
||
1901 | if (!await _tagManager.AddIptcKeywords(item.Name, tags, cancellationToken)) |
||
1902 | { |
||
1903 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1904 | continue; |
||
1905 | } |
||
1906 | } |
||
1907 | |||
1908 | var merge = new HashSet<string>(tags); |
||
1909 | if (Configuration.SupportedFormats.Images.Image.Contains(mime)) |
||
1910 | await foreach (var iptcTag in _tagManager.GetIptcKeywords(item.Name, cancellationToken)) |
||
1911 | merge.UnionWith(new[] { iptcTag }); |
||
1912 | |||
1913 | if (!await _quickImageDatabase.AddTagsAsync(item.Name, merge, cancellationToken)) |
||
1914 | { |
||
1915 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, |
||
1916 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1917 | continue; |
||
1918 | } |
||
1919 | |||
1920 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, merge)); |
||
1921 | } |
||
1922 | catch (Exception exception) |
||
1923 | { |
||
1924 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception)); |
||
1925 | } |
||
1926 | } |
||
1927 | } |
||
1928 | |||
1929 | private async Task AddTags(IEnumerable<ListViewItem> items, IReadOnlyList<string> keywords, MagicMime magicMime, |
||
1930 | IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken) |
||
1931 | { |
||
1932 | foreach (var item in items) |
||
1933 | { |
||
1934 | if (cancellationToken.IsCancellationRequested) |
||
1935 | { |
||
1936 | return; |
||
1937 | } |
||
1938 | |||
1939 | try |
||
1940 | { |
||
1941 | var mime = await magicMime.GetMimeType(item.Name, cancellationToken); |
||
1942 | |||
1943 | if (Configuration.SupportedFormats.IsSupportedImage(mime)) |
||
1944 | { |
||
1945 | if (!await _tagManager.AddIptcKeywords(item.Name, keywords, cancellationToken)) |
||
1946 | { |
||
1947 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1948 | continue; |
||
1949 | } |
||
1950 | } |
||
1951 | |||
1952 | if (!await _quickImageDatabase.AddTagsAsync(item.Name, keywords, cancellationToken)) |
||
1953 | { |
||
1954 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, |
||
1955 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1956 | continue; |
||
1957 | } |
||
1958 | |||
1959 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, keywords, true)); |
||
1960 | } |
||
1961 | catch (Exception exception) |
||
1962 | { |
||
1963 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception)); |
||
1964 | } |
||
1965 | } |
||
1966 | } |
||
1967 | |||
1968 | private async Task StripTags(IEnumerable<ListViewItem> items, MagicMime magicMime, |
||
1969 | IProgress<ImageListViewItemProgress<ListViewItem>> progress, CancellationToken cancellationToken) |
||
1970 | { |
||
1971 | foreach (var item in items) |
||
1972 | { |
||
1973 | if (cancellationToken.IsCancellationRequested) |
||
1974 | { |
||
1975 | return; |
||
1976 | } |
||
1977 | |||
1978 | try |
||
1979 | { |
||
1980 | var mime = await magicMime.GetMimeType(item.Name, cancellationToken); |
||
1981 | |||
8 | office | 1982 | if (Configuration.SupportedFormats.IsSupportedImage(mime)) |
1983 | { |
||
7 | office | 1984 | if (!await _tagManager.StripIptcProfile(item.Name, cancellationToken)) |
1985 | { |
||
1986 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, |
||
1987 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1988 | continue; |
||
8 | office | 1989 | } |
1990 | } |
||
7 | office | 1991 | |
8 | office | 1992 | var tags = await _quickImageDatabase.GetTags(item.Name, cancellationToken).ToArrayAsync(cancellationToken); |
7 | office | 1993 | |
1994 | if (tags.Length != 0) |
||
1995 | if (!await _quickImageDatabase.StripTagsAsync(item.Name, cancellationToken)) |
||
1996 | { |
||
1997 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, |
||
1998 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
1999 | continue; |
||
2000 | } |
||
2001 | |||
2002 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, tags, false)); |
||
2003 | } |
||
2004 | catch (Exception exception) |
||
2005 | { |
||
2006 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception)); |
||
2007 | } |
||
2008 | } |
||
2009 | } |
||
2010 | |||
2011 | private async Task RemoveTags(IEnumerable<ListViewItem> items, IReadOnlyList<string> keywords, |
||
2012 | MagicMime magicMime, IProgress<ImageListViewItemProgress<ListViewItem>> progress, |
||
2013 | CancellationToken cancellationToken) |
||
2014 | { |
||
2015 | foreach (var item in items) |
||
2016 | { |
||
2017 | if (cancellationToken.IsCancellationRequested) |
||
2018 | { |
||
2019 | return; |
||
2020 | } |
||
2021 | |||
2022 | try |
||
2023 | { |
||
2024 | var mime = await magicMime.GetMimeType(item.Name, cancellationToken); |
||
2025 | |||
8 | office | 2026 | if (Configuration.SupportedFormats.IsSupportedImage(mime)) |
2027 | { |
||
7 | office | 2028 | if (!await _tagManager.RemoveIptcKeywords(item.Name, keywords, cancellationToken)) |
2029 | { |
||
2030 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, |
||
2031 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
2032 | continue; |
||
8 | office | 2033 | } |
2034 | } |
||
7 | office | 2035 | |
2036 | if (!await _quickImageDatabase.RemoveTagsAsync(item.Name, keywords, cancellationToken)) |
||
2037 | { |
||
2038 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, |
||
2039 | new ArgumentException(MethodBase.GetCurrentMethod()?.Name))); |
||
2040 | continue; |
||
2041 | } |
||
2042 | |||
2043 | progress.Report(new ImageListViewItemProgressSuccess<ListViewItem>(item, keywords, false)); |
||
2044 | } |
||
2045 | catch (Exception exception) |
||
2046 | { |
||
2047 | progress.Report(new ImageListViewItemProgressFailure<ListViewItem>(item, exception)); |
||
2048 | } |
||
2049 | } |
||
9 | office | 2050 | } |
2051 | |||
2052 | |||
2053 | private void collapseToolStripMenuItem_Click(object sender, EventArgs e) |
||
2054 | { |
||
2055 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
2056 | |||
2057 | var listViewItems = items as ListViewItem[] ?? items.ToArray(); |
||
2058 | |||
2059 | foreach(var item in listViewItems) |
||
2060 | { |
||
2061 | var group = item.Group; |
||
2062 | |||
2063 | if (!imageListView.GetCollapsed(group)) |
||
2064 | { |
||
2065 | imageListView.SetCollapsed(group, true); |
||
2066 | } |
||
2067 | } |
||
7 | office | 2068 | } |
2069 | |||
2070 | private void collapseAllToolStripMenuItem_Click(object sender, EventArgs e) |
||
2071 | { |
||
2072 | foreach (var group in _imageListViewGroupDictionary.Values) |
||
2073 | { |
||
2074 | if (!imageListView.GetCollapsed(group)) |
||
2075 | { |
||
2076 | imageListView.SetCollapsed(group, true); |
||
2077 | } |
||
2078 | } |
||
2079 | } |
||
2080 | |||
2081 | private void expandAllToolStripMenuItem_Click(object sender, EventArgs e) |
||
2082 | { |
||
2083 | foreach (var group in _imageListViewGroupDictionary.Values) |
||
2084 | { |
||
2085 | if (imageListView.GetCollapsed(group)) |
||
2086 | { |
||
2087 | imageListView.SetCollapsed(group, false); |
||
2088 | } |
||
2089 | } |
||
2090 | } |
||
2091 | |||
2092 | private void Form1_Closing(object sender, FormClosingEventArgs e) |
||
2093 | { |
||
2094 | } |
||
2095 | |||
2096 | private async void creationTimeAscendingSortMenuItem_Click(object sender, EventArgs e) |
||
2097 | { |
||
2098 | await SortImageListView( |
||
2099 | new DateImageListViewSorter(SortOrder.Ascending, DateImageListViewSorterType.Creation)); |
||
2100 | } |
||
2101 | |||
2102 | private async void creationTimeDescendingSortMenuItem_Click(object sender, EventArgs e) |
||
2103 | { |
||
2104 | await SortImageListView( |
||
2105 | new DateImageListViewSorter(SortOrder.Descending, DateImageListViewSorterType.Creation)); |
||
2106 | } |
||
2107 | |||
2108 | private async void accessTimeAscendingSortMenuItem_Click(object sender, EventArgs e) |
||
2109 | { |
||
2110 | await SortImageListView( |
||
2111 | new DateImageListViewSorter(SortOrder.Ascending, DateImageListViewSorterType.Access)); |
||
2112 | } |
||
2113 | |||
2114 | private async void accessTimeDescendingSortMenuItem_Click(object sender, EventArgs e) |
||
2115 | { |
||
2116 | await SortImageListView(new DateImageListViewSorter(SortOrder.Descending, |
||
2117 | DateImageListViewSorterType.Access)); |
||
2118 | } |
||
2119 | |||
2120 | private async void modificationTimeAscendingSortMenuItem_Click(object sender, EventArgs e) |
||
2121 | { |
||
2122 | await SortImageListView(new DateImageListViewSorter(SortOrder.Ascending, |
||
2123 | DateImageListViewSorterType.Modification)); |
||
2124 | } |
||
2125 | |||
2126 | private async void modificationTimeDescendingSortMenuItem_Click(object sender, EventArgs e) |
||
2127 | { |
||
2128 | await SortImageListView(new DateImageListViewSorter(SortOrder.Descending, |
||
2129 | DateImageListViewSorterType.Modification)); |
||
2130 | } |
||
2131 | |||
2132 | private void renameToolStripMenuItem_Click(object sender, EventArgs e) |
||
2133 | { |
||
8 | office | 2134 | if (_renameForm != null) |
2135 | { |
||
2136 | return; |
||
2137 | } |
||
7 | office | 2138 | |
2139 | var item = imageListView.SelectedItems.OfType<ListViewItem>().FirstOrDefault(); |
||
2140 | |||
8 | office | 2141 | if (item == null) |
2142 | { |
||
2143 | return; |
||
2144 | } |
||
7 | office | 2145 | |
2146 | _renameForm = new RenameForm(item); |
||
2147 | _renameForm.Rename += RenameForm_Rename; |
||
2148 | _renameForm.Closing += RenameForm_Closing; |
||
2149 | _renameForm.Show(); |
||
2150 | } |
||
2151 | |||
2152 | private async void RenameForm_Rename(object sender, RenameForm.RenameEventArgs e) |
||
2153 | { |
||
2154 | await RenameImageAsync(e.ListViewItem, e.FileName, _cancellationToken); |
||
2155 | } |
||
2156 | |||
2157 | private void RenameForm_Closing(object sender, CancelEventArgs e) |
||
2158 | { |
||
8 | office | 2159 | if (_renameForm == null) |
2160 | { |
||
2161 | return; |
||
2162 | } |
||
7 | office | 2163 | |
2164 | _renameForm.Closing -= RenameForm_Closing; |
||
2165 | _renameForm.Dispose(); |
||
2166 | _renameForm = null; |
||
2167 | } |
||
2168 | |||
2169 | private async void relocateToToolStropMenuItem_Click(object sender, EventArgs e) |
||
2170 | { |
||
2171 | var dialog = new CommonOpenFileDialog |
||
2172 | { |
||
2173 | AddToMostRecentlyUsedList = true, |
||
2174 | Multiselect = false, |
||
2175 | IsFolderPicker = true |
||
2176 | }; |
||
2177 | |||
2178 | var groupItems = new List<ListViewItem>(); |
||
2179 | |||
8 | office | 2180 | if (dialog.ShowDialog() == CommonFileDialogResult.Ok) |
2181 | { |
||
2182 | foreach (var item in imageListView.SelectedItems.OfType<ListViewItem>()) |
||
2183 | { |
||
2184 | foreach (var groupItem in item.Group.Items.OfType<ListViewItem>()) |
||
2185 | { |
||
2186 | groupItems.Add(groupItem); |
||
2187 | } |
||
2188 | } |
||
2189 | } |
||
7 | office | 2190 | |
8 | office | 2191 | RelocateTo(groupItems, dialog.FileName, _cancellationToken); |
7 | office | 2192 | } |
2193 | |||
2194 | private void imageListView_MouseDown(object sender, MouseEventArgs e) |
||
2195 | { |
||
2196 | } |
||
2197 | |||
2198 | private void checkBox2_CheckedChanged(object sender, EventArgs e) |
||
2199 | { |
||
2200 | var checkBox = (CheckBox)sender; |
||
2201 | switch (checkBox.Checked) |
||
2202 | { |
||
2203 | case true: |
||
2204 | _quickImageSearchParameters = |
||
2205 | _quickImageSearchParameters | QuickImageSearchParameters.Metadata; |
||
2206 | |||
2207 | break; |
||
2208 | case false: |
||
2209 | _quickImageSearchParameters = |
||
2210 | _quickImageSearchParameters & ~QuickImageSearchParameters.Metadata; |
||
2211 | break; |
||
2212 | } |
||
2213 | |||
2214 | var text = textBox1.Text; |
||
2215 | if (string.IsNullOrEmpty(text)) |
||
2216 | { |
||
2217 | return; |
||
2218 | } |
||
2219 | |||
2220 | if (_searchCancellationTokenSource != null) |
||
2221 | { |
||
2222 | _searchCancellationTokenSource.Cancel(); |
||
2223 | } |
||
2224 | |||
2225 | _searchCancellationTokenSource = new CancellationTokenSource(); |
||
2226 | _searchCancellationToken = _searchCancellationTokenSource.Token; |
||
2227 | _linkedSearchCancellationTokenSource = |
||
2228 | CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken); |
||
2229 | _combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token; |
||
2230 | |||
8 | office | 2231 | _searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken); |
7 | office | 2232 | } |
2233 | |||
2234 | private void checkBox3_CheckedChanged(object sender, EventArgs e) |
||
2235 | { |
||
2236 | var checkBox = (CheckBox)sender; |
||
2237 | switch (checkBox.Checked) |
||
2238 | { |
||
2239 | case true: |
||
2240 | _quickImageSearchParameters = |
||
2241 | _quickImageSearchParameters | QuickImageSearchParameters.Split; |
||
2242 | |||
2243 | break; |
||
2244 | case false: |
||
2245 | _quickImageSearchParameters = |
||
2246 | _quickImageSearchParameters & ~QuickImageSearchParameters.Split; |
||
2247 | break; |
||
2248 | } |
||
2249 | |||
2250 | var text = textBox1.Text; |
||
8 | office | 2251 | if (string.IsNullOrEmpty(text)) |
2252 | { |
||
2253 | return; |
||
2254 | } |
||
7 | office | 2255 | |
8 | office | 2256 | if (_searchCancellationTokenSource != null) |
2257 | { |
||
2258 | _searchCancellationTokenSource.Cancel(); |
||
2259 | } |
||
7 | office | 2260 | |
2261 | _searchCancellationTokenSource = new CancellationTokenSource(); |
||
2262 | _searchCancellationToken = _searchCancellationTokenSource.Token; |
||
2263 | _linkedSearchCancellationTokenSource = |
||
2264 | CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken); |
||
2265 | _combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token; |
||
2266 | |||
8 | office | 2267 | _searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken); |
7 | office | 2268 | } |
2269 | |||
2270 | private void checkBox2_VisibleChanged(object sender, EventArgs e) |
||
2271 | { |
||
2272 | var checkBox = (CheckBox)sender; |
||
2273 | if (checkBox.Checked) |
||
2274 | { |
||
2275 | _quickImageSearchParameters = _quickImageSearchParameters | QuickImageSearchParameters.Metadata; |
||
2276 | |||
2277 | return; |
||
2278 | } |
||
2279 | |||
2280 | _quickImageSearchParameters = _quickImageSearchParameters & ~QuickImageSearchParameters.Metadata; |
||
2281 | } |
||
2282 | |||
2283 | private void checkBox3_VisibleChanged(object sender, EventArgs e) |
||
2284 | { |
||
2285 | var checkBox = (CheckBox)sender; |
||
2286 | if (checkBox.Checked) |
||
2287 | { |
||
2288 | _quickImageSearchParameters = _quickImageSearchParameters | QuickImageSearchParameters.Split; |
||
2289 | |||
2290 | return; |
||
2291 | } |
||
2292 | |||
2293 | _quickImageSearchParameters = _quickImageSearchParameters & ~QuickImageSearchParameters.Split; |
||
2294 | } |
||
2295 | |||
2296 | private async void removeMissingToolStripMenuItem_Click(object sender, EventArgs e) |
||
2297 | { |
||
2298 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
2299 | |||
2300 | var listViewItems = items as ListViewItem[] ?? items.ToArray(); |
||
2301 | |||
2302 | await RemoveMissingAsync(listViewItems.Select(item => item.Group.Name), _cancellationToken); |
||
2303 | } |
||
2304 | |||
2305 | private async Task PerformUpgrade() |
||
2306 | { |
||
2307 | // Manually check for updates, this will not show a ui |
||
2308 | var updateCheck = await _sparkle.CheckForUpdatesQuietly(); |
||
8 | office | 2309 | |
7 | office | 2310 | switch (updateCheck.Status) |
2311 | { |
||
2312 | case UpdateStatus.UserSkipped: |
||
2313 | var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version; |
||
8 | office | 2314 | |
7 | office | 2315 | updateCheck.Updates.Sort(UpdateComparer); |
2316 | var latestVersion = updateCheck.Updates.FirstOrDefault(); |
||
2317 | if (latestVersion != null) |
||
2318 | { |
||
2319 | var availableVersion = new Version(latestVersion.Version); |
||
2320 | |||
2321 | if (availableVersion <= assemblyVersion) |
||
2322 | { |
||
2323 | return; |
||
2324 | } |
||
2325 | } |
||
2326 | |||
2327 | var decision = MessageBox.Show( |
||
2328 | "Update available but it has been previously skipped. Should the update proceed anyway?", |
||
2329 | Assembly.GetExecutingAssembly().GetName().Name, MessageBoxButtons.YesNo, |
||
2330 | MessageBoxIcon.Asterisk, |
||
2331 | MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false); |
||
2332 | |||
2333 | if (decision.HasFlag(DialogResult.No)) |
||
2334 | { |
||
2335 | return; |
||
2336 | } |
||
2337 | |||
2338 | goto default; |
||
2339 | case UpdateStatus.UpdateNotAvailable: |
||
2340 | MessageBox.Show("No updates available at this time.", |
||
2341 | Assembly.GetExecutingAssembly().GetName().Name, MessageBoxButtons.OK, |
||
2342 | MessageBoxIcon.Asterisk, |
||
2343 | MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly, false); |
||
2344 | break; |
||
2345 | case UpdateStatus.CouldNotDetermine: |
||
2346 | Log.Error("Could not determine the update availability status."); |
||
2347 | break; |
||
2348 | default: |
||
2349 | _sparkle.ShowUpdateNeededUI(); |
||
2350 | break; |
||
2351 | } |
||
2352 | } |
||
2353 | |||
2354 | private static int UpdateComparer(AppCastItem x, AppCastItem y) |
||
2355 | { |
||
2356 | if (x == null) return 1; |
||
2357 | |||
2358 | if (y == null) return -1; |
||
2359 | |||
2360 | if (x == y) return 0; |
||
2361 | |||
2362 | return new Version(y.Version).CompareTo(new Version(x.Version)); |
||
2363 | } |
||
2364 | |||
2365 | private async void oCRTextToTagsToolStripMenuItem_Click(object sender, EventArgs e) |
||
2366 | { |
||
2367 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
2368 | |||
2369 | var listViewItems = items as ListViewItem[] ?? items.ToArray(); |
||
2370 | |||
2371 | if (listViewItems.Length == 0) |
||
2372 | { |
||
2373 | return; |
||
2374 | } |
||
2375 | |||
2376 | await OcrImagesAsync(listViewItems, _cancellationToken); |
||
2377 | } |
||
2378 | |||
2379 | #region Static Methods |
||
2380 | |||
2381 | private static async IAsyncEnumerable<(string File, Stream Data, Definition Mime)> GetDragDropFiles( |
||
2382 | IDataObject data, MagicMime magicMime, [EnumeratorCancellation] CancellationToken cancellationToken) |
||
2383 | { |
||
2384 | var files = (string[])data.GetData(DataFormats.FileDrop); |
||
2385 | if (files != null) |
||
2386 | { |
||
2387 | foreach (var file in files) |
||
2388 | { |
||
2389 | var fileAttributes = File.GetAttributes(file); |
||
2390 | if (fileAttributes.HasFlag(FileAttributes.Directory)) |
||
2391 | { |
||
2392 | continue; |
||
2393 | } |
||
2394 | |||
8 | office | 2395 | using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); |
2396 | var memoryStream = new MemoryStream(); |
||
2397 | await fileStream.CopyToAsync(memoryStream); |
||
7 | office | 2398 | memoryStream.Position = 0L; |
2399 | |||
2400 | var mime = await magicMime.Identify(file, cancellationToken); |
||
2401 | if (mime == null) |
||
2402 | { |
||
2403 | continue; |
||
2404 | } |
||
2405 | |||
2406 | yield return (File: file, Data: memoryStream, Mime: mime.Definition); |
||
2407 | } |
||
2408 | |||
2409 | yield break; |
||
2410 | } |
||
2411 | |||
2412 | var fileNames = data.GetFileContentNames(); |
||
2413 | for (var i = 0; i < fileNames.Length; ++i) |
||
2414 | { |
||
2415 | var memoryStream = data.GetFileContent(i); |
||
2416 | memoryStream.Position = 0L; |
||
8 | office | 2417 | |
10 | office | 2418 | MagicMimeFile mime; |
2419 | try |
||
2420 | { |
||
2421 | mime = magicMime.Identify(fileNames[i], memoryStream, cancellationToken); |
||
2422 | |||
2423 | if (mime == null) |
||
2424 | { |
||
2425 | continue; |
||
2426 | } |
||
2427 | } |
||
2428 | catch(Exception exception) |
||
2429 | { |
||
2430 | Log.Error(exception, $"Could not determine mime type for file {fileNames[i]}."); |
||
2431 | |||
8 | office | 2432 | continue; |
2433 | } |
||
2434 | |||
7 | office | 2435 | yield return (File: fileNames[0], Data: memoryStream, Mime: mime.Definition); |
2436 | } |
||
2437 | } |
||
2438 | |||
2439 | private static async IAsyncEnumerable<string> GetFilesAsync(string entry, |
||
2440 | Configuration.Configuration configuration, MagicMime magicMime, |
||
2441 | [EnumeratorCancellation] CancellationToken cancellationToken) |
||
2442 | { |
||
8 | office | 2443 | var bufferBlock = new BufferBlock<string>(new DataflowBlockOptions { CancellationToken = cancellationToken }); |
7 | office | 2444 | |
2445 | #pragma warning disable CS4014 |
||
2446 | Task.Run(async () => |
||
2447 | #pragma warning restore CS4014 |
||
2448 | { |
||
2449 | try |
||
2450 | { |
||
2451 | var attributes = File.GetAttributes(entry); |
||
2452 | if (attributes.HasFlag(FileAttributes.Directory)) |
||
2453 | { |
||
2454 | var directoryFiles = Directory.GetFiles(entry); |
||
2455 | foreach (var directoryFile in directoryFiles) |
||
2456 | { |
||
2457 | if (!File.Exists(directoryFile)) |
||
2458 | { |
||
2459 | continue; |
||
2460 | } |
||
2461 | |||
10 | office | 2462 | var fileMimeType = await magicMime.GetMimeType(directoryFile, cancellationToken); |
7 | office | 2463 | |
2464 | if (!configuration.SupportedFormats.IsSupported(fileMimeType)) |
||
2465 | { |
||
2466 | continue; |
||
2467 | } |
||
2468 | |||
2469 | await bufferBlock.SendAsync(directoryFile, cancellationToken); |
||
2470 | } |
||
2471 | |||
2472 | return; |
||
2473 | } |
||
2474 | |||
10 | office | 2475 | var entryMimeType = await magicMime.GetMimeType(entry, cancellationToken); |
7 | office | 2476 | if (!configuration.SupportedFormats.IsSupported(entryMimeType)) |
2477 | { |
||
2478 | return; |
||
2479 | } |
||
2480 | |||
2481 | await bufferBlock.SendAsync(entry, cancellationToken); |
||
2482 | } |
||
2483 | finally |
||
2484 | { |
||
2485 | bufferBlock.Complete(); |
||
2486 | } |
||
2487 | }, cancellationToken); |
||
2488 | |||
2489 | while (await bufferBlock.OutputAvailableAsync(cancellationToken)) |
||
2490 | { |
||
8 | office | 2491 | if (!bufferBlock.TryReceiveAll(out var files)) |
2492 | { |
||
2493 | continue; |
||
2494 | } |
||
7 | office | 2495 | |
8 | office | 2496 | foreach (var file in files) |
2497 | { |
||
2498 | yield return file; |
||
2499 | } |
||
7 | office | 2500 | } |
2501 | } |
||
2502 | |||
2503 | public static async Task SaveConfiguration(Configuration.Configuration configuration) |
||
2504 | { |
||
2505 | if (!Directory.Exists(Constants.UserApplicationDirectory)) |
||
2506 | Directory.CreateDirectory(Constants.UserApplicationDirectory); |
||
2507 | |||
2508 | switch (await Serialization.Serialize(configuration, Constants.ConfigurationFile, "Configuration", |
||
2509 | "<!ATTLIST Configuration xmlns:xsi CDATA #IMPLIED xsi:noNamespaceSchemaLocation CDATA #IMPLIED>", |
||
2510 | CancellationToken.None)) |
||
2511 | { |
||
2512 | case SerializationSuccess<Configuration.Configuration> _: |
||
2513 | Log.Information("Configuration serialized successfully"); |
||
2514 | break; |
||
2515 | case SerializationFailure serializationFailure: |
||
2516 | Log.Warning(serializationFailure.Exception.Message, "Configuration failed to serialize"); |
||
2517 | break; |
||
2518 | } |
||
2519 | } |
||
2520 | |||
2521 | public static async Task<Configuration.Configuration> LoadConfiguration() |
||
2522 | { |
||
2523 | if (!Directory.Exists(Constants.UserApplicationDirectory)) |
||
2524 | Directory.CreateDirectory(Constants.UserApplicationDirectory); |
||
2525 | |||
2526 | var deserializationResult = |
||
2527 | await Serialization.Deserialize<Configuration.Configuration>(Constants.ConfigurationFile, |
||
2528 | Constants.ConfigurationNamespace, Constants.ConfigurationXsd, CancellationToken.None); |
||
2529 | |||
2530 | switch (deserializationResult) |
||
2531 | { |
||
2532 | case SerializationSuccess<Configuration.Configuration> serializationSuccess: |
||
2533 | return serializationSuccess.Result; |
||
2534 | case SerializationFailure serializationFailure: |
||
2535 | Log.Warning(serializationFailure.Exception, "Configuration failed to deserialize"); |
||
2536 | return new Configuration.Configuration(); |
||
2537 | default: |
||
2538 | return new Configuration.Configuration(); |
||
2539 | } |
||
2540 | } |
||
2541 | |||
2542 | private static async Task<Bitmap> CreateThumbnail(Stream file, uint width, uint height, |
||
2543 | CancellationToken cancellationToken) |
||
2544 | { |
||
2545 | using var memoryStream = new MemoryStream(); |
||
2546 | using var imageCollection = |
||
2547 | new MagickImageCollection(file, new MagickReadSettings { FrameIndex = 0, FrameCount = 1 }); |
||
2548 | var frame = imageCollection[0]; |
||
2549 | |||
2550 | var scaleHeight = width / (float)frame.Height; |
||
2551 | var scaleWidth = height / (float)frame.Width; |
||
2552 | var scale = Math.Min(scaleHeight, scaleWidth); |
||
2553 | |||
2554 | width = (uint)(frame.Width * scale); |
||
2555 | height = (uint)(frame.Height * scale); |
||
2556 | |||
2557 | var geometry = new MagickGeometry(width, height); |
||
2558 | frame.Resize(geometry); |
||
2559 | |||
2560 | await frame.WriteAsync(memoryStream, MagickFormat.Bmp, cancellationToken); |
||
2561 | |||
2562 | using var image = new MagickImage(MagickColors.Transparent, 128, 128); |
||
2563 | memoryStream.Position = 0L; |
||
2564 | using var composite = await new MagickFactory().Image.CreateAsync(memoryStream, cancellationToken); |
||
2565 | image.Composite(composite, Gravity.Center, CompositeOperator.Over); |
||
2566 | |||
2567 | using var outputStream = new MemoryStream(); |
||
2568 | await image.WriteAsync(outputStream, MagickFormat.Bmp, cancellationToken); |
||
2569 | var optimizer = new ImageOptimizer { IgnoreUnsupportedFormats = true }; |
||
2570 | outputStream.Position = 0L; |
||
2571 | optimizer.Compress(outputStream); |
||
2572 | |||
2573 | outputStream.Position = 0L; |
||
2574 | return (Bitmap)Image.FromStream(outputStream, true); |
||
2575 | } |
||
2576 | |||
2577 | private static int[] CreateHistogram(MemoryStream bitmapMemoryStream, CancellationToken cancellationToken, |
||
2578 | int threads = 2) |
||
2579 | { |
||
2580 | using var bitmap = (Bitmap)Image.FromStream(bitmapMemoryStream); |
||
2581 | |||
2582 | var histogram = new int[0xFFFFFFFF]; |
||
2583 | histogram.Initialize(); |
||
2584 | |||
2585 | var parallelOptions = new ParallelOptions |
||
2586 | { CancellationToken = cancellationToken, MaxDegreeOfParallelism = threads / 2 }; |
||
2587 | Parallel.For(0, bitmap.Width, parallelOptions, (x, state) => |
||
2588 | { |
||
2589 | Parallel.For(0, bitmap.Height, parallelOptions, (y, state) => |
||
2590 | { |
||
2591 | var value = bitmap.GetPixel(x, y).ToArgb(); |
||
2592 | |||
2593 | histogram[value]++; |
||
2594 | }); |
||
2595 | }); |
||
2596 | |||
2597 | return histogram; |
||
2598 | } |
||
2599 | |||
2600 | #endregion |
||
2601 | |||
2602 | #region Event Handlers |
||
2603 | |||
2604 | private void toolStripTextBox1_KeyUp(object sender, KeyEventArgs e) |
||
2605 | { |
||
8 | office | 2606 | if (e.KeyCode != Keys.Return) |
2607 | { |
||
2608 | return; |
||
2609 | } |
||
7 | office | 2610 | |
2611 | // Skip the beep. |
||
2612 | e.Handled = true; |
||
2613 | |||
2614 | var toolStripTextBox = (ToolStripTextBox)sender; |
||
2615 | var tagText = toolStripTextBox.Text; |
||
2616 | toolStripTextBox.Clear(); |
||
2617 | |||
8 | office | 2618 | if (string.IsNullOrEmpty(tagText)) |
2619 | { |
||
2620 | return; |
||
2621 | } |
||
7 | office | 2622 | |
2623 | var keywords = new[] { tagText }; |
||
2624 | |||
2625 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
2626 | |||
2627 | var count = items.Length; |
||
2628 | |||
2629 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
2630 | toolStripProgressBar1.Minimum = 0; |
||
2631 | toolStripProgressBar1.Maximum = count; |
||
2632 | toolStripProgressBar1.Value = 0; |
||
2633 | |||
2634 | void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e) |
||
2635 | { |
||
2636 | switch (e) |
||
2637 | { |
||
2638 | case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess: |
||
2639 | foreach (var tag in imageListViewItemProgressSuccess.Tags) |
||
2640 | { |
||
2641 | if (!_tagAutoCompleteStringCollection.Contains(tag)) |
||
2642 | _tagAutoCompleteStringCollection.Add(tag); |
||
2643 | |||
2644 | tagListView.BeginUpdate(); |
||
2645 | if (tagListView.Items.ContainsKey(tag)) |
||
2646 | { |
||
2647 | tagListView.Items[tag].Checked = true; |
||
2648 | tagListView.EndUpdate(); |
||
2649 | |||
2650 | _sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(1000), () => |
||
2651 | { |
||
2652 | tagListView.BeginUpdate(); |
||
2653 | tagListView.Sort(); |
||
2654 | tagListView.EndUpdate(); |
||
2655 | }, _formTaskScheduler, _cancellationToken); |
||
2656 | continue; |
||
2657 | } |
||
2658 | |||
2659 | tagListView.Items.Add(new ListViewItem(tag) { Name = tag }); |
||
2660 | tagListView.Items[tag].Checked = true; |
||
2661 | tagListView.EndUpdate(); |
||
2662 | |||
2663 | _sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(1000), () => |
||
2664 | { |
||
2665 | tagListView.BeginUpdate(); |
||
2666 | tagListView.Sort(); |
||
2667 | tagListView.EndUpdate(); |
||
2668 | }, _formTaskScheduler, _cancellationToken); |
||
2669 | } |
||
2670 | break; |
||
2671 | case ImageListViewItemProgressFailure<ListViewItem> imageListViewItemProgressFailure: |
||
2672 | break; |
||
2673 | } |
||
2674 | |||
2675 | toolStripStatusLabel1.Text = "Adding tags..."; |
||
2676 | toolStripProgressBar1.Increment(1); |
||
2677 | |||
8 | office | 2678 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
2679 | { |
||
2680 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
2681 | } |
||
7 | office | 2682 | } |
2683 | |||
2684 | toolStripStatusLabel1.Text = "Adding tags..."; |
||
2685 | #pragma warning disable CS4014 |
||
2686 | Task.Factory.StartNew(async () => |
||
2687 | #pragma warning restore CS4014 |
||
2688 | { |
||
2689 | _listViewItemProgress.ProgressChanged += ImageListViewItemProgress; |
||
2690 | try |
||
2691 | { |
||
2692 | await AddTags(items, keywords, _magicMime, _listViewItemProgress, _cancellationToken); |
||
2693 | } |
||
2694 | catch |
||
2695 | { |
||
2696 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
2697 | } |
||
2698 | }, _cancellationToken); |
||
2699 | } |
||
2700 | |||
2701 | private void contextMenuStrip1_Opened(object sender, EventArgs e) |
||
2702 | { |
||
2703 | tagTextBox.Focus(); |
||
2704 | |||
2705 | } |
||
2706 | |||
2707 | private async void Form1_Load(object sender, EventArgs e) |
||
2708 | { |
||
2709 | JotFormTracker.Tracker.Track(this); |
||
2710 | |||
2711 | JotFormTracker.Tracker.Configure<Form1>() |
||
2712 | .Properties(form => new { form.tabControl1.SelectedIndex }); |
||
2713 | |||
2714 | JotFormTracker.Tracker.Configure<Form1>() |
||
2715 | .Properties(form => new { form.splitContainer3.SplitterDistance }); |
||
2716 | |||
2717 | JotFormTracker.Tracker.Configure<Form1>() |
||
2718 | .Properties(form => new { form.radioButton1.Checked }); |
||
2719 | |||
2720 | JotFormTracker.Tracker.Configure<Form1>() |
||
2721 | .Properties(form => new { form.checkBox1.Checked, form.checkBox1.CheckState }); |
||
2722 | |||
2723 | JotFormTracker.Tracker.Configure<Form1>() |
||
2724 | .Properties(form => new { form.checkBox2.Checked, form.checkBox2.CheckState }); |
||
2725 | |||
2726 | JotFormTracker.Tracker.Configure<Form1>() |
||
11 | office | 2727 | .Properties(form => new { form.checkBox3.Checked, form.checkBox3.CheckState }); |
2728 | |||
2729 | |||
2730 | _tagListViewSorter = new TagListViewSorter(); |
||
2731 | tagListView.ListViewItemSorter = _tagListViewSorter; |
||
2732 | |||
2733 | _tagAutoCompleteStringCollection = new AutoCompleteStringCollection(); |
||
2734 | textBox1.AutoCompleteCustomSource = _tagAutoCompleteStringCollection; |
||
2735 | tagTextBox.AutoCompleteCustomSource = _tagAutoCompleteStringCollection; |
||
7 | office | 2736 | |
20 | office | 2737 | moveToolStripMenuItem.DropDown.ItemClicked += moveTargetToolStripMenuItem_Click; |
2738 | |||
7 | office | 2739 | Configuration = await LoadConfiguration(); |
2740 | |||
2741 | #pragma warning disable CS4014 |
||
2742 | PerformUpgrade(); |
||
2743 | #pragma warning restore CS4014 |
||
2744 | |||
2745 | #pragma warning disable CS4014 |
||
2746 | Task.Factory.StartNew(async () => |
||
2747 | #pragma warning restore CS4014 |
||
2748 | { |
||
2749 | try |
||
2750 | { |
||
2751 | await _imageListViewLock.WaitAsync(_cancellationToken); |
||
2752 | } |
||
2753 | catch |
||
2754 | { |
||
2755 | return; |
||
2756 | } |
||
2757 | |||
2758 | this.InvokeIfRequired(form => |
||
2759 | { |
||
2760 | form.toolStripStatusLabel1.Text = "Loading images..."; |
||
2761 | form.toolStripProgressBar1.Style = ProgressBarStyle.Marquee; |
||
2762 | form.toolStripProgressBar1.MarqueeAnimationSpeed = 30; |
||
2763 | }); |
||
2764 | |||
2765 | try |
||
2766 | { |
||
8 | office | 2767 | var imageListViewItems = new ConcurrentBag<ListViewItem>(); |
7 | office | 2768 | var tags = new HashSet<string>(StringComparer.Ordinal); |
8 | office | 2769 | await foreach (var image in _quickImageDatabase.GetAll(_cancellationToken)) |
7 | office | 2770 | { |
8 | office | 2771 | if (!largeImageList.Images.ContainsKey(image.File)) |
2772 | { |
||
2773 | largeImageList.Images.Add(image.File, image.Thumbnail); |
||
2774 | } |
||
7 | office | 2775 | |
2776 | var fileInfo = new FileInfo(image.File); |
||
2777 | |||
2778 | if (!_imageListViewGroupDictionary.TryGetValue(fileInfo.DirectoryName, out var group)) |
||
2779 | { |
||
8 | office | 2780 | group = new ListViewGroup(fileInfo.DirectoryName, HorizontalAlignment.Left) { Name = fileInfo.DirectoryName }; |
7 | office | 2781 | |
8 | office | 2782 | _imageListViewGroupDictionary.TryAdd(fileInfo.DirectoryName, group); |
2783 | |||
7 | office | 2784 | imageListView.InvokeIfRequired(view => |
2785 | { |
||
8 | office | 2786 | view.BeginUpdate(); |
7 | office | 2787 | view.Groups.Add(group); |
8 | office | 2788 | view.EndUpdate(); |
7 | office | 2789 | }); |
2790 | } |
||
2791 | |||
2792 | tags.UnionWith(image.Tags); |
||
2793 | |||
8 | office | 2794 | var imageListViewItem = new ListViewItem(image.File) |
7 | office | 2795 | { |
2796 | Name = image.File, |
||
2797 | ImageKey = image.File, |
||
2798 | Text = fileInfo.Name, |
||
2799 | Group = group |
||
8 | office | 2800 | }; |
2801 | |||
2802 | imageListViewItems.Add(imageListViewItem); |
||
7 | office | 2803 | |
8 | office | 2804 | } |
2805 | |||
7 | office | 2806 | this.InvokeIfRequired(_ => { _tagAutoCompleteStringCollection.AddRange(tags.ToArray()); }); |
2807 | |||
2808 | imageListView.InvokeIfRequired(view => |
||
2809 | { |
||
2810 | view.BeginUpdate(); |
||
2811 | view.Items.AddRange(imageListViewItems.ToArray()); |
||
2812 | view.EndUpdate(); |
||
2813 | }); |
||
2814 | |||
2815 | tagListView.InvokeIfRequired(view => |
||
2816 | { |
||
2817 | view.BeginUpdate(); |
||
2818 | view.Items.AddRange(tags.Select(tag => new ListViewItem(tag) { Name = tag }).ToArray()); |
||
2819 | view.EndUpdate(); |
||
8 | office | 2820 | }); |
7 | office | 2821 | } |
2822 | catch (Exception exception) |
||
2823 | { |
||
2824 | Log.Error(exception, "Unable to load images."); |
||
2825 | } |
||
2826 | finally |
||
2827 | { |
||
2828 | this.InvokeIfRequired(form => |
||
2829 | { |
||
2830 | form.toolStripStatusLabel1.Text = "Done loading images."; |
||
2831 | form.toolStripProgressBar1.MarqueeAnimationSpeed = 0; |
||
2832 | }); |
||
2833 | |||
2834 | _imageListViewLock.Release(); |
||
2835 | } |
||
2836 | }, _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); |
||
2837 | } |
||
2838 | |||
2839 | private async void tagListView_MouseDown(object sender, MouseEventArgs e) |
||
2840 | { |
||
2841 | var listView = (ListView)sender; |
||
8 | office | 2842 | if (!listView.CheckBoxes) |
2843 | { |
||
2844 | return; |
||
2845 | } |
||
7 | office | 2846 | |
2847 | // Allow clicking anywhere on tag. |
||
2848 | var hitTest = listView.HitTest(e.Location); |
||
8 | office | 2849 | if (hitTest.Item == null) |
2850 | { |
||
2851 | return; |
||
2852 | } |
||
7 | office | 2853 | |
2854 | var item = hitTest.Item; |
||
2855 | |||
2856 | var tagText = item.Text; |
||
2857 | |||
2858 | var keywords = new[] { tagText }; |
||
2859 | |||
2860 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
2861 | |||
2862 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
2863 | toolStripProgressBar1.Minimum = 0; |
||
2864 | toolStripProgressBar1.Maximum = items.Length; |
||
2865 | toolStripProgressBar1.Value = 0; |
||
2866 | |||
2867 | void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e) |
||
2868 | { |
||
2869 | switch (e) |
||
2870 | { |
||
2871 | case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess: |
||
2872 | foreach (var tag in imageListViewItemProgressSuccess.Tags) |
||
2873 | { |
||
2874 | tagListView.BeginUpdate(); |
||
2875 | if (tagListView.Items.ContainsKey(tag)) |
||
2876 | { |
||
2877 | tagListView.Items[tag].Checked = imageListViewItemProgressSuccess.Check; |
||
2878 | tagListView.EndUpdate(); |
||
2879 | continue; |
||
2880 | } |
||
2881 | |||
2882 | tagListView.Items.Add(new ListViewItem(tag) { Name = tag }); |
||
2883 | tagListView.Items[tag].Checked = imageListViewItemProgressSuccess.Check; |
||
2884 | tagListView.EndUpdate(); |
||
2885 | } |
||
2886 | break; |
||
2887 | case ImageListViewItemProgressFailure<ListViewItem> imageListViewItemProgressFailure: |
||
2888 | break; |
||
2889 | } |
||
2890 | |||
2891 | toolStripProgressBar1.Increment(1); |
||
8 | office | 2892 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
2893 | { |
||
2894 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
2895 | } |
||
7 | office | 2896 | } |
2897 | |||
2898 | if (item.Checked) |
||
2899 | { |
||
2900 | |||
2901 | toolStripStatusLabel1.Text = "Removing tags..."; |
||
8 | office | 2902 | |
7 | office | 2903 | _listViewItemProgress.ProgressChanged += ImageListViewItemProgress; |
2904 | try |
||
2905 | { |
||
2906 | await RemoveTags(items, keywords, _magicMime, _listViewItemProgress, _cancellationToken); |
||
2907 | } |
||
2908 | finally |
||
2909 | { |
||
2910 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
2911 | } |
||
2912 | |||
8 | office | 2913 | switch(hitTest.Location) |
2914 | { |
||
2915 | case ListViewHitTestLocations.Label: |
||
2916 | hitTest.Item.Checked = !hitTest.Item.Checked; |
||
2917 | break; |
||
2918 | } |
||
7 | office | 2919 | |
2920 | return; |
||
2921 | } |
||
2922 | |||
2923 | toolStripStatusLabel1.Text = "Adding tags..."; |
||
2924 | _listViewItemProgress.ProgressChanged += ImageListViewItemProgress; |
||
2925 | try |
||
2926 | { |
||
2927 | await AddTags(items, keywords, _magicMime, _listViewItemProgress, _cancellationToken); |
||
2928 | } |
||
2929 | finally |
||
2930 | { |
||
2931 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
2932 | } |
||
2933 | |||
8 | office | 2934 | switch(hitTest.Location) |
2935 | { |
||
2936 | case ListViewHitTestLocations.Label: |
||
2937 | hitTest.Item.Checked = !hitTest.Item.Checked; |
||
2938 | break; |
||
2939 | } |
||
7 | office | 2940 | } |
2941 | |||
2942 | private void aboutToolStripMenuItem_Click(object sender, EventArgs e) |
||
2943 | { |
||
8 | office | 2944 | if (_aboutForm != null) |
2945 | { |
||
2946 | return; |
||
2947 | } |
||
7 | office | 2948 | |
2949 | _aboutForm = new AboutForm(); |
||
2950 | _aboutForm.Closing += AboutForm_Closing; |
||
2951 | _aboutForm.Show(); |
||
2952 | } |
||
2953 | |||
2954 | private void AboutForm_Closing(object sender, CancelEventArgs e) |
||
2955 | { |
||
8 | office | 2956 | if (_aboutForm == null) |
2957 | { |
||
2958 | return; |
||
2959 | } |
||
7 | office | 2960 | |
2961 | _aboutForm.Closing -= AboutForm_Closing; |
||
2962 | _aboutForm.Dispose(); |
||
2963 | _aboutForm = null; |
||
2964 | } |
||
2965 | |||
2966 | private async void updateToolStripMenuItem_Click(object sender, EventArgs e) |
||
2967 | { |
||
2968 | await PerformUpgrade(); |
||
2969 | } |
||
2970 | |||
2971 | private void viewLogsToolStripMenuItem_Click(object sender, EventArgs e) |
||
2972 | { |
||
8 | office | 2973 | if (_viewLogsForm != null) |
2974 | { |
||
2975 | return; |
||
2976 | } |
||
7 | office | 2977 | |
2978 | _viewLogsForm = new ViewLogsForm(this, _memorySink, _cancellationToken); |
||
2979 | _viewLogsForm.Closing += ViewLogsForm_Closing; |
||
2980 | _viewLogsForm.Show(); |
||
2981 | } |
||
2982 | |||
2983 | private void ViewLogsForm_Closing(object sender, CancelEventArgs e) |
||
2984 | { |
||
8 | office | 2985 | if (_viewLogsForm == null) |
2986 | { |
||
2987 | return; |
||
2988 | } |
||
7 | office | 2989 | |
2990 | _viewLogsForm.Closing -= ViewLogsForm_Closing; |
||
2991 | _viewLogsForm.Close(); |
||
2992 | _viewLogsForm = null; |
||
2993 | } |
||
2994 | |||
2995 | private void quitToolStripMenuItem_Click(object sender, EventArgs e) |
||
2996 | { |
||
2997 | _cancellationTokenSource.Cancel(); |
||
2998 | Close(); |
||
2999 | } |
||
3000 | |||
3001 | private async void removeToolStripMenuItem_Click(object sender, EventArgs e) |
||
3002 | { |
||
3003 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
3004 | |||
8 | office | 3005 | RemoveImages(items, _cancellationToken); |
7 | office | 3006 | } |
3007 | |||
3008 | private void textBox1_TextChanged(object sender, EventArgs e) |
||
3009 | { |
||
3010 | var textBox = (TextBox)sender; |
||
3011 | var text = textBox.Text; |
||
3012 | |||
3013 | if (_searchCancellationTokenSource != null) _searchCancellationTokenSource.Cancel(); |
||
3014 | |||
3015 | _searchCancellationTokenSource = new CancellationTokenSource(); |
||
3016 | _searchCancellationToken = _searchCancellationTokenSource.Token; |
||
3017 | _linkedSearchCancellationTokenSource = |
||
3018 | CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken); |
||
3019 | _combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token; |
||
3020 | |||
3021 | if (string.IsNullOrEmpty(text)) |
||
3022 | { |
||
3023 | _searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), |
||
3024 | async () => { await EndSearch(); }, _combinedSearchSelectionCancellationToken); |
||
3025 | |||
3026 | return; |
||
3027 | } |
||
3028 | |||
3029 | _searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, |
||
3030 | async text => { await BeginSearch(text); }, _combinedSearchSelectionCancellationToken); |
||
3031 | } |
||
3032 | |||
3033 | private void imageListView_KeyDown(object sender, KeyEventArgs e) |
||
3034 | { |
||
3035 | if (e.Control && e.KeyCode == Keys.A) |
||
3036 | { |
||
3037 | foreach (var item in imageListView.Items.OfType<ListViewItem>()) |
||
3038 | { |
||
3039 | item.Selected = true; |
||
3040 | } |
||
3041 | } |
||
3042 | } |
||
3043 | |||
3044 | private void imageListView_DragLeave(object sender, EventArgs e) |
||
3045 | { |
||
3046 | } |
||
3047 | |||
3048 | private void deleteToolStripMenuItem_Click(object sender, EventArgs e) |
||
3049 | { |
||
3050 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
3051 | |||
3052 | DeleteImages(items, _cancellationToken); |
||
3053 | } |
||
3054 | |||
3055 | private void selectAllToolStripMenuItem1_Click(object sender, EventArgs e) |
||
3056 | { |
||
3057 | foreach (var item in imageListView.SelectedItems.OfType<ListViewItem>()) |
||
3058 | { |
||
3059 | foreach (var groupItem in item.Group.Items.OfType<ListViewItem>()) |
||
3060 | { |
||
3061 | groupItem.Selected = true; |
||
3062 | } |
||
3063 | } |
||
3064 | } |
||
3065 | |||
3066 | private void selectAllToolStripMenuItem_Click(object sender, EventArgs e) |
||
3067 | { |
||
3068 | foreach (var item in imageListView.Items.OfType<ListViewItem>()) |
||
3069 | { |
||
3070 | item.Selected = true; |
||
3071 | } |
||
3072 | } |
||
3073 | |||
3074 | private void textBox1_KeyDown(object sender, KeyEventArgs e) |
||
3075 | { |
||
3076 | if (e.Control && e.KeyCode == Keys.A) |
||
3077 | { |
||
3078 | var textBox = (TextBox)sender; |
||
8 | office | 3079 | |
7 | office | 3080 | textBox.SelectAll(); |
3081 | } |
||
3082 | } |
||
3083 | |||
3084 | private void radioButton2_CheckedChanged(object sender, EventArgs e) |
||
3085 | { |
||
3086 | _quickImageSearchType = QuickImageSearchType.Any; |
||
3087 | |||
3088 | var text = textBox1.Text; |
||
8 | office | 3089 | if (string.IsNullOrEmpty(text)) |
3090 | { |
||
3091 | return; |
||
3092 | } |
||
7 | office | 3093 | |
8 | office | 3094 | if (_searchCancellationTokenSource != null) |
3095 | { |
||
3096 | _searchCancellationTokenSource.Cancel(); |
||
3097 | } |
||
7 | office | 3098 | |
3099 | _searchCancellationTokenSource = new CancellationTokenSource(); |
||
3100 | _searchCancellationToken = _searchCancellationTokenSource.Token; |
||
3101 | _linkedSearchCancellationTokenSource = |
||
3102 | CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken); |
||
3103 | _combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token; |
||
3104 | |||
8 | office | 3105 | _searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken); |
7 | office | 3106 | } |
3107 | |||
3108 | private void radiobutton1_CheckedChanged(object sender, EventArgs e) |
||
3109 | { |
||
3110 | _quickImageSearchType = QuickImageSearchType.All; |
||
3111 | |||
3112 | var text = textBox1.Text; |
||
8 | office | 3113 | if (string.IsNullOrEmpty(text)) |
3114 | { |
||
3115 | return; |
||
3116 | } |
||
7 | office | 3117 | |
3118 | if (_searchCancellationTokenSource != null) _searchCancellationTokenSource.Cancel(); |
||
3119 | |||
3120 | _searchCancellationTokenSource = new CancellationTokenSource(); |
||
3121 | _searchCancellationToken = _searchCancellationTokenSource.Token; |
||
3122 | _linkedSearchCancellationTokenSource = |
||
3123 | CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken); |
||
3124 | _combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token; |
||
3125 | |||
8 | office | 3126 | _searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken); |
7 | office | 3127 | } |
3128 | |||
3129 | private async void editToolStripMenuItem1_Click(object sender, EventArgs e) |
||
3130 | { |
||
3131 | var item = imageListView.SelectedItems.OfType<ListViewItem>().FirstOrDefault(); |
||
8 | office | 3132 | if (item == null) |
7 | office | 3133 | { |
8 | office | 3134 | return; |
7 | office | 3135 | } |
8 | office | 3136 | |
3137 | if (_editorForm != null) |
||
7 | office | 3138 | { |
8 | office | 3139 | return; |
7 | office | 3140 | } |
3141 | |||
10 | office | 3142 | try |
3143 | { |
||
3144 | var mime = await _magicMime.GetMimeType(item.Name, _cancellationToken); |
||
3145 | switch (mime) |
||
3146 | { |
||
3147 | case "IMAGE/JPEG": |
||
3148 | case "IMAGE/PNG": |
||
3149 | case "IMAGE/BMP": |
||
3150 | break; |
||
3151 | default: |
||
3152 | this.InvokeIfRequired(form => |
||
3153 | { |
||
3154 | form.toolStripStatusLabel1.Text = $"Image format not supported for file {item.Name}"; |
||
3155 | }); |
||
3156 | return; |
||
3157 | } |
||
7 | office | 3158 | } |
10 | office | 3159 | catch(Exception exception) |
3160 | { |
||
3161 | Log.Error(exception, $"Could not deterine image format for file {item.Name}"); |
||
3162 | return; |
||
3163 | } |
||
7 | office | 3164 | |
3165 | _editorForm = new EditorForm(item.Name, Configuration, _magicMime, _cancellationToken); |
||
3166 | _editorForm.ImageSave += _editorForm_ImageSave; |
||
3167 | _editorForm.ImageSaveAs += _editorForm_ImageSaveAs; |
||
3168 | _editorForm.Closing += _editorForm_Closing; |
||
3169 | _editorForm.Show(); |
||
3170 | } |
||
3171 | |||
3172 | private async void _editorForm_ImageSaveAs(object sender, EditorForm.ImageChangedEventArgs e) |
||
3173 | { |
||
3174 | using var imageMemoryStream = new MemoryStream(e.ImageBytes); |
||
3175 | using (var fileStream = new FileStream(e.FileName, FileMode.OpenOrCreate, FileAccess.Write)) |
||
3176 | { |
||
3177 | await imageMemoryStream.CopyToAsync(fileStream); |
||
3178 | } |
||
3179 | |||
3180 | await LoadFilesAsync(new[] { e.FileName }, _magicMime, _cancellationToken); |
||
3181 | } |
||
3182 | |||
3183 | private async void _editorForm_ImageSave(object sender, EditorForm.ImageChangedEventArgs e) |
||
3184 | { |
||
3185 | using var bitmapMemoryStream = new MemoryStream(); |
||
3186 | using var imageMemoryStream = new MemoryStream(e.ImageBytes); |
||
3187 | using (var fileStream = new FileStream(e.FileName, FileMode.OpenOrCreate, FileAccess.Write)) |
||
3188 | { |
||
3189 | await imageMemoryStream.CopyToAsync(fileStream); |
||
3190 | } |
||
3191 | |||
3192 | imageMemoryStream.Position = 0L; |
||
3193 | using var image = Image.FromStream(imageMemoryStream); |
||
3194 | image.Save(bitmapMemoryStream, ImageFormat.Bmp); |
||
3195 | |||
3196 | bitmapMemoryStream.Position = 0L; |
||
3197 | using var hashBitmap = Image.FromStream(bitmapMemoryStream); |
||
3198 | var hash = ImagePhash.ComputeDigest(hashBitmap.ToBitmap().ToLuminanceImage()); |
||
3199 | |||
3200 | bitmapMemoryStream.Position = 0L; |
||
3201 | using var thumbnailBitmap = await CreateThumbnail(bitmapMemoryStream, 128, 128, _cancellationToken); |
||
3202 | var thumbnail = new Bitmap(thumbnailBitmap); |
||
3203 | thumbnailBitmap.Dispose(); |
||
3204 | |||
3205 | try |
||
3206 | { |
||
8 | office | 3207 | if (!await _quickImageDatabase.RemoveImageAsync(e.FileName, _cancellationToken)) |
3208 | { |
||
3209 | throw new ArgumentException($"Could not remove image {e.FileName} from database."); |
||
3210 | } |
||
7 | office | 3211 | |
8 | office | 3212 | var keywords = await _quickImageDatabase.GetTags(e.FileName, _cancellationToken).ToArrayAsync(_cancellationToken); |
7 | office | 3213 | |
8 | office | 3214 | if (!await _quickImageDatabase.AddImageAsync(e.FileName, hash, keywords, thumbnail, _cancellationToken)) |
3215 | { |
||
3216 | throw new ArgumentException($"Could not add image {e.FileName} to database."); |
||
3217 | } |
||
7 | office | 3218 | |
3219 | this.InvokeIfRequired(form => |
||
3220 | { |
||
3221 | form.largeImageList.Images.RemoveByKey(e.FileName); |
||
3222 | form.largeImageList.Images.Add(e.FileName, thumbnail); |
||
3223 | }); |
||
3224 | } |
||
3225 | catch (Exception exception) |
||
3226 | { |
||
3227 | Log.Error(exception, "Could not update image in database"); |
||
3228 | } |
||
3229 | } |
||
3230 | |||
3231 | private void _editorForm_Closing(object sender, CancelEventArgs e) |
||
3232 | { |
||
8 | office | 3233 | if (_editorForm == null) |
3234 | { |
||
3235 | return; |
||
3236 | } |
||
7 | office | 3237 | |
3238 | _editorForm.ImageSave -= _editorForm_ImageSave; |
||
3239 | _editorForm.ImageSaveAs -= _editorForm_ImageSaveAs; |
||
3240 | _editorForm.Closing -= _editorForm_Closing; |
||
3241 | _editorForm.Dispose(); |
||
3242 | _editorForm = null; |
||
3243 | } |
||
3244 | |||
3245 | private async void synchronizeTagsToolStripMenuItem_Click(object sender, EventArgs e) |
||
3246 | { |
||
3247 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
3248 | |||
3249 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
3250 | toolStripProgressBar1.Minimum = 0; |
||
3251 | toolStripProgressBar1.Maximum = items.Length; |
||
3252 | toolStripProgressBar1.Value = 0; |
||
3253 | |||
3254 | void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e) |
||
3255 | { |
||
3256 | switch (e) |
||
3257 | { |
||
3258 | case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess: |
||
8 | office | 3259 | if (!(imageListViewItemProgressSuccess.Item is { } listViewItem)) |
3260 | { |
||
3261 | break; |
||
3262 | } |
||
7 | office | 3263 | |
3264 | toolStripStatusLabel1.Text = $"Synchronizing tags for {listViewItem.Name}"; |
||
3265 | |||
3266 | break; |
||
3267 | case ImageListViewItemProgressFailure<ListViewItem> imageListViewItemProgressFailure: |
||
3268 | break; |
||
3269 | } |
||
3270 | |||
3271 | toolStripProgressBar1.Increment(1); |
||
3272 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
||
3273 | { |
||
3274 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
3275 | _imageListViewLock.Release(); |
||
3276 | } |
||
3277 | } |
||
3278 | |||
3279 | toolStripStatusLabel1.Text = "Synchronizing image tags with database tags..."; |
||
3280 | try |
||
3281 | { |
||
3282 | await _imageListViewLock.WaitAsync(_cancellationToken); |
||
3283 | } |
||
3284 | catch |
||
3285 | { |
||
3286 | return; |
||
3287 | } |
||
3288 | |||
3289 | _listViewItemProgress.ProgressChanged += ImageListViewItemProgress; |
||
3290 | try |
||
3291 | { |
||
3292 | await BalanceImageTags(items, _magicMime, _listViewItemProgress, _cancellationToken); |
||
3293 | } |
||
3294 | catch |
||
3295 | { |
||
3296 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
3297 | _imageListViewLock.Release(); |
||
3298 | } |
||
3299 | } |
||
3300 | |||
3301 | private void checkBox1_VisibleChanged(object sender, EventArgs e) |
||
3302 | { |
||
3303 | var checkBox = (CheckBox)sender; |
||
3304 | if (checkBox.Checked) |
||
3305 | { |
||
3306 | _quickImageSearchParameters = _quickImageSearchParameters | QuickImageSearchParameters.CaseSensitive; |
||
3307 | |||
3308 | return; |
||
3309 | } |
||
3310 | |||
3311 | _quickImageSearchParameters = _quickImageSearchParameters & ~QuickImageSearchParameters.CaseSensitive; |
||
3312 | } |
||
3313 | |||
3314 | private void checkBox1_CheckedChanged(object sender, EventArgs e) |
||
3315 | { |
||
3316 | var checkBox = (CheckBox)sender; |
||
3317 | switch (checkBox.Checked) |
||
3318 | { |
||
3319 | case true: |
||
3320 | _quickImageSearchParameters = |
||
3321 | _quickImageSearchParameters | QuickImageSearchParameters.CaseSensitive; |
||
3322 | |||
3323 | break; |
||
3324 | case false: |
||
3325 | _quickImageSearchParameters = |
||
3326 | _quickImageSearchParameters & ~QuickImageSearchParameters.CaseSensitive; |
||
3327 | break; |
||
3328 | } |
||
3329 | |||
3330 | var text = textBox1.Text; |
||
8 | office | 3331 | if (string.IsNullOrEmpty(text)) |
3332 | { |
||
3333 | return; |
||
3334 | } |
||
7 | office | 3335 | |
8 | office | 3336 | if (_searchCancellationTokenSource != null) |
3337 | { |
||
3338 | _searchCancellationTokenSource.Cancel(); |
||
3339 | } |
||
7 | office | 3340 | |
3341 | _searchCancellationTokenSource = new CancellationTokenSource(); |
||
3342 | _searchCancellationToken = _searchCancellationTokenSource.Token; |
||
3343 | _linkedSearchCancellationTokenSource = |
||
3344 | CancellationTokenSource.CreateLinkedTokenSource(_cancellationToken, _searchCancellationToken); |
||
3345 | _combinedSearchSelectionCancellationToken = _linkedSearchCancellationTokenSource.Token; |
||
3346 | |||
8 | office | 3347 | _searchScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(250), text, async text => { await BeginSearch(text); }, _formTaskScheduler, _combinedSearchSelectionCancellationToken); |
7 | office | 3348 | } |
3349 | |||
3350 | private void PreviewFormClosing(object sender, CancelEventArgs e) |
||
3351 | { |
||
8 | office | 3352 | if (_previewForm == null) |
3353 | { |
||
3354 | return; |
||
3355 | } |
||
7 | office | 3356 | |
3357 | _previewForm.Closing -= PreviewFormClosing; |
||
3358 | _previewForm.Dispose(); |
||
3359 | _previewForm = null; |
||
3360 | } |
||
3361 | |||
3362 | private async void perceptionToolStripMenuItem1_Click(object sender, EventArgs e) |
||
3363 | { |
||
3364 | await SortImageListView(new PerceptionImageListViewItemSorter( |
||
3365 | imageListView.Items.OfType<ListViewItem>().ToList(), |
||
3366 | _quickImageDatabase, |
||
3367 | _cancellationToken)); |
||
3368 | } |
||
3369 | |||
3370 | private void openDirectoryToolStripMenuItem_Click(object sender, EventArgs e) |
||
3371 | { |
||
3372 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
3373 | |||
3374 | var item = items.FirstOrDefault(); |
||
8 | office | 3375 | if (item == null) |
3376 | { |
||
3377 | return; |
||
3378 | } |
||
7 | office | 3379 | |
3380 | Process.Start("explorer.exe", $"/select, \"{item.Name}\""); |
||
3381 | } |
||
3382 | |||
3383 | private void aboutToolStripMenuItem1_Click(object sender, EventArgs e) |
||
3384 | { |
||
8 | office | 3385 | if (_aboutForm != null) |
3386 | { |
||
3387 | return; |
||
3388 | } |
||
7 | office | 3389 | |
3390 | _aboutForm = new AboutForm(); |
||
3391 | _aboutForm.Closing += AboutForm_Closing; |
||
3392 | _aboutForm.Show(); |
||
3393 | } |
||
3394 | |||
3395 | private async void imageListView_DragDrop(object sender, DragEventArgs e) |
||
3396 | { |
||
9 | office | 3397 | await LoadDataObjectAsync(e.Data); |
7 | office | 3398 | } |
3399 | |||
3400 | private void imageListView_DragEnter(object sender, DragEventArgs e) |
||
3401 | { |
||
3402 | e.Effect = e.AllowedEffect; |
||
3403 | } |
||
3404 | |||
3405 | private void imageListView_MouseDoubleClick(object sender, MouseEventArgs e) |
||
3406 | { |
||
3407 | var listView = (ListView)sender; |
||
3408 | var info = listView.HitTest(e.X, e.Y); |
||
3409 | |||
3410 | switch (info.Location) |
||
3411 | { |
||
3412 | case ListViewHitTestLocations.AboveClientArea: |
||
3413 | case ListViewHitTestLocations.BelowClientArea: |
||
3414 | case ListViewHitTestLocations.LeftOfClientArea: |
||
3415 | case ListViewHitTestLocations.RightOfClientArea: |
||
3416 | case ListViewHitTestLocations.None: |
||
3417 | return; |
||
3418 | } |
||
3419 | |||
3420 | var item = info.Item; |
||
3421 | |||
8 | office | 3422 | if (item == null) |
3423 | { |
||
3424 | return; |
||
3425 | } |
||
7 | office | 3426 | |
12 | office | 3427 | try |
8 | office | 3428 | { |
12 | office | 3429 | if (_previewForm != null) |
3430 | { |
||
3431 | _previewForm.InvokeIfRequired(form => |
||
3432 | { |
||
3433 | form.Close(); |
||
3434 | }); |
||
3435 | |||
3436 | _previewForm = null; |
||
3437 | } |
||
8 | office | 3438 | } |
12 | office | 3439 | catch(Exception exception) |
3440 | { |
||
3441 | Log.Warning(exception, "Error encoutnered while trying to close preview form."); |
||
3442 | } |
||
7 | office | 3443 | |
3444 | try |
||
3445 | { |
||
3446 | new Thread(() => |
||
3447 | { |
||
3448 | _previewForm = new PreviewForm(item.Name, Configuration, _magicMime, _cancellationToken); |
||
3449 | _previewForm.Closing += PreviewFormClosing; |
||
3450 | _previewForm.ShowDialog(); |
||
3451 | }).Start(); |
||
3452 | } |
||
3453 | catch (Exception exception) |
||
3454 | { |
||
3455 | Log.Error(exception, $"File {item.Name} could not be displayed due to the path not being accessible."); |
||
3456 | } |
||
3457 | } |
||
3458 | |||
3459 | private void imageListView_MouseUp(object sender, MouseEventArgs e) |
||
3460 | { |
||
3461 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
3462 | |||
3463 | SelectTags(items); |
||
3464 | } |
||
3465 | |||
8 | office | 3466 | private void imageListView_KeyUp(object sender, KeyEventArgs e) |
7 | office | 3467 | { |
3468 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
3469 | if (e.KeyCode == Keys.Delete) |
||
3470 | { |
||
8 | office | 3471 | RemoveImages(items, _cancellationToken); |
7 | office | 3472 | return; |
3473 | } |
||
3474 | |||
3475 | SelectTags(items); |
||
3476 | } |
||
3477 | |||
3478 | private async void imageListView_ItemDrag(object sender, ItemDragEventArgs e) |
||
3479 | { |
||
3480 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
3481 | toolStripProgressBar1.Minimum = 0; |
||
3482 | toolStripProgressBar1.Value = 0; |
||
3483 | toolStripProgressBar1.Maximum = 3; |
||
3484 | |||
8 | office | 3485 | var inputBlock = new BufferBlock<ListViewItem>(new DataflowBlockOptions { CancellationToken = _cancellationToken }); |
7 | office | 3486 | var fileTransformBlock = |
8 | office | 3487 | new TransformBlock<ListViewItem, (string Source, string Path, string Name, string Mime, string Extension)>(async item => |
7 | office | 3488 | { |
10 | office | 3489 | string mimeType = string.Empty; |
3490 | try |
||
3491 | { |
||
3492 | mimeType = await _magicMime.GetMimeType(item.Name, _cancellationToken); |
||
3493 | } |
||
3494 | catch (Exception exception) |
||
3495 | { |
||
3496 | Log.Error(exception, $"Could not determine image type for file {item.Name}"); |
||
3497 | } |
||
3498 | |||
3499 | var extension = Path.GetExtension(item.Name); |
||
3500 | var path = Path.GetTempPath(); |
||
3501 | var file = Path.GetFileNameWithoutExtension(item.Name); |
||
7 | office | 3502 | if (Configuration.OutboundDragDrop.RenameOnDragDrop) |
3503 | { |
||
10 | office | 3504 | switch (Configuration.OutboundDragDrop.DragDropRenameMethod) |
3505 | { |
||
3506 | case DragDropRenameMethod.Random: |
||
3507 | file = string.Join("", Enumerable.Repeat(0, 5).Select(n => (char)_random.Next('a', 'z' + 1))); |
||
3508 | break; |
||
3509 | case DragDropRenameMethod.Timestamp: |
||
3510 | file = $"{DateTimeOffset.Now.ToUnixTimeSeconds()}"; |
||
3511 | break; |
||
7 | office | 3512 | } |
10 | office | 3513 | } |
3514 | |||
3515 | this.InvokeIfRequired(form => |
||
3516 | { |
||
3517 | form.toolStripStatusLabel1.Text = $"File {item.Name} scanned for drag and drop..."; |
||
3518 | }); |
||
3519 | |||
3520 | return (Source: item.Name, Path: path, Name: file, Mime: mimeType, Extension: extension); |
||
3521 | |||
3522 | |||
7 | office | 3523 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
3524 | |||
3525 | #pragma warning disable CS4014 |
||
3526 | fileTransformBlock.Completion.ContinueWith(_ => |
||
3527 | #pragma warning restore CS4014 |
||
3528 | { |
||
3529 | toolStripStatusLabel1.Text = "All files scanned for drag and drop."; |
||
3530 | toolStripProgressBar1.Increment(1); |
||
3531 | }, _formTaskScheduler); |
||
3532 | |||
3533 | var noConvertTransformBlock = |
||
8 | office | 3534 | new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>(async item => |
7 | office | 3535 | { |
3536 | var destination = Path.Combine(item.Path, $"{item.Name}{item.Extension}"); |
||
8 | office | 3537 | |
7 | office | 3538 | await Miscellaneous.CopyFileAsync(item.Source, destination, _cancellationToken); |
3539 | |||
3540 | this.InvokeIfRequired(form => |
||
3541 | { |
||
3542 | form.toolStripStatusLabel1.Text = |
||
3543 | $"File {item.Source} does not need conversion for drag and drop..."; |
||
3544 | }); |
||
3545 | return destination; |
||
3546 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
3547 | |||
3548 | #pragma warning disable CS4014 |
||
3549 | noConvertTransformBlock.Completion.ContinueWith(_ => |
||
3550 | #pragma warning restore CS4014 |
||
3551 | { |
||
3552 | toolStripStatusLabel1.Text = "Conversion complete for drag and drop."; |
||
3553 | toolStripProgressBar1.Increment(1); |
||
3554 | }, _formTaskScheduler); |
||
3555 | |||
3556 | var jpegTransformBlock = |
||
8 | office | 3557 | new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>(async file => |
7 | office | 3558 | { |
3559 | using var imageStream = |
||
3560 | await _imageTool.ConvertTo(file.Source, MagickFormat.Jpeg, _cancellationToken); |
||
3561 | |||
3562 | var jpegDestination = Path.Combine(file.Path, $"{file.Name}.jpg"); |
||
8 | office | 3563 | await Miscellaneous.CopyFileAsync(imageStream, jpegDestination, _cancellationToken); |
7 | office | 3564 | |
3565 | this.InvokeIfRequired(form => |
||
3566 | { |
||
3567 | form.toolStripStatusLabel1.Text = |
||
3568 | $"File {file.Source} converted to JPEG for drag and drop..."; |
||
3569 | }); |
||
3570 | |||
3571 | return jpegDestination; |
||
3572 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
3573 | |||
3574 | #pragma warning disable CS4014 |
||
3575 | jpegTransformBlock.Completion.ContinueWith(_ => |
||
3576 | #pragma warning restore CS4014 |
||
3577 | { |
||
3578 | toolStripStatusLabel1.Text = "Conversion complete for drag and drop."; |
||
3579 | toolStripProgressBar1.Increment(1); |
||
3580 | }, _formTaskScheduler); |
||
3581 | |||
3582 | var pngTransformBlock = |
||
8 | office | 3583 | new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>(async file => |
7 | office | 3584 | { |
8 | office | 3585 | using var imageStream = await _imageTool.ConvertTo(file.Source, MagickFormat.Png, _cancellationToken); |
7 | office | 3586 | |
3587 | var pngDestination = Path.Combine(file.Path, $"{file.Name}.png"); |
||
3588 | await Miscellaneous.CopyFileAsync(imageStream, pngDestination, _cancellationToken); |
||
3589 | |||
3590 | this.InvokeIfRequired(form => |
||
3591 | { |
||
3592 | form.toolStripStatusLabel1.Text = |
||
3593 | $"File {file.Source} converted to PNG for drag and drop..."; |
||
3594 | }); |
||
3595 | |||
3596 | return pngDestination; |
||
3597 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
3598 | |||
3599 | #pragma warning disable CS4014 |
||
3600 | pngTransformBlock.Completion.ContinueWith(_ => |
||
3601 | #pragma warning restore CS4014 |
||
3602 | { |
||
3603 | toolStripStatusLabel1.Text = "Conversion complete for drag and drop."; |
||
3604 | toolStripProgressBar1.Increment(1); |
||
3605 | }, _formTaskScheduler); |
||
3606 | |||
3607 | var bmpTransformBlock = |
||
8 | office | 3608 | new TransformBlock<(string Source, string Path, string Name, string Mime, string Extension), string>( async file => |
7 | office | 3609 | { |
8 | office | 3610 | using var imageStream = await _imageTool.ConvertTo(file.Source, MagickFormat.Jpeg, _cancellationToken); |
7 | office | 3611 | |
3612 | var bmpDestination = Path.Combine(file.Path, $"{file.Name}.bmp"); |
||
3613 | await Miscellaneous.CopyFileAsync(imageStream, bmpDestination, _cancellationToken); |
||
3614 | |||
3615 | this.InvokeIfRequired(form => |
||
3616 | { |
||
3617 | form.toolStripStatusLabel1.Text = |
||
3618 | $"File {file.Source} converted to BMP for drag and drop..."; |
||
3619 | }); |
||
3620 | |||
3621 | return bmpDestination; |
||
3622 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
3623 | |||
3624 | #pragma warning disable CS4014 |
||
3625 | bmpTransformBlock.Completion.ContinueWith(_ => |
||
3626 | #pragma warning restore CS4014 |
||
3627 | { |
||
3628 | toolStripStatusLabel1.Text = "Conversion complete for drag and drop."; |
||
3629 | toolStripProgressBar1.Increment(1); |
||
3630 | }, _formTaskScheduler); |
||
3631 | |||
3632 | var iptcStripTransformBlock = new TransformBlock<string, string>(async file => |
||
3633 | { |
||
3634 | try |
||
3635 | { |
||
3636 | var mime = await _magicMime.GetMimeType(file, _cancellationToken); |
||
3637 | if (Configuration.SupportedFormats.IsSupportedVideo(mime)) |
||
3638 | { |
||
3639 | return file; |
||
3640 | } |
||
3641 | |||
3642 | if (!Configuration.SupportedFormats.IsSupportedImage(mime)) |
||
3643 | { |
||
3644 | throw new ArgumentException(); |
||
3645 | } |
||
3646 | |||
3647 | if (!await _tagManager.StripIptcProfile(file, _cancellationToken)) |
||
3648 | { |
||
3649 | throw new ArgumentException(); |
||
3650 | } |
||
3651 | } |
||
3652 | catch |
||
3653 | { |
||
3654 | this.InvokeIfRequired(form => |
||
3655 | { |
||
3656 | form.toolStripStatusLabel1.Text = |
||
3657 | $"Failed to strip IPTC tags for file {file} for drag and drop..."; |
||
3658 | }); |
||
3659 | |||
3660 | return string.Empty; |
||
3661 | } |
||
3662 | |||
3663 | this.InvokeIfRequired(form => |
||
3664 | { |
||
3665 | form.toolStripStatusLabel1.Text = $"Stripped IPTC tags from file {file} for drag and drop..."; |
||
3666 | }); |
||
3667 | |||
3668 | return file; |
||
3669 | }, new ExecutionDataflowBlockOptions { CancellationToken = _cancellationToken }); |
||
3670 | |||
3671 | #pragma warning disable CS4014 |
||
3672 | iptcStripTransformBlock.Completion.ContinueWith(_ => |
||
3673 | #pragma warning restore CS4014 |
||
3674 | { |
||
3675 | toolStripStatusLabel1.Text = "All tags stripped for drag and drop."; |
||
3676 | toolStripProgressBar1.Increment(1); |
||
3677 | }, _formTaskScheduler); |
||
3678 | |||
3679 | var outputBlock = new BufferBlock<string>(new DataflowBlockOptions |
||
3680 | { CancellationToken = _cancellationToken }); |
||
3681 | |||
3682 | using var _1 = |
||
3683 | inputBlock.LinkTo(fileTransformBlock, new DataflowLinkOptions { PropagateCompletion = true }); |
||
3684 | |||
3685 | using var _2 = fileTransformBlock.LinkTo(jpegTransformBlock, item => |
||
3686 | Configuration.OutboundDragDrop.ConvertOnDragDrop && |
||
3687 | !Configuration.SupportedFormats.IsSupportedVideo(item.Mime) && |
||
3688 | !Configuration.OutboundDragDrop.DragDropConvertExclude.IsExcludedImage(item.Mime) && |
||
3689 | Configuration.SupportedFormats.IsSupported(item.Mime) && |
||
3690 | Configuration.OutboundDragDrop.DragDropConvertType == "image/jpeg" && |
||
3691 | item.Mime != "image/jpeg"); |
||
3692 | using var _3 = |
||
3693 | jpegTransformBlock.LinkTo(iptcStripTransformBlock, |
||
3694 | file => Configuration.OutboundDragDrop.StripTagsOnDragDrop); |
||
3695 | using var _11 = |
||
3696 | jpegTransformBlock.LinkTo(outputBlock); |
||
3697 | using var _4 = fileTransformBlock.LinkTo(pngTransformBlock, item => |
||
3698 | Configuration.OutboundDragDrop.ConvertOnDragDrop && |
||
3699 | !Configuration.SupportedFormats.IsSupportedVideo(item.Mime) && |
||
3700 | !Configuration.OutboundDragDrop.DragDropConvertExclude.IsExcludedImage(item.Mime) && |
||
3701 | Configuration.SupportedFormats.IsSupported(item.Mime) && |
||
3702 | Configuration.OutboundDragDrop.DragDropConvertType == "image/png" && |
||
3703 | item.Mime != "image/png"); |
||
3704 | using var _5 = |
||
3705 | pngTransformBlock.LinkTo(iptcStripTransformBlock, |
||
3706 | file => Configuration.OutboundDragDrop.StripTagsOnDragDrop); |
||
3707 | using var _12 = |
||
3708 | pngTransformBlock.LinkTo(outputBlock); |
||
3709 | using var _6 = fileTransformBlock.LinkTo(bmpTransformBlock, item => |
||
3710 | Configuration.OutboundDragDrop.ConvertOnDragDrop && |
||
3711 | !Configuration.SupportedFormats.IsSupportedVideo(item.Mime) && |
||
3712 | !Configuration.OutboundDragDrop.DragDropConvertExclude.IsExcludedImage(item.Mime) && |
||
3713 | Configuration.SupportedFormats.IsSupported(item.Mime) && |
||
3714 | Configuration.OutboundDragDrop.DragDropConvertType == "image/bmp" && |
||
3715 | item.Mime != "image/bmp"); |
||
3716 | using var _7 = |
||
3717 | bmpTransformBlock.LinkTo(iptcStripTransformBlock, |
||
3718 | file => Configuration.OutboundDragDrop.StripTagsOnDragDrop); |
||
3719 | using var _13 = |
||
3720 | bmpTransformBlock.LinkTo(outputBlock); |
||
3721 | |||
3722 | using var _8 = fileTransformBlock.LinkTo(noConvertTransformBlock); |
||
3723 | using var _15 = fileTransformBlock.LinkTo(DataflowBlock |
||
3724 | .NullTarget<(string Source, string Path, string Name, string Mime, string Extension)>()); |
||
3725 | |||
3726 | using var _9 = noConvertTransformBlock.LinkTo(iptcStripTransformBlock, |
||
3727 | file => Configuration.OutboundDragDrop.StripTagsOnDragDrop); |
||
3728 | |||
3729 | using var _10 = |
||
3730 | iptcStripTransformBlock.LinkTo(outputBlock, file => !string.IsNullOrEmpty(file)); |
||
3731 | using var _16 = iptcStripTransformBlock.LinkTo(DataflowBlock.NullTarget<string>()); |
||
3732 | using var _14 = |
||
3733 | noConvertTransformBlock.LinkTo(outputBlock, file => !string.IsNullOrEmpty(file)); |
||
3734 | using var _17 = noConvertTransformBlock.LinkTo(DataflowBlock.NullTarget<string>()); |
||
3735 | |||
3736 | foreach (var item in imageListView.SelectedItems.OfType<ListViewItem>()) |
||
3737 | { |
||
3738 | #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed |
||
3739 | inputBlock.SendAsync(item, _cancellationToken); |
||
3740 | #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed |
||
3741 | } |
||
3742 | |||
3743 | inputBlock.Complete(); |
||
3744 | |||
3745 | await fileTransformBlock.Completion.ContinueWith(_ => |
||
3746 | { |
||
3747 | jpegTransformBlock.Complete(); |
||
3748 | pngTransformBlock.Complete(); |
||
3749 | bmpTransformBlock.Complete(); |
||
3750 | noConvertTransformBlock.Complete(); |
||
3751 | iptcStripTransformBlock.Complete(); |
||
3752 | }, _cancellationToken); |
||
3753 | |||
3754 | await Task.WhenAll( |
||
3755 | jpegTransformBlock.Completion, |
||
3756 | pngTransformBlock.Completion, |
||
3757 | bmpTransformBlock.Completion, |
||
3758 | iptcStripTransformBlock.Completion, |
||
8 | office | 3759 | noConvertTransformBlock.Completion).ContinueWith(_ => |
3760 | { |
||
7 | office | 3761 | outputBlock.Complete(); |
3762 | }, _cancellationToken); |
||
3763 | |||
3764 | var files = new HashSet<string>(); |
||
3765 | while (await outputBlock.OutputAvailableAsync(_cancellationToken)) |
||
3766 | { |
||
3767 | if (!outputBlock.TryReceiveAll(out var items)) |
||
3768 | { |
||
3769 | continue; |
||
3770 | } |
||
3771 | |||
3772 | files.UnionWith(items); |
||
3773 | } |
||
3774 | |||
3775 | files.RemoveWhere(string.IsNullOrEmpty); |
||
3776 | |||
3777 | this.InvokeIfRequired(form => |
||
3778 | { |
||
3779 | form.toolStripStatusLabel1.Text = $"{files.Count} ready for drag and drop..."; |
||
3780 | }); |
||
3781 | |||
3782 | if (files.Count == 0) |
||
3783 | { |
||
3784 | return; |
||
3785 | } |
||
3786 | |||
3787 | var data = new DataObject(DataFormats.FileDrop, files.ToArray()); |
||
8 | office | 3788 | this.InvokeIfRequired(_ => |
3789 | { |
||
7 | office | 3790 | DoDragDrop(data, DragDropEffects.Copy); |
3791 | }); |
||
3792 | } |
||
3793 | |||
3794 | private void imageListView_DragOver(object sender, DragEventArgs e) |
||
3795 | { |
||
3796 | } |
||
3797 | |||
3798 | private async void nameToolStripMenuItem_Click(object sender, EventArgs e) |
||
3799 | { |
||
3800 | await SortImageListView(new NameImageListViewItemSorter()); |
||
3801 | } |
||
3802 | |||
3803 | private async void ascendingToolStripMenuItem_Click(object sender, EventArgs e) |
||
3804 | { |
||
3805 | await SortImageListView(new SizeImageListViewItemSorter(SortOrder.Ascending)); |
||
3806 | } |
||
3807 | |||
3808 | private async void descendingToolStripMenuItem_Click(object sender, EventArgs e) |
||
3809 | { |
||
3810 | await SortImageListView(new SizeImageListViewItemSorter(SortOrder.Descending)); |
||
3811 | } |
||
3812 | |||
3813 | private async void typeToolStripMenuItem_Click(object sender, EventArgs e) |
||
3814 | { |
||
3815 | await SortImageListView(new TypeImageListViewItemSorter()); |
||
3816 | } |
||
3817 | |||
3818 | private async void importToolStripMenuItem_Click(object sender, EventArgs e) |
||
3819 | { |
||
3820 | var dialog = new CommonOpenFileDialog |
||
3821 | { |
||
3822 | AddToMostRecentlyUsedList = true, |
||
3823 | Multiselect = true, |
||
3824 | IsFolderPicker = false |
||
3825 | }; |
||
3826 | |||
8 | office | 3827 | switch (dialog.ShowDialog()) |
3828 | { |
||
3829 | case CommonFileDialogResult.Ok: |
||
3830 | await LoadFilesAsync(dialog.FileNames, _magicMime, _cancellationToken); |
||
3831 | break; |
||
3832 | } |
||
7 | office | 3833 | } |
3834 | |||
3835 | private async void importDirectoryToolStripMenuItem_Click(object sender, EventArgs e) |
||
3836 | { |
||
3837 | var dialog = new CommonOpenFileDialog |
||
3838 | { |
||
3839 | AddToMostRecentlyUsedList = true, |
||
3840 | Multiselect = true, |
||
3841 | IsFolderPicker = true |
||
3842 | }; |
||
3843 | |||
8 | office | 3844 | switch (dialog.ShowDialog()) |
3845 | { |
||
3846 | case CommonFileDialogResult.Ok: |
||
3847 | await LoadFilesAsync(dialog.FileNames, _magicMime, _cancellationToken); |
||
3848 | break; |
||
3849 | } |
||
7 | office | 3850 | } |
3851 | |||
3852 | private void settingsToolStripMenuItem_Click(object sender, EventArgs e) |
||
3853 | { |
||
8 | office | 3854 | if (_settingsForm != null) |
3855 | { |
||
3856 | return; |
||
3857 | } |
||
7 | office | 3858 | |
3859 | _settingsForm = new SettingsForm(Configuration, _cancellationToken); |
||
3860 | _settingsForm.Closing += SettingsForm_Closing; |
||
3861 | _settingsForm.Show(); |
||
3862 | } |
||
3863 | |||
3864 | private void SettingsForm_Closing(object sender, CancelEventArgs e) |
||
3865 | { |
||
8 | office | 3866 | if (_settingsForm == null) |
3867 | { |
||
3868 | return; |
||
3869 | } |
||
7 | office | 3870 | |
8 | office | 3871 | if (_settingsForm.SaveOnClose) |
3872 | { |
||
3873 | // Commit the configuration. |
||
7 | office | 3874 | _changedConfigurationContinuation.Schedule(TimeSpan.FromSeconds(1), |
8 | office | 3875 | async () => { await SaveConfiguration(Configuration); }, _cancellationToken); |
3876 | } |
||
7 | office | 3877 | |
3878 | _settingsForm.Closing -= SettingsForm_Closing; |
||
3879 | _settingsForm.Dispose(); |
||
3880 | _settingsForm = null; |
||
3881 | } |
||
3882 | |||
3883 | private async void removeTagsToolStripMenuItem_Click(object sender, EventArgs e) |
||
3884 | { |
||
3885 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
3886 | |||
3887 | var count = items.Length; |
||
3888 | |||
3889 | toolStripProgressBar1.Style = ProgressBarStyle.Continuous; |
||
3890 | toolStripProgressBar1.Minimum = 0; |
||
3891 | toolStripProgressBar1.Maximum = count; |
||
3892 | toolStripProgressBar1.Value = 0; |
||
3893 | |||
3894 | void ImageListViewItemProgress(object sender, ImageListViewItemProgress<ListViewItem> e) |
||
3895 | { |
||
3896 | switch (e) |
||
3897 | { |
||
3898 | case ImageListViewItemProgressSuccess<ListViewItem> imageListViewItemProgressSuccess: |
||
3899 | if (!(imageListViewItemProgressSuccess.Item is { } imageListViewItem)) break; |
||
3900 | |||
3901 | foreach (var tag in imageListViewItemProgressSuccess.Tags) |
||
3902 | { |
||
3903 | tagListView.BeginUpdate(); |
||
3904 | if (tagListView.CheckedItems.ContainsKey(tag)) |
||
3905 | { |
||
3906 | tagListView.Items[tag].Checked = false; |
||
3907 | tagListView.EndUpdate(); |
||
3908 | continue; |
||
3909 | } |
||
3910 | |||
3911 | tagListView.Items.Add(new ListViewItem(tag) { Name = tag }); |
||
3912 | tagListView.Items[tag].Checked = false; |
||
3913 | tagListView.EndUpdate(); |
||
3914 | } |
||
3915 | |||
3916 | break; |
||
3917 | case ImageListViewItemProgressFailure<ListViewItem> _: |
||
3918 | break; |
||
3919 | } |
||
3920 | |||
3921 | toolStripStatusLabel1.Text = "Stripping tags..."; |
||
3922 | toolStripProgressBar1.Increment(1); |
||
3923 | if (toolStripProgressBar1.Value == toolStripProgressBar1.Maximum) |
||
3924 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
3925 | } |
||
3926 | |||
3927 | toolStripStatusLabel1.Text = "Stripping tags..."; |
||
3928 | |||
3929 | _listViewItemProgress.ProgressChanged += ImageListViewItemProgress; |
||
3930 | try |
||
3931 | { |
||
3932 | await StripTags(items, _magicMime, _listViewItemProgress, _cancellationToken); |
||
3933 | } |
||
3934 | catch |
||
3935 | { |
||
3936 | _listViewItemProgress.ProgressChanged -= ImageListViewItemProgress; |
||
3937 | } |
||
3938 | } |
||
3939 | |||
3940 | private async void balanceTagsToolStripMenuItem_Click(object sender, EventArgs e) |
||
3941 | { |
||
3942 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
3943 | |||
3944 | var listViewItems = items as ListViewItem[] ?? items.ToArray(); |
||
8 | office | 3945 | if (listViewItems.Length < 2) |
3946 | { |
||
3947 | return; |
||
3948 | } |
||
7 | office | 3949 | |
3950 | await BalanceTags(listViewItems); |
||
3951 | } |
||
3952 | |||
3953 | private async void loadMissingToolStripMenuItem_Click(object sender, EventArgs e) |
||
3954 | { |
||
3955 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
3956 | |||
3957 | var listViewItems = items as ListViewItem[] ?? items.ToArray(); |
||
3958 | |||
3959 | await LoadFilesAsync(listViewItems.Select(item => item.Group.Name), _magicMime, _cancellationToken); |
||
3960 | } |
||
3961 | |||
3962 | private void moveToolStripMenuItem_DropDownOpening(object sender, EventArgs e) |
||
3963 | { |
||
3964 | var menuItem = (ToolStripMenuItem)sender; |
||
3965 | |||
3966 | foreach (var group in _imageListViewGroupDictionary.Keys) |
||
3967 | { |
||
8 | office | 3968 | if (menuItem.DropDownItems.ContainsKey(group)) |
3969 | { |
||
3970 | continue; |
||
3971 | } |
||
7 | office | 3972 | |
20 | office | 3973 | var toolStripMenuSubItem = new ToolStripMenuItem(group) { Name = group }; |
7 | office | 3974 | menuItem.DropDownItems.Add(toolStripMenuSubItem); |
3975 | } |
||
8 | office | 3976 | } |
3977 | |||
3978 | private void moveToolStripMenuItem_DropDownClosed(object sender, EventArgs e) |
||
3979 | { |
||
20 | office | 3980 | |
8 | office | 3981 | var menuItem = (ToolStripMenuItem)sender; |
3982 | |||
20 | office | 3983 | var items = new ConcurrentBag<ToolStripMenuItem>(); |
3984 | foreach(var toolStripMenuSubItem in menuItem.DropDownItems.OfType<ToolStripMenuItem>()) |
||
8 | office | 3985 | { |
3986 | items.Add(toolStripMenuSubItem); |
||
3987 | } |
||
3988 | |||
20 | office | 3989 | while(items.TryTake(out var toolStripMenuSubItem)) |
8 | office | 3990 | { |
3991 | menuItem.DropDownItems.Remove(toolStripMenuSubItem); |
||
3992 | } |
||
7 | office | 3993 | } |
3994 | |||
20 | office | 3995 | private async void moveTargetToolStripMenuItem_Click(object sender, ToolStripItemClickedEventArgs e) |
7 | office | 3996 | { |
20 | office | 3997 | var menuItem = (ToolStripMenuItem)e.ClickedItem; |
7 | office | 3998 | |
3999 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
4000 | |||
4001 | await MoveImagesAsync(items, menuItem.Name, _cancellationToken); |
||
4002 | } |
||
4003 | |||
4004 | private void imageListView_GroupHeaderClick(object sender, ListViewGroup e) |
||
4005 | { |
||
4006 | var listViewCollapsible = (ListViewCollapsible)sender; |
||
4007 | |||
4008 | var collapsed = listViewCollapsible.GetCollapsed(e); |
||
4009 | if (collapsed) |
||
4010 | { |
||
4011 | listViewCollapsible.SetCollapsed(e, false); |
||
4012 | return; |
||
4013 | } |
||
4014 | |||
4015 | listViewCollapsible.SetCollapsed(e, true); |
||
4016 | } |
||
4017 | |||
4018 | private async void convertToTypeToolStripMenuItem_Click(object sender, EventArgs e) |
||
4019 | { |
||
4020 | var toolStripMenuItem = (ToolStripMenuItem)sender; |
||
4021 | var extension = toolStripMenuItem.Text; |
||
4022 | |||
4023 | var extensionToMime = new Dictionary<string, string> |
||
4024 | { |
||
4025 | { "jpg", "image/jpeg" }, |
||
4026 | { "png", "image/png" }, |
||
4027 | { "bmp", "image/bmp" }, |
||
4028 | { "gif", "image/gif" } |
||
4029 | }; |
||
4030 | |||
8 | office | 4031 | if (!Configuration.SupportedFormats.Images.Image.Contains(extensionToMime[extension])) |
4032 | { |
||
4033 | return; |
||
4034 | } |
||
7 | office | 4035 | |
4036 | var items = imageListView.SelectedItems.OfType<ListViewItem>(); |
||
4037 | |||
4038 | await ConvertImagesAsync(items, extension, _cancellationToken); |
||
4039 | } |
||
4040 | |||
4041 | #endregion |
||
4042 | |||
4043 | private void button1_Click(object sender, EventArgs e) |
||
4044 | { |
||
4045 | _sortScheduledContinuation.Schedule(TimeSpan.FromMilliseconds(50), () => |
||
4046 | { |
||
4047 | tagListView.BeginUpdate(); |
||
4048 | tagListView.Sort(); |
||
4049 | tagListView.EndUpdate(); |
||
4050 | }, _formTaskScheduler, _cancellationToken); |
||
4051 | } |
||
4052 | |||
4053 | /// <summary> |
||
4054 | /// Enable or disable menu items recursively. |
||
4055 | /// </summary> |
||
4056 | /// <param name="item">the menu item from where to start disabling or enabling menu items</param> |
||
4057 | /// <param name="enable">whether to enable or disable the menu item |
||
4058 | /// </param> |
||
4059 | private void ToggleMenuItemsRecursive(ToolStripMenuItem item, MenuItemsToggleOperation menuItemsToggleOperation) |
||
4060 | { |
||
4061 | if (!item.HasDropDown) |
||
4062 | { |
||
4063 | return; |
||
4064 | } |
||
4065 | |||
4066 | switch (menuItemsToggleOperation) |
||
4067 | { |
||
4068 | case MenuItemsToggleOperation.NONE: |
||
4069 | throw new ArgumentException("Unknown menu toggle operation."); |
||
4070 | case MenuItemsToggleOperation.ENABLE: |
||
4071 | item.Enabled = true; |
||
4072 | break; |
||
4073 | case MenuItemsToggleOperation.DISABLE: |
||
4074 | item.Enabled = false; |
||
4075 | break; |
||
4076 | } |
||
4077 | |||
4078 | foreach (var menuItem in item.DropDownItems.OfType<ToolStripMenuItem>()) |
||
4079 | { |
||
4080 | ToggleMenuItemsRecursive(menuItem, menuItemsToggleOperation); |
||
4081 | } |
||
4082 | } |
||
4083 | |||
4084 | private void contextMenuStrip1_Closing(object sender, ToolStripDropDownClosingEventArgs e) |
||
4085 | { |
||
4086 | ToggleMenuItemsRecursive(directoryToolStripMenuItem, MenuItemsToggleOperation.ENABLE); |
||
4087 | ToggleMenuItemsRecursive(fileToolStripMenuItem1, MenuItemsToggleOperation.ENABLE); |
||
4088 | ToggleMenuItemsRecursive(imageToolStripMenuItem, MenuItemsToggleOperation.ENABLE); |
||
4089 | ToggleMenuItemsRecursive(taggingToolStripMenuItem, MenuItemsToggleOperation.ENABLE); |
||
4090 | } |
||
4091 | |||
4092 | private void contextMenuStrip1_Opening(object sender, CancelEventArgs e) |
||
4093 | { |
||
4094 | var mousePoint = imageListView.PointToClient(Cursor.Position); |
||
4095 | var listViewHitTestInfo = imageListView.HitTest(mousePoint); |
||
4096 | // check if the mouse was hovering over an item |
||
4097 | if (listViewHitTestInfo.Item != null) |
||
4098 | { |
||
4099 | // hovering |
||
4100 | ToggleMenuItemsRecursive(directoryToolStripMenuItem, MenuItemsToggleOperation.ENABLE); |
||
4101 | ToggleMenuItemsRecursive(fileToolStripMenuItem1, MenuItemsToggleOperation.ENABLE); |
||
4102 | ToggleMenuItemsRecursive(imageToolStripMenuItem, MenuItemsToggleOperation.ENABLE); |
||
4103 | ToggleMenuItemsRecursive(taggingToolStripMenuItem, MenuItemsToggleOperation.ENABLE); |
||
4104 | return; |
||
4105 | } |
||
4106 | |||
4107 | // disable menu items not related to list view items |
||
4108 | ToggleMenuItemsRecursive(directoryToolStripMenuItem, MenuItemsToggleOperation.DISABLE); |
||
4109 | ToggleMenuItemsRecursive(fileToolStripMenuItem1, MenuItemsToggleOperation.DISABLE); |
||
4110 | ToggleMenuItemsRecursive(imageToolStripMenuItem, MenuItemsToggleOperation.DISABLE); |
||
4111 | ToggleMenuItemsRecursive(taggingToolStripMenuItem, MenuItemsToggleOperation.DISABLE); |
||
4112 | } |
||
9 | office | 4113 | |
4114 | private void copyCtrlToolStripMenuItem_Click(object sender, EventArgs e) |
||
4115 | { |
||
4116 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
4117 | switch(items.Length) |
||
4118 | { |
||
4119 | case 0: |
||
4120 | break; |
||
4121 | default: |
||
4122 | // copy file paths as the default for multiple files selected |
||
4123 | var paths = new StringCollection(); |
||
4124 | foreach (var item in items) |
||
4125 | { |
||
4126 | paths.Add(item.Name); |
||
4127 | } |
||
4128 | |||
4129 | Clipboard.SetFileDropList(paths); |
||
4130 | break; |
||
4131 | } |
||
4132 | } |
||
4133 | |||
4134 | private async void copyAsMediaToolStripMenuItem_Click(object sender, EventArgs e) |
||
4135 | { |
||
4136 | var items = imageListView.SelectedItems.OfType<ListViewItem>().ToArray(); |
||
4137 | |||
4138 | switch (items.Length) |
||
4139 | { |
||
4140 | case 1: |
||
4141 | |||
4142 | { |
||
4143 | var file = items[0].Name; |
||
4144 | |||
4145 | using var memoryStream = new MemoryStream(); |
||
4146 | using var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read); |
||
4147 | await fileStream.CopyToAsync(memoryStream); |
||
4148 | memoryStream.Position = 0L; |
||
4149 | |||
10 | office | 4150 | MagicMimeFile mime; |
4151 | try |
||
4152 | { |
||
4153 | mime = _magicMime.Identify(file, memoryStream, _cancellationToken); |
||
4154 | |||
4155 | if (mime == null) |
||
4156 | { |
||
4157 | break; |
||
4158 | } |
||
4159 | } |
||
4160 | catch (Exception exception) |
||
4161 | { |
||
4162 | Log.Error(exception, $"Could not determine mime type for file {file}."); |
||
4163 | |||
4164 | break; |
||
4165 | } |
||
4166 | |||
9 | office | 4167 | if (Configuration.SupportedFormats.IsSupportedVideo(mime.Definition.File.MimeType)) |
4168 | { |
||
4169 | |||
4170 | |||
4171 | Clipboard.SetDataObject(memoryStream); |
||
4172 | break; |
||
4173 | } |
||
4174 | |||
4175 | if (!Configuration.SupportedFormats.IsSupportedImage(mime.Definition.File.MimeType)) |
||
4176 | { |
||
4177 | using var image = Image.FromFile(file); |
||
4178 | Clipboard.SetImage(image); |
||
4179 | break; |
||
4180 | } |
||
4181 | } |
||
4182 | |||
4183 | break; |
||
4184 | case 0: |
||
4185 | break; |
||
4186 | default: |
||
4187 | // copy file paths as the default for multiple files selected |
||
4188 | var paths = new StringCollection(); |
||
4189 | foreach (var item in items) |
||
4190 | { |
||
4191 | paths.Add(item.Name); |
||
4192 | } |
||
4193 | |||
4194 | Clipboard.SetFileDropList(paths); |
||
4195 | break; |
||
4196 | } |
||
4197 | |||
4198 | } |
||
4199 | |||
4200 | private async void pasteCtrlCToolStripMenuItem_Click(object sender, EventArgs e) |
||
4201 | { |
||
4202 | var dataObject = Clipboard.GetDataObject(); |
||
4203 | |||
4204 | await LoadDataObjectAsync(dataObject); |
||
4205 | } |
||
4206 | |||
4207 | |||
7 | office | 4208 | } |
4209 | } |