ItemController.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Threading.Tasks;
  7. using MediaBrowser.Controller.Events;
  8. using MediaBrowser.Controller.IO;
  9. using MediaBrowser.Controller.Resolvers;
  10. using MediaBrowser.Model.Entities;
  11. namespace MediaBrowser.Controller.Library
  12. {
  13. public class ItemController
  14. {
  15. #region PreBeginResolvePath Event
  16. /// <summary>
  17. /// Fires when a path is about to be resolved, but before child folders and files
  18. /// have been collected from the file system.
  19. /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
  20. /// </summary>
  21. public event EventHandler<PreBeginResolveEventArgs> PreBeginResolvePath;
  22. private bool OnPreBeginResolvePath(Folder parent, string path, FileAttributes attributes)
  23. {
  24. PreBeginResolveEventArgs args = new PreBeginResolveEventArgs()
  25. {
  26. Path = path,
  27. Parent = parent,
  28. FileAttributes = attributes,
  29. Cancel = false
  30. };
  31. if (PreBeginResolvePath != null)
  32. {
  33. PreBeginResolvePath(this, args);
  34. }
  35. return !args.Cancel;
  36. }
  37. #endregion
  38. #region BeginResolvePath Event
  39. /// <summary>
  40. /// Fires when a path is about to be resolved, but after child folders and files
  41. /// have been collected from the file system.
  42. /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
  43. /// </summary>
  44. public event EventHandler<ItemResolveEventArgs> BeginResolvePath;
  45. private bool OnBeginResolvePath(ItemResolveEventArgs args)
  46. {
  47. if (BeginResolvePath != null)
  48. {
  49. BeginResolvePath(this, args);
  50. }
  51. return !args.Cancel;
  52. }
  53. #endregion
  54. private async Task<BaseItem> ResolveItem(ItemResolveEventArgs args)
  55. {
  56. // If that didn't pan out, try the slow ones
  57. foreach (IBaseItemResolver resolver in Kernel.Instance.EntityResolvers)
  58. {
  59. var item = await resolver.ResolvePath(args);
  60. if (item != null)
  61. {
  62. return item;
  63. }
  64. }
  65. return null;
  66. }
  67. /// <summary>
  68. /// Resolves a path into a BaseItem
  69. /// </summary>
  70. public async Task<BaseItem> GetItem(Folder parent, string path)
  71. {
  72. return await GetItemInternal(parent, path, File.GetAttributes(path));
  73. }
  74. /// <summary>
  75. /// Resolves a path into a BaseItem
  76. /// </summary>
  77. private async Task<BaseItem> GetItemInternal(Folder parent, string path, FileAttributes attributes)
  78. {
  79. if (!OnPreBeginResolvePath(parent, path, attributes))
  80. {
  81. return null;
  82. }
  83. IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren;
  84. // Gather child folder and files
  85. if (attributes.HasFlag(FileAttributes.Directory))
  86. {
  87. fileSystemChildren = Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair<string, FileAttributes>(f, File.GetAttributes(f)));
  88. bool isVirtualFolder = parent != null && parent.IsRoot;
  89. fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder);
  90. }
  91. else
  92. {
  93. fileSystemChildren = new KeyValuePair<string, FileAttributes>[] { };
  94. }
  95. ItemResolveEventArgs args = new ItemResolveEventArgs()
  96. {
  97. Path = path,
  98. FileAttributes = attributes,
  99. FileSystemChildren = fileSystemChildren,
  100. Parent = parent,
  101. Cancel = false
  102. };
  103. // Fire BeginResolvePath to see if anyone wants to cancel this operation
  104. if (!OnBeginResolvePath(args))
  105. {
  106. return null;
  107. }
  108. BaseItem item = await ResolveItem(args);
  109. var folder = item as Folder;
  110. if (folder != null)
  111. {
  112. // If it's a folder look for child entities
  113. await AttachChildren(folder, fileSystemChildren);
  114. }
  115. return item;
  116. }
  117. /// <summary>
  118. /// Finds child BaseItems for a given Folder
  119. /// </summary>
  120. private async Task AttachChildren(Folder folder, IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren)
  121. {
  122. KeyValuePair<string, FileAttributes>[] fileSystemChildrenArray = fileSystemChildren.ToArray();
  123. int count = fileSystemChildrenArray.Length;
  124. Task<BaseItem>[] tasks = new Task<BaseItem>[count];
  125. for (int i = 0; i < count; i++)
  126. {
  127. var child = fileSystemChildrenArray[i];
  128. tasks[i] = GetItemInternal(folder, child.Key, child.Value);
  129. }
  130. BaseItem[] baseItemChildren = await Task<BaseItem>.WhenAll(tasks);
  131. // Sort them
  132. folder.Children = baseItemChildren.Where(i => i != null).OrderBy(f =>
  133. {
  134. return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
  135. }).ToArray();
  136. }
  137. /// <summary>
  138. /// Transforms shortcuts into their actual paths
  139. /// </summary>
  140. private List<KeyValuePair<string, FileAttributes>> FilterChildFileSystemEntries(IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren, bool flattenShortcuts)
  141. {
  142. List<KeyValuePair<string, FileAttributes>> returnFiles = new List<KeyValuePair<string, FileAttributes>>();
  143. // Loop through each file
  144. foreach (KeyValuePair<string, FileAttributes> file in fileSystemChildren)
  145. {
  146. // Folders
  147. if (file.Value.HasFlag(FileAttributes.Directory))
  148. {
  149. returnFiles.Add(file);
  150. }
  151. // If it's a shortcut, resolve it
  152. else if (Shortcut.IsShortcut(file.Key))
  153. {
  154. string newPath = Shortcut.ResolveShortcut(file.Key);
  155. FileAttributes newPathAttributes = File.GetAttributes(newPath);
  156. // Find out if the shortcut is pointing to a directory or file
  157. if (newPathAttributes.HasFlag(FileAttributes.Directory))
  158. {
  159. // If we're flattening then get the shortcut's children
  160. if (flattenShortcuts)
  161. {
  162. IEnumerable<KeyValuePair<string, FileAttributes>> newChildren = Directory.GetFileSystemEntries(newPath, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair<string, FileAttributes>(f, File.GetAttributes(f)));
  163. returnFiles.AddRange(FilterChildFileSystemEntries(newChildren, false));
  164. }
  165. else
  166. {
  167. returnFiles.Add(new KeyValuePair<string, FileAttributes>(newPath, newPathAttributes));
  168. }
  169. }
  170. else
  171. {
  172. returnFiles.Add(new KeyValuePair<string, FileAttributes>(newPath, newPathAttributes));
  173. }
  174. }
  175. else
  176. {
  177. returnFiles.Add(file);
  178. }
  179. }
  180. return returnFiles;
  181. }
  182. /// <summary>
  183. /// Gets a Person
  184. /// </summary>
  185. public async Task<Person> GetPerson(string name)
  186. {
  187. string path = Path.Combine(Kernel.Instance.ApplicationPaths.PeoplePath, name);
  188. return await GetImagesByNameItem<Person>(path, name);
  189. }
  190. /// <summary>
  191. /// Gets a Studio
  192. /// </summary>
  193. public async Task<Studio> GetStudio(string name)
  194. {
  195. string path = Path.Combine(Kernel.Instance.ApplicationPaths.StudioPath, name);
  196. return await GetImagesByNameItem<Studio>(path, name);
  197. }
  198. /// <summary>
  199. /// Gets a Genre
  200. /// </summary>
  201. public async Task<Genre> GetGenre(string name)
  202. {
  203. string path = Path.Combine(Kernel.Instance.ApplicationPaths.GenrePath, name);
  204. return await GetImagesByNameItem<Genre>(path, name);
  205. }
  206. /// <summary>
  207. /// Gets a Year
  208. /// </summary>
  209. public async Task<Year> GetYear(int value)
  210. {
  211. string path = Path.Combine(Kernel.Instance.ApplicationPaths.YearPath, value.ToString());
  212. return await GetImagesByNameItem<Year>(path, value.ToString());
  213. }
  214. private ConcurrentDictionary<string, object> ImagesByNameItemCache = new ConcurrentDictionary<string, object>();
  215. /// <summary>
  216. /// Generically retrieves an IBN item
  217. /// </summary>
  218. private async Task<T> GetImagesByNameItem<T>(string path, string name)
  219. where T : BaseEntity, new()
  220. {
  221. string key = path.ToLower();
  222. // Look for it in the cache, if it's not there, create it
  223. if (!ImagesByNameItemCache.ContainsKey(key))
  224. {
  225. T obj = await CreateImagesByNameItem<T>(path, name);
  226. ImagesByNameItemCache[key] = obj;
  227. return obj;
  228. }
  229. return ImagesByNameItemCache[key] as T;
  230. }
  231. /// <summary>
  232. /// Creates an IBN item based on a given path
  233. /// </summary>
  234. private async Task<T> CreateImagesByNameItem<T>(string path, string name)
  235. where T : BaseEntity, new()
  236. {
  237. T item = new T();
  238. item.Name = name;
  239. item.Id = Kernel.GetMD5(path);
  240. if (!Directory.Exists(path))
  241. {
  242. Directory.CreateDirectory(path);
  243. }
  244. item.DateCreated = Directory.GetCreationTime(path);
  245. item.DateModified = Directory.GetLastAccessTime(path);
  246. ItemResolveEventArgs args = new ItemResolveEventArgs();
  247. args.Path = path;
  248. args.FileAttributes = File.GetAttributes(path);
  249. args.FileSystemChildren = Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair<string, FileAttributes>(f, File.GetAttributes(f)));
  250. await Kernel.Instance.ExecuteMetadataProviders(item, args);
  251. return item;
  252. }
  253. }
  254. }