ItemController.cs 11 KB

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