ImageSaver.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. using MediaBrowser.Common.IO;
  2. using MediaBrowser.Controller.Configuration;
  3. using MediaBrowser.Controller.Entities;
  4. using MediaBrowser.Controller.Entities.Audio;
  5. using MediaBrowser.Controller.Entities.TV;
  6. using MediaBrowser.Controller.IO;
  7. using MediaBrowser.Model.Configuration;
  8. using MediaBrowser.Model.Entities;
  9. using System;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Linq;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. namespace MediaBrowser.Server.Implementations.Providers
  16. {
  17. /// <summary>
  18. /// Class ImageSaver
  19. /// </summary>
  20. public class ImageSaver
  21. {
  22. private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
  23. /// <summary>
  24. /// The _config
  25. /// </summary>
  26. private readonly IServerConfigurationManager _config;
  27. /// <summary>
  28. /// The remote image cache
  29. /// </summary>
  30. private readonly FileSystemRepository _remoteImageCache;
  31. /// <summary>
  32. /// The _directory watchers
  33. /// </summary>
  34. private readonly IDirectoryWatchers _directoryWatchers;
  35. /// <summary>
  36. /// Initializes a new instance of the <see cref="ImageSaver"/> class.
  37. /// </summary>
  38. /// <param name="config">The config.</param>
  39. /// <param name="directoryWatchers">The directory watchers.</param>
  40. public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers)
  41. {
  42. _config = config;
  43. _directoryWatchers = directoryWatchers;
  44. _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath);
  45. }
  46. /// <summary>
  47. /// Saves the image.
  48. /// </summary>
  49. /// <param name="item">The item.</param>
  50. /// <param name="source">The source.</param>
  51. /// <param name="mimeType">Type of the MIME.</param>
  52. /// <param name="type">The type.</param>
  53. /// <param name="imageIndex">Index of the image.</param>
  54. /// <param name="cancellationToken">The cancellation token.</param>
  55. /// <returns>Task.</returns>
  56. public async Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
  57. {
  58. if (string.IsNullOrEmpty(mimeType))
  59. {
  60. throw new ArgumentNullException("mimeType");
  61. }
  62. var saveLocally = _config.Configuration.SaveLocalMeta;
  63. if (item is IItemByName)
  64. {
  65. saveLocally = true;
  66. }
  67. else if (item is User)
  68. {
  69. saveLocally = true;
  70. }
  71. else if (item is Audio || item.Parent == null || string.IsNullOrEmpty(item.MetaLocation))
  72. {
  73. saveLocally = false;
  74. }
  75. if (type != ImageType.Primary)
  76. {
  77. if (item is Episode)
  78. {
  79. saveLocally = false;
  80. }
  81. }
  82. if (item.LocationType == LocationType.Remote || item.LocationType == LocationType.Virtual)
  83. {
  84. saveLocally = false;
  85. }
  86. var path = saveLocally && _config.Configuration.ImageSavingConvention == ImageSavingConvention.Compatible ?
  87. GetCompatibleSavePath(item, type, imageIndex, mimeType) :
  88. GetLegacySavePath(item, type, imageIndex, mimeType, saveLocally);
  89. Directory.CreateDirectory(Path.GetDirectoryName(path));
  90. var currentPath = GetCurrentImagePath(item, type, imageIndex);
  91. try
  92. {
  93. _directoryWatchers.TemporarilyIgnore(path);
  94. using (source)
  95. {
  96. // If the file is currently hidden we'll have to remove that or the save will fail
  97. var file = new FileInfo(path);
  98. // This will fail if the file is hidden
  99. if (file.Exists)
  100. {
  101. if ((file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
  102. {
  103. file.Attributes &= ~FileAttributes.Hidden;
  104. }
  105. }
  106. using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
  107. {
  108. await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
  109. }
  110. }
  111. SetImagePath(item, type, imageIndex, path);
  112. if (!string.IsNullOrEmpty(currentPath) && !string.Equals(path, currentPath, StringComparison.OrdinalIgnoreCase))
  113. {
  114. File.Delete(currentPath);
  115. }
  116. }
  117. finally
  118. {
  119. _directoryWatchers.RemoveTempIgnore(path);
  120. }
  121. }
  122. private string GetCurrentImagePath(BaseItem item, ImageType type, int? imageIndex)
  123. {
  124. switch (type)
  125. {
  126. case ImageType.Screenshot:
  127. if (!imageIndex.HasValue)
  128. {
  129. throw new ArgumentNullException("imageIndex");
  130. }
  131. return item.ScreenshotImagePaths.Count > imageIndex.Value ? item.ScreenshotImagePaths[imageIndex.Value] : null;
  132. case ImageType.Backdrop:
  133. if (!imageIndex.HasValue)
  134. {
  135. throw new ArgumentNullException("imageIndex");
  136. }
  137. return item.BackdropImagePaths.Count > imageIndex.Value ? item.BackdropImagePaths[imageIndex.Value] : null;
  138. default:
  139. return item.GetImage(type);
  140. }
  141. }
  142. private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path)
  143. {
  144. switch (type)
  145. {
  146. case ImageType.Screenshot:
  147. if (!imageIndex.HasValue)
  148. {
  149. throw new ArgumentNullException("imageIndex");
  150. }
  151. if (item.ScreenshotImagePaths.Count > imageIndex.Value)
  152. {
  153. item.ScreenshotImagePaths[imageIndex.Value] = path;
  154. }
  155. else
  156. {
  157. item.ScreenshotImagePaths.Add(path);
  158. }
  159. break;
  160. case ImageType.Backdrop:
  161. if (!imageIndex.HasValue)
  162. {
  163. throw new ArgumentNullException("imageIndex");
  164. }
  165. if (item.BackdropImagePaths.Count > imageIndex.Value)
  166. {
  167. item.BackdropImagePaths[imageIndex.Value] = path;
  168. }
  169. else
  170. {
  171. item.BackdropImagePaths.Add(path);
  172. }
  173. break;
  174. default:
  175. item.SetImage(type, path);
  176. break;
  177. }
  178. }
  179. /// <summary>
  180. /// Gets the save path.
  181. /// </summary>
  182. /// <param name="item">The item.</param>
  183. /// <param name="type">The type.</param>
  184. /// <param name="imageIndex">Index of the image.</param>
  185. /// <param name="mimeType">Type of the MIME.</param>
  186. /// <param name="saveLocally">if set to <c>true</c> [save locally].</param>
  187. /// <returns>System.String.</returns>
  188. /// <exception cref="System.ArgumentNullException">
  189. /// imageIndex
  190. /// or
  191. /// imageIndex
  192. /// </exception>
  193. private string GetLegacySavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally)
  194. {
  195. string filename;
  196. switch (type)
  197. {
  198. case ImageType.Art:
  199. filename = "clearart";
  200. break;
  201. case ImageType.Primary:
  202. filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder";
  203. break;
  204. case ImageType.Backdrop:
  205. if (!imageIndex.HasValue)
  206. {
  207. throw new ArgumentNullException("imageIndex");
  208. }
  209. filename = imageIndex.Value == 0 ? "backdrop" : "backdrop" + imageIndex.Value.ToString(UsCulture);
  210. break;
  211. case ImageType.Screenshot:
  212. if (!imageIndex.HasValue)
  213. {
  214. throw new ArgumentNullException("imageIndex");
  215. }
  216. filename = imageIndex.Value == 0 ? "screenshot" : "screenshot" + imageIndex.Value.ToString(UsCulture);
  217. break;
  218. default:
  219. filename = type.ToString().ToLower();
  220. break;
  221. }
  222. var extension = mimeType.Split('/').Last();
  223. if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase))
  224. {
  225. extension = "jpg";
  226. }
  227. filename += "." + extension.ToLower();
  228. string path = null;
  229. if (saveLocally)
  230. {
  231. if (item.IsInMixedFolder && !(item is Episode))
  232. {
  233. path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
  234. }
  235. if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(item.MetaLocation))
  236. {
  237. path = Path.Combine(item.MetaLocation, filename);
  238. }
  239. }
  240. // None of the save local conditions passed, so store it in our internal folders
  241. if (string.IsNullOrEmpty(path))
  242. {
  243. path = _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Id, filename);
  244. }
  245. return path;
  246. }
  247. /// <summary>
  248. /// Gets the compatible save path.
  249. /// </summary>
  250. /// <param name="item">The item.</param>
  251. /// <param name="type">The type.</param>
  252. /// <param name="imageIndex">Index of the image.</param>
  253. /// <param name="mimeType">Type of the MIME.</param>
  254. /// <returns>System.String.</returns>
  255. /// <exception cref="System.ArgumentNullException">imageIndex</exception>
  256. private string GetCompatibleSavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType)
  257. {
  258. var extension = mimeType.Split('/').Last();
  259. if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase))
  260. {
  261. extension = "jpg";
  262. }
  263. extension = "." + extension.ToLower();
  264. // Backdrop paths
  265. if (type == ImageType.Backdrop)
  266. {
  267. if (!imageIndex.HasValue)
  268. {
  269. throw new ArgumentNullException("imageIndex");
  270. }
  271. if (imageIndex.Value == 0)
  272. {
  273. return Path.Combine(item.MetaLocation, "fanart" + extension);
  274. }
  275. return Path.Combine(item.MetaLocation, "extrafanart", "fanart" + imageIndex.Value.ToString(UsCulture) + extension);
  276. }
  277. if (type == ImageType.Primary)
  278. {
  279. if (item is Episode)
  280. {
  281. return Path.ChangeExtension(item.Path, extension);
  282. }
  283. if (item.IsInMixedFolder)
  284. {
  285. return GetSavePathForItemInMixedFolder(item, type, string.Empty, extension);
  286. }
  287. var filename = Path.GetFileNameWithoutExtension(item.Path) + "-poster" + extension;
  288. return Path.Combine(item.MetaLocation, filename);
  289. }
  290. // All other paths are the same
  291. return GetLegacySavePath(item, type, imageIndex, mimeType, true);
  292. }
  293. /// <summary>
  294. /// Gets the save path for item in mixed folder.
  295. /// </summary>
  296. /// <param name="item">The item.</param>
  297. /// <param name="type">The type.</param>
  298. /// <param name="imageFilename">The image filename.</param>
  299. /// <param name="extension">The extension.</param>
  300. /// <returns>System.String.</returns>
  301. private string GetSavePathForItemInMixedFolder(BaseItem item, ImageType type, string imageFilename, string extension)
  302. {
  303. if (type == ImageType.Primary)
  304. {
  305. imageFilename = "poster";
  306. }
  307. var folder = Path.GetDirectoryName(item.Path);
  308. return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension);
  309. }
  310. }
  311. }