ItemController.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using MediaBrowser.Common.Events;
  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. private List<IBaseItemResolver> Resolvers = new List<IBaseItemResolver>();
  16. /// <summary>
  17. /// Registers a new BaseItem resolver.
  18. /// </summary>
  19. public void AddResovler<TBaseItemType, TResolverType>()
  20. where TBaseItemType : BaseItem, new()
  21. where TResolverType : BaseItemResolver<TBaseItemType>, new()
  22. {
  23. Resolvers.Insert(0, new TResolverType());
  24. }
  25. #region PreBeginResolvePath Event
  26. /// <summary>
  27. /// Fires when a path is about to be resolved, but before child folders and files
  28. /// have been collected from the file system.
  29. /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
  30. /// </summary>
  31. public event EventHandler<PreBeginResolveEventArgs> PreBeginResolvePath;
  32. private bool OnPreBeginResolvePath(Folder parent, string path, FileAttributes attributes)
  33. {
  34. PreBeginResolveEventArgs args = new PreBeginResolveEventArgs()
  35. {
  36. Path = path,
  37. Parent = parent,
  38. FileAttributes = attributes,
  39. Cancel = false
  40. };
  41. if (PreBeginResolvePath != null)
  42. {
  43. PreBeginResolvePath(this, args);
  44. }
  45. return !args.Cancel;
  46. }
  47. #endregion
  48. #region BeginResolvePath Event
  49. /// <summary>
  50. /// Fires when a path is about to be resolved, but after child folders and files
  51. /// have been collected from the file system.
  52. /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
  53. /// </summary>
  54. public event EventHandler<ItemResolveEventArgs> BeginResolvePath;
  55. private bool OnBeginResolvePath(ItemResolveEventArgs args)
  56. {
  57. if (BeginResolvePath != null)
  58. {
  59. BeginResolvePath(this, args);
  60. }
  61. return !args.Cancel;
  62. }
  63. #endregion
  64. #region Item Events
  65. /// <summary>
  66. /// Called when an item is being created.
  67. /// This should be used to fill item values, such as metadata
  68. /// </summary>
  69. public event EventHandler<GenericItemEventArgs<BaseItem>> ItemCreating;
  70. /// <summary>
  71. /// Called when an item has been created.
  72. /// This should be used to process or modify item values.
  73. /// </summary>
  74. public event EventHandler<GenericItemEventArgs<BaseItem>> ItemCreated;
  75. #endregion
  76. /// <summary>
  77. /// Called when an item has been created
  78. /// </summary>
  79. private void OnItemCreated(BaseItem item, Folder parent)
  80. {
  81. GenericItemEventArgs<BaseItem> args = new GenericItemEventArgs<BaseItem> { Item = item };
  82. if (ItemCreating != null)
  83. {
  84. ItemCreating(this, args);
  85. }
  86. if (ItemCreated != null)
  87. {
  88. ItemCreated(this, args);
  89. }
  90. }
  91. private void FireCreateEventsRecursive(Folder folder, Folder parent)
  92. {
  93. OnItemCreated(folder, parent);
  94. int count = folder.Children.Length;
  95. Parallel.For(0, count, i =>
  96. {
  97. BaseItem item = folder.Children[i];
  98. Folder childFolder = item as Folder;
  99. if (childFolder != null)
  100. {
  101. FireCreateEventsRecursive(childFolder, folder);
  102. }
  103. else
  104. {
  105. OnItemCreated(item, folder);
  106. }
  107. });
  108. }
  109. private BaseItem ResolveItem(ItemResolveEventArgs args)
  110. {
  111. // If that didn't pan out, try the slow ones
  112. foreach (IBaseItemResolver resolver in Resolvers)
  113. {
  114. var item = resolver.ResolvePath(args);
  115. if (item != null)
  116. {
  117. return item;
  118. }
  119. }
  120. return null;
  121. }
  122. /// <summary>
  123. /// Resolves a path into a BaseItem
  124. /// </summary>
  125. public BaseItem GetItem(string path)
  126. {
  127. return GetItem(null, path);
  128. }
  129. /// <summary>
  130. /// Resolves a path into a BaseItem
  131. /// </summary>
  132. public BaseItem GetItem(Folder parent, string path)
  133. {
  134. BaseItem item = GetItemInternal(parent, path, File.GetAttributes(path));
  135. if (item != null)
  136. {
  137. var folder = item as Folder;
  138. if (folder != null)
  139. {
  140. FireCreateEventsRecursive(folder, parent);
  141. }
  142. else
  143. {
  144. OnItemCreated(item, parent);
  145. }
  146. }
  147. return item;
  148. }
  149. /// <summary>
  150. /// Resolves a path into a BaseItem
  151. /// </summary>
  152. private BaseItem GetItemInternal(Folder parent, string path, FileAttributes attributes)
  153. {
  154. if (!OnPreBeginResolvePath(parent, path, attributes))
  155. {
  156. return null;
  157. }
  158. IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren;
  159. // Gather child folder and files
  160. if (attributes.HasFlag(FileAttributes.Directory))
  161. {
  162. fileSystemChildren = Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair<string, FileAttributes>(f, File.GetAttributes(f)));
  163. bool isVirtualFolder = parent != null && parent.IsRoot;
  164. fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder);
  165. }
  166. else
  167. {
  168. fileSystemChildren = new KeyValuePair<string, FileAttributes>[] { };
  169. }
  170. ItemResolveEventArgs args = new ItemResolveEventArgs()
  171. {
  172. Path = path,
  173. FileAttributes = attributes,
  174. FileSystemChildren = fileSystemChildren,
  175. Parent = parent,
  176. Cancel = false
  177. };
  178. // Fire BeginResolvePath to see if anyone wants to cancel this operation
  179. if (!OnBeginResolvePath(args))
  180. {
  181. return null;
  182. }
  183. BaseItem item = ResolveItem(args);
  184. var folder = item as Folder;
  185. if (folder != null)
  186. {
  187. // If it's a folder look for child entities
  188. AttachChildren(folder, fileSystemChildren);
  189. }
  190. return item;
  191. }
  192. /// <summary>
  193. /// Finds child BaseItems for a given Folder
  194. /// </summary>
  195. private void AttachChildren(Folder folder, IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren)
  196. {
  197. List<BaseItem> baseItemChildren = new List<BaseItem>();
  198. int count = fileSystemChildren.Count();
  199. // Resolve the child folder paths into entities
  200. Parallel.For(0, count, i =>
  201. {
  202. KeyValuePair<string, FileAttributes> child = fileSystemChildren.ElementAt(i);
  203. BaseItem item = GetItemInternal(folder, child.Key, child.Value);
  204. if (item != null)
  205. {
  206. lock (baseItemChildren)
  207. {
  208. baseItemChildren.Add(item);
  209. }
  210. }
  211. });
  212. // Sort them
  213. folder.Children = baseItemChildren.OrderBy(f =>
  214. {
  215. return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
  216. }).ToArray();
  217. }
  218. /// <summary>
  219. /// Transforms shortcuts into their actual paths
  220. /// </summary>
  221. private List<KeyValuePair<string, FileAttributes>> FilterChildFileSystemEntries(IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren, bool flattenShortcuts)
  222. {
  223. List<KeyValuePair<string, FileAttributes>> returnFiles = new List<KeyValuePair<string, FileAttributes>>();
  224. // Loop through each file
  225. foreach (KeyValuePair<string, FileAttributes> file in fileSystemChildren)
  226. {
  227. // Folders
  228. if (file.Value.HasFlag(FileAttributes.Directory))
  229. {
  230. returnFiles.Add(file);
  231. }
  232. // If it's a shortcut, resolve it
  233. else if (Shortcut.IsShortcut(file.Key))
  234. {
  235. string newPath = Shortcut.ResolveShortcut(file.Key);
  236. FileAttributes newPathAttributes = File.GetAttributes(newPath);
  237. // Find out if the shortcut is pointing to a directory or file
  238. if (newPathAttributes.HasFlag(FileAttributes.Directory))
  239. {
  240. // If we're flattening then get the shortcut's children
  241. if (flattenShortcuts)
  242. {
  243. IEnumerable<KeyValuePair<string, FileAttributes>> newChildren = Directory.GetFileSystemEntries(newPath, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair<string, FileAttributes>(f, File.GetAttributes(f)));
  244. returnFiles.AddRange(FilterChildFileSystemEntries(newChildren, false));
  245. }
  246. else
  247. {
  248. returnFiles.Add(new KeyValuePair<string, FileAttributes>(newPath, newPathAttributes));
  249. }
  250. }
  251. else
  252. {
  253. returnFiles.Add(new KeyValuePair<string, FileAttributes>(newPath, newPathAttributes));
  254. }
  255. }
  256. else
  257. {
  258. returnFiles.Add(file);
  259. }
  260. }
  261. return returnFiles;
  262. }
  263. }
  264. }