/ src / modules / imageresizer / ui / Models / ResizeBatch.cs
ResizeBatch.cs
  1  #pragma warning disable IDE0073
  2  // Copyright (c) Brice Lambson
  3  // The Brice Lambson licenses this file to you under the MIT license.
  4  // See the LICENSE file in the project root for more information.  Code forked from Brice Lambson's https://github.com/bricelam/ImageResizer/
  5  #pragma warning restore IDE0073
  6  
  7  using System;
  8  using System.Collections.Concurrent;
  9  using System.Collections.Generic;
 10  using System.IO;
 11  using System.IO.Abstractions;
 12  using System.IO.Pipes;
 13  using System.Linq;
 14  using System.Text;
 15  using System.Threading;
 16  using System.Threading.Tasks;
 17  
 18  using ImageResizer.Properties;
 19  using ImageResizer.Services;
 20  
 21  namespace ImageResizer.Models
 22  {
 23      public class ResizeBatch
 24      {
 25          private readonly IFileSystem _fileSystem = new FileSystem();
 26          private static IAISuperResolutionService _aiSuperResolutionService;
 27  
 28          public string DestinationDirectory { get; set; }
 29  
 30          public ICollection<string> Files { get; } = new List<string>();
 31  
 32          public static void SetAiSuperResolutionService(IAISuperResolutionService service)
 33          {
 34              _aiSuperResolutionService = service;
 35          }
 36  
 37          public static void DisposeAiSuperResolutionService()
 38          {
 39              _aiSuperResolutionService?.Dispose();
 40              _aiSuperResolutionService = null;
 41          }
 42  
 43          /// <summary>
 44          /// Validates if a file path is a supported image format.
 45          /// </summary>
 46          /// <param name="path">The file path to validate.</param>
 47          /// <returns>True if the path is valid and points to a supported image file.</returns>
 48          private static bool IsValidImagePath(string path)
 49          {
 50              if (string.IsNullOrWhiteSpace(path))
 51              {
 52                  return false;
 53              }
 54  
 55              if (!File.Exists(path))
 56              {
 57                  return false;
 58              }
 59  
 60              var ext = Path.GetExtension(path)?.ToLowerInvariant();
 61              var validExtensions = new[]
 62              {
 63                  ".bmp", ".dib", ".gif", ".jfif", ".jpe", ".jpeg", ".jpg",
 64                  ".jxr", ".png", ".rle", ".tif", ".tiff", ".wdp",
 65              };
 66  
 67              return validExtensions.Contains(ext);
 68          }
 69  
 70          /// <summary>
 71          /// Creates a ResizeBatch from CliOptions.
 72          /// </summary>
 73          /// <param name="standardInput">Standard input stream for reading additional file paths.</param>
 74          /// <param name="options">The parsed CLI options.</param>
 75          /// <returns>A ResizeBatch instance.</returns>
 76          public static ResizeBatch FromCliOptions(TextReader standardInput, CliOptions options)
 77          {
 78              var batch = new ResizeBatch
 79              {
 80                  DestinationDirectory = options.DestinationDirectory,
 81              };
 82  
 83              foreach (var file in options.Files)
 84              {
 85                  // Convert relative paths to absolute paths
 86                  var absolutePath = Path.IsPathRooted(file) ? file : Path.GetFullPath(file);
 87                  if (IsValidImagePath(absolutePath))
 88                  {
 89                      batch.Files.Add(absolutePath);
 90                  }
 91              }
 92  
 93              if (string.IsNullOrEmpty(options.PipeName))
 94              {
 95                  // NB: We read these from stdin since there are limits on the number of args you can have
 96                  // Only read from stdin if it's redirected (piped input), not from interactive terminal
 97                  string file;
 98                  if (standardInput != null && (Console.IsInputRedirected || !ReferenceEquals(standardInput, Console.In)))
 99                  {
100                      while ((file = standardInput.ReadLine()) != null)
101                      {
102                          // Convert relative paths to absolute paths
103                          var absolutePath = Path.IsPathRooted(file) ? file : Path.GetFullPath(file);
104                          if (IsValidImagePath(absolutePath))
105                          {
106                              batch.Files.Add(absolutePath);
107                          }
108                      }
109                  }
110              }
111              else
112              {
113                  using (NamedPipeClientStream pipeClient =
114                      new NamedPipeClientStream(".", options.PipeName, PipeDirection.In))
115                  {
116                      // Connect to the pipe or wait until the pipe is available.
117                      pipeClient.Connect();
118  
119                      using (StreamReader sr = new StreamReader(pipeClient, Encoding.Unicode))
120                      {
121                          string file;
122  
123                          // Display the read text to the console
124                          while ((file = sr.ReadLine()) != null)
125                          {
126                              if (IsValidImagePath(file))
127                              {
128                                  batch.Files.Add(file);
129                              }
130                          }
131                      }
132                  }
133              }
134  
135              return batch;
136          }
137  
138          public static ResizeBatch FromCommandLine(TextReader standardInput, string[] args)
139          {
140              var options = CliOptions.Parse(args);
141              return FromCliOptions(standardInput, options);
142          }
143  
144          public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, CancellationToken cancellationToken)
145          {
146              // NOTE: Settings.Default is captured once before parallel processing.
147              // Any changes to settings on disk during this batch will NOT be reflected until the next batch.
148              // This improves performance and predictability by avoiding repeated mutex acquisition and behaviour change results in a batch.
149              return Process(reportProgress, Settings.Default, cancellationToken);
150          }
151  
152          public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, Settings settings, CancellationToken cancellationToken)
153          {
154              double total = Files.Count;
155              int completed = 0;
156              var errors = new ConcurrentBag<ResizeError>();
157  
158              // TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async
159              //       APIs and a custom SynchronizationContext
160              Parallel.ForEach(
161                  Files,
162                  new ParallelOptions
163                  {
164                      CancellationToken = cancellationToken,
165                  },
166                  (file, state, i) =>
167                  {
168                      try
169                      {
170                          Execute(file, settings);
171                      }
172                      catch (Exception ex)
173                      {
174                          errors.Add(new ResizeError { File = _fileSystem.Path.GetFileName(file), Error = ex.Message });
175                      }
176  
177                      Interlocked.Increment(ref completed);
178                      reportProgress(completed, total);
179                  });
180  
181              return errors;
182          }
183  
184          protected virtual void Execute(string file, Settings settings)
185          {
186              var aiService = _aiSuperResolutionService ?? NoOpAiSuperResolutionService.Instance;
187              new ResizeOperation(file, DestinationDirectory, settings, aiService).Execute();
188          }
189      }
190  }