ItemController.cs 11 KB

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