Folder.cs 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.Progress;
  3. using MediaBrowser.Controller.Entities.TV;
  4. using MediaBrowser.Controller.Library;
  5. using MediaBrowser.Controller.Localization;
  6. using MediaBrowser.Controller.Resolvers;
  7. using MediaBrowser.Model.Entities;
  8. using MoreLinq;
  9. using System;
  10. using System.Collections;
  11. using System.Collections.Concurrent;
  12. using System.Collections.Generic;
  13. using System.IO;
  14. using System.Linq;
  15. using System.Runtime.Serialization;
  16. using System.Threading;
  17. using System.Threading.Tasks;
  18. namespace MediaBrowser.Controller.Entities
  19. {
  20. /// <summary>
  21. /// Class Folder
  22. /// </summary>
  23. public class Folder : BaseItem, IHasThemeMedia
  24. {
  25. public static IUserManager UserManager { get; set; }
  26. public List<Guid> ThemeSongIds { get; set; }
  27. public List<Guid> ThemeVideoIds { get; set; }
  28. public Folder()
  29. {
  30. LinkedChildren = new List<LinkedChild>();
  31. ThemeSongIds = new List<Guid>();
  32. ThemeVideoIds = new List<Guid>();
  33. }
  34. /// <summary>
  35. /// Gets a value indicating whether this instance is folder.
  36. /// </summary>
  37. /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
  38. [IgnoreDataMember]
  39. public override bool IsFolder
  40. {
  41. get
  42. {
  43. return true;
  44. }
  45. }
  46. /// <summary>
  47. /// Gets or sets a value indicating whether this instance is physical root.
  48. /// </summary>
  49. /// <value><c>true</c> if this instance is physical root; otherwise, <c>false</c>.</value>
  50. public bool IsPhysicalRoot { get; set; }
  51. /// <summary>
  52. /// Gets or sets a value indicating whether this instance is root.
  53. /// </summary>
  54. /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
  55. public bool IsRoot { get; set; }
  56. /// <summary>
  57. /// Gets a value indicating whether this instance is virtual folder.
  58. /// </summary>
  59. /// <value><c>true</c> if this instance is virtual folder; otherwise, <c>false</c>.</value>
  60. [IgnoreDataMember]
  61. public virtual bool IsVirtualFolder
  62. {
  63. get
  64. {
  65. return false;
  66. }
  67. }
  68. public virtual List<LinkedChild> LinkedChildren { get; set; }
  69. protected virtual bool SupportsShortcutChildren
  70. {
  71. get { return true; }
  72. }
  73. /// <summary>
  74. /// Adds the child.
  75. /// </summary>
  76. /// <param name="item">The item.</param>
  77. /// <param name="cancellationToken">The cancellation token.</param>
  78. /// <returns>Task.</returns>
  79. /// <exception cref="System.InvalidOperationException">Unable to add + item.Name</exception>
  80. public async Task AddChild(BaseItem item, CancellationToken cancellationToken)
  81. {
  82. item.Parent = this;
  83. if (item.Id == Guid.Empty)
  84. {
  85. item.Id = item.Path.GetMBId(item.GetType());
  86. }
  87. if (ActualChildren.Any(i => i.Id == item.Id))
  88. {
  89. throw new ArgumentException(string.Format("A child with the Id {0} already exists.", item.Id));
  90. }
  91. if (item.DateCreated == DateTime.MinValue)
  92. {
  93. item.DateCreated = DateTime.UtcNow;
  94. }
  95. if (item.DateModified == DateTime.MinValue)
  96. {
  97. item.DateModified = DateTime.UtcNow;
  98. }
  99. AddChildInternal(item);
  100. await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  101. await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
  102. }
  103. protected void AddChildrenInternal(IEnumerable<BaseItem> children)
  104. {
  105. lock (_childrenSyncLock)
  106. {
  107. var newChildren = ActualChildren.ToList();
  108. newChildren.AddRange(children);
  109. _children = newChildren;
  110. }
  111. }
  112. protected void AddChildInternal(BaseItem child)
  113. {
  114. lock (_childrenSyncLock)
  115. {
  116. var newChildren = ActualChildren.ToList();
  117. newChildren.Add(child);
  118. _children = newChildren;
  119. }
  120. }
  121. protected void RemoveChildrenInternal(IEnumerable<BaseItem> children)
  122. {
  123. lock (_childrenSyncLock)
  124. {
  125. _children = ActualChildren.Except(children).ToList();
  126. }
  127. }
  128. protected void ClearChildrenInternal()
  129. {
  130. lock (_childrenSyncLock)
  131. {
  132. _children = new List<BaseItem>();
  133. }
  134. }
  135. /// <summary>
  136. /// Never want folders to be blocked by "BlockNotRated"
  137. /// </summary>
  138. [IgnoreDataMember]
  139. public override string OfficialRatingForComparison
  140. {
  141. get
  142. {
  143. if (this is Series)
  144. {
  145. return base.OfficialRatingForComparison;
  146. }
  147. return !string.IsNullOrEmpty(base.OfficialRatingForComparison) ? base.OfficialRatingForComparison : "None";
  148. }
  149. }
  150. /// <summary>
  151. /// Removes the child.
  152. /// </summary>
  153. /// <param name="item">The item.</param>
  154. /// <param name="cancellationToken">The cancellation token.</param>
  155. /// <returns>Task.</returns>
  156. /// <exception cref="System.InvalidOperationException">Unable to remove + item.Name</exception>
  157. public Task RemoveChild(BaseItem item, CancellationToken cancellationToken)
  158. {
  159. RemoveChildrenInternal(new[] { item });
  160. item.Parent = null;
  161. LibraryManager.ReportItemRemoved(item);
  162. return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
  163. }
  164. /// <summary>
  165. /// Clears the children.
  166. /// </summary>
  167. /// <param name="cancellationToken">The cancellation token.</param>
  168. /// <returns>Task.</returns>
  169. public Task ClearChildren(CancellationToken cancellationToken)
  170. {
  171. var items = ActualChildren.ToList();
  172. ClearChildrenInternal();
  173. foreach (var item in items)
  174. {
  175. LibraryManager.ReportItemRemoved(item);
  176. }
  177. return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
  178. }
  179. #region Indexing
  180. /// <summary>
  181. /// Returns the valid set of index by options for this folder type.
  182. /// Override or extend to modify.
  183. /// </summary>
  184. /// <returns>Dictionary{System.StringFunc{UserIEnumerable{BaseItem}}}.</returns>
  185. protected virtual IEnumerable<string> GetIndexByOptions()
  186. {
  187. return new List<string> {
  188. {LocalizedStrings.Instance.GetString("NoneDispPref")},
  189. {LocalizedStrings.Instance.GetString("PerformerDispPref")},
  190. {LocalizedStrings.Instance.GetString("GenreDispPref")},
  191. {LocalizedStrings.Instance.GetString("DirectorDispPref")},
  192. {LocalizedStrings.Instance.GetString("YearDispPref")},
  193. //{LocalizedStrings.Instance.GetString("OfficialRatingDispPref"), null},
  194. {LocalizedStrings.Instance.GetString("StudioDispPref")}
  195. };
  196. }
  197. /// <summary>
  198. /// Get the list of indexy by choices for this folder (localized).
  199. /// </summary>
  200. /// <value>The index by option strings.</value>
  201. [IgnoreDataMember]
  202. public IEnumerable<string> IndexByOptionStrings
  203. {
  204. get { return GetIndexByOptions(); }
  205. }
  206. #endregion
  207. /// <summary>
  208. /// The children
  209. /// </summary>
  210. private IReadOnlyList<BaseItem> _children;
  211. /// <summary>
  212. /// The _children sync lock
  213. /// </summary>
  214. private readonly object _childrenSyncLock = new object();
  215. /// <summary>
  216. /// Gets or sets the actual children.
  217. /// </summary>
  218. /// <value>The actual children.</value>
  219. protected virtual IEnumerable<BaseItem> ActualChildren
  220. {
  221. get
  222. {
  223. return _children ?? (_children = LoadChildrenInternal());
  224. }
  225. }
  226. /// <summary>
  227. /// thread-safe access to the actual children of this folder - without regard to user
  228. /// </summary>
  229. /// <value>The children.</value>
  230. [IgnoreDataMember]
  231. public IEnumerable<BaseItem> Children
  232. {
  233. get { return ActualChildren; }
  234. }
  235. /// <summary>
  236. /// thread-safe access to all recursive children of this folder - without regard to user
  237. /// </summary>
  238. /// <value>The recursive children.</value>
  239. [IgnoreDataMember]
  240. public IEnumerable<BaseItem> RecursiveChildren
  241. {
  242. get { return GetRecursiveChildren(); }
  243. }
  244. private List<BaseItem> LoadChildrenInternal()
  245. {
  246. return LoadChildren().ToList();
  247. }
  248. /// <summary>
  249. /// Loads our children. Validation will occur externally.
  250. /// We want this sychronous.
  251. /// </summary>
  252. protected virtual IEnumerable<BaseItem> LoadChildren()
  253. {
  254. //just load our children from the repo - the library will be validated and maintained in other processes
  255. return GetCachedChildren();
  256. }
  257. /// <summary>
  258. /// Gets or sets the current validation cancellation token source.
  259. /// </summary>
  260. /// <value>The current validation cancellation token source.</value>
  261. private CancellationTokenSource CurrentValidationCancellationTokenSource { get; set; }
  262. /// <summary>
  263. /// Validates that the children of the folder still exist
  264. /// </summary>
  265. /// <param name="progress">The progress.</param>
  266. /// <param name="cancellationToken">The cancellation token.</param>
  267. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  268. /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
  269. /// <returns>Task.</returns>
  270. public async Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
  271. {
  272. cancellationToken.ThrowIfCancellationRequested();
  273. // Cancel the current validation, if any
  274. if (CurrentValidationCancellationTokenSource != null)
  275. {
  276. CurrentValidationCancellationTokenSource.Cancel();
  277. }
  278. // Create an inner cancellation token. This can cancel all validations from this level on down,
  279. // but nothing above this
  280. var innerCancellationTokenSource = new CancellationTokenSource();
  281. try
  282. {
  283. CurrentValidationCancellationTokenSource = innerCancellationTokenSource;
  284. var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken);
  285. await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, forceRefreshMetadata).ConfigureAwait(false);
  286. }
  287. catch (OperationCanceledException ex)
  288. {
  289. Logger.Info("ValidateChildren cancelled for " + Name);
  290. // If the outer cancelletion token in the cause for the cancellation, throw it
  291. if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken)
  292. {
  293. throw;
  294. }
  295. }
  296. finally
  297. {
  298. // Null out the token source
  299. if (CurrentValidationCancellationTokenSource == innerCancellationTokenSource)
  300. {
  301. CurrentValidationCancellationTokenSource = null;
  302. }
  303. innerCancellationTokenSource.Dispose();
  304. }
  305. }
  306. /// <summary>
  307. /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes
  308. /// ***Currently does not contain logic to maintain items that are unavailable in the file system***
  309. /// </summary>
  310. /// <param name="progress">The progress.</param>
  311. /// <param name="cancellationToken">The cancellation token.</param>
  312. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  313. /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
  314. /// <returns>Task.</returns>
  315. protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
  316. {
  317. var locationType = LocationType;
  318. // Nothing to do here
  319. if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
  320. {
  321. return;
  322. }
  323. cancellationToken.ThrowIfCancellationRequested();
  324. IEnumerable<BaseItem> nonCachedChildren;
  325. try
  326. {
  327. nonCachedChildren = GetNonCachedChildren();
  328. }
  329. catch (IOException ex)
  330. {
  331. nonCachedChildren = new BaseItem[] { };
  332. Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
  333. }
  334. if (nonCachedChildren == null) return; //nothing to validate
  335. progress.Report(5);
  336. //build a dictionary of the current children we have now by Id so we can compare quickly and easily
  337. var currentChildren = ActualChildren.ToDictionary(i => i.Id);
  338. //create a list for our validated children
  339. var validChildren = new List<Tuple<BaseItem, bool>>();
  340. var newItems = new List<BaseItem>();
  341. cancellationToken.ThrowIfCancellationRequested();
  342. foreach (var child in nonCachedChildren)
  343. {
  344. BaseItem currentChild;
  345. if (currentChildren.TryGetValue(child.Id, out currentChild))
  346. {
  347. currentChild.ResetResolveArgs(child.ResolveArgs);
  348. //existing item - check if it has changed
  349. if (currentChild.HasChanged(child))
  350. {
  351. var currentChildLocationType = currentChild.LocationType;
  352. if (currentChildLocationType != LocationType.Remote &&
  353. currentChildLocationType != LocationType.Virtual)
  354. {
  355. EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
  356. }
  357. validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
  358. }
  359. else
  360. {
  361. validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false));
  362. }
  363. currentChild.IsOffline = false;
  364. }
  365. else
  366. {
  367. //brand new item - needs to be added
  368. newItems.Add(child);
  369. validChildren.Add(new Tuple<BaseItem, bool>(child, true));
  370. }
  371. }
  372. // If any items were added or removed....
  373. if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
  374. {
  375. var newChildren = validChildren.Select(c => c.Item1).ToList();
  376. // That's all the new and changed ones - now see if there are any that are missing
  377. var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
  378. var actualRemovals = new List<BaseItem>();
  379. foreach (var item in itemsRemoved)
  380. {
  381. if (item.LocationType == LocationType.Virtual ||
  382. item.LocationType == LocationType.Remote)
  383. {
  384. // Don't remove these because there's no way to accurately validate them.
  385. continue;
  386. }
  387. if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
  388. {
  389. item.IsOffline = true;
  390. validChildren.Add(new Tuple<BaseItem, bool>(item, false));
  391. }
  392. else
  393. {
  394. item.IsOffline = false;
  395. actualRemovals.Add(item);
  396. }
  397. }
  398. if (actualRemovals.Count > 0)
  399. {
  400. RemoveChildrenInternal(actualRemovals);
  401. foreach (var item in actualRemovals)
  402. {
  403. LibraryManager.ReportItemRemoved(item);
  404. }
  405. }
  406. await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
  407. AddChildrenInternal(newItems);
  408. await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
  409. }
  410. progress.Report(10);
  411. cancellationToken.ThrowIfCancellationRequested();
  412. await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
  413. progress.Report(100);
  414. }
  415. /// <summary>
  416. /// Refreshes the children.
  417. /// </summary>
  418. /// <param name="children">The children.</param>
  419. /// <param name="progress">The progress.</param>
  420. /// <param name="cancellationToken">The cancellation token.</param>
  421. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  422. /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
  423. /// <returns>Task.</returns>
  424. private async Task RefreshChildren(IList<Tuple<BaseItem, bool>> children, IProgress<double> progress, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false)
  425. {
  426. var list = children;
  427. var percentages = new Dictionary<Guid, double>(list.Count);
  428. var tasks = new List<Task>();
  429. foreach (var tuple in list)
  430. {
  431. if (tasks.Count > 10)
  432. {
  433. await Task.WhenAll(tasks).ConfigureAwait(false);
  434. }
  435. Tuple<BaseItem, bool> currentTuple = tuple;
  436. tasks.Add(Task.Run(async () =>
  437. {
  438. cancellationToken.ThrowIfCancellationRequested();
  439. var child = currentTuple.Item1;
  440. try
  441. {
  442. //refresh it
  443. await child.RefreshMetadata(cancellationToken, forceSave: currentTuple.Item2, forceRefresh: forceRefreshMetadata, resetResolveArgs: false).ConfigureAwait(false);
  444. }
  445. catch (IOException ex)
  446. {
  447. Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
  448. }
  449. // Refresh children if a folder and the item changed or recursive is set to true
  450. var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value));
  451. if (refreshChildren)
  452. {
  453. // Don't refresh children if explicitly set to false
  454. if (recursive.HasValue && recursive.Value == false)
  455. {
  456. refreshChildren = false;
  457. }
  458. }
  459. if (refreshChildren)
  460. {
  461. cancellationToken.ThrowIfCancellationRequested();
  462. var innerProgress = new ActionableProgress<double>();
  463. innerProgress.RegisterAction(p =>
  464. {
  465. lock (percentages)
  466. {
  467. percentages[child.Id] = p / 100;
  468. var percent = percentages.Values.Sum();
  469. percent /= list.Count;
  470. progress.Report((90 * percent) + 10);
  471. }
  472. });
  473. await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
  474. try
  475. {
  476. // Some folder providers are unable to refresh until children have been refreshed.
  477. await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false);
  478. }
  479. catch (IOException ex)
  480. {
  481. Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
  482. }
  483. }
  484. else
  485. {
  486. lock (percentages)
  487. {
  488. percentages[child.Id] = 1;
  489. var percent = percentages.Values.Sum();
  490. percent /= list.Count;
  491. progress.Report((90 * percent) + 10);
  492. }
  493. }
  494. }, cancellationToken));
  495. }
  496. cancellationToken.ThrowIfCancellationRequested();
  497. await Task.WhenAll(tasks).ConfigureAwait(false);
  498. }
  499. /// <summary>
  500. /// Determines whether the specified path is offline.
  501. /// </summary>
  502. /// <param name="path">The path.</param>
  503. /// <returns><c>true</c> if the specified path is offline; otherwise, <c>false</c>.</returns>
  504. private bool IsPathOffline(string path)
  505. {
  506. if (File.Exists(path))
  507. {
  508. return false;
  509. }
  510. var originalPath = path;
  511. // Depending on whether the path is local or unc, it may return either null or '\' at the top
  512. while (!string.IsNullOrEmpty(path) && path.Length > 1)
  513. {
  514. if (Directory.Exists(path))
  515. {
  516. return false;
  517. }
  518. path = System.IO.Path.GetDirectoryName(path);
  519. }
  520. if (ContainsPath(LibraryManager.GetDefaultVirtualFolders(), originalPath))
  521. {
  522. return true;
  523. }
  524. return UserManager.Users.Any(user => ContainsPath(LibraryManager.GetVirtualFolders(user), originalPath));
  525. }
  526. /// <summary>
  527. /// Determines whether the specified folders contains path.
  528. /// </summary>
  529. /// <param name="folders">The folders.</param>
  530. /// <param name="path">The path.</param>
  531. /// <returns><c>true</c> if the specified folders contains path; otherwise, <c>false</c>.</returns>
  532. private bool ContainsPath(IEnumerable<VirtualFolderInfo> folders, string path)
  533. {
  534. return folders.SelectMany(i => i.Locations).Any(i => ContainsPath(i, path));
  535. }
  536. private bool ContainsPath(string parent, string path)
  537. {
  538. return string.Equals(parent, path, StringComparison.OrdinalIgnoreCase) || path.IndexOf(parent.TrimEnd(System.IO.Path.DirectorySeparatorChar) + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) != -1;
  539. }
  540. /// <summary>
  541. /// Get the children of this folder from the actual file system
  542. /// </summary>
  543. /// <returns>IEnumerable{BaseItem}.</returns>
  544. protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
  545. {
  546. var resolveArgs = ResolveArgs;
  547. if (resolveArgs == null || resolveArgs.FileSystemDictionary == null)
  548. {
  549. Logger.Error("ResolveArgs null for {0}", Path);
  550. }
  551. return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this);
  552. }
  553. /// <summary>
  554. /// Get our children from the repo - stubbed for now
  555. /// </summary>
  556. /// <returns>IEnumerable{BaseItem}.</returns>
  557. protected IEnumerable<BaseItem> GetCachedChildren()
  558. {
  559. return ItemRepository.GetChildren(Id).Select(RetrieveChild).Where(i => i != null);
  560. }
  561. /// <summary>
  562. /// Retrieves the child.
  563. /// </summary>
  564. /// <param name="child">The child.</param>
  565. /// <returns>BaseItem.</returns>
  566. private BaseItem RetrieveChild(Guid child)
  567. {
  568. var item = LibraryManager.RetrieveItem(child);
  569. if (item != null)
  570. {
  571. if (item is IByReferenceItem)
  572. {
  573. return LibraryManager.GetOrAddByReferenceItem(item);
  574. }
  575. item.Parent = this;
  576. }
  577. return item;
  578. }
  579. /// <summary>
  580. /// Gets allowed children of an item
  581. /// </summary>
  582. /// <param name="user">The user.</param>
  583. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  584. /// <returns>IEnumerable{BaseItem}.</returns>
  585. /// <exception cref="System.ArgumentNullException"></exception>
  586. public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
  587. {
  588. if (user == null)
  589. {
  590. throw new ArgumentNullException();
  591. }
  592. //the true root should return our users root folder children
  593. if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, includeLinkedChildren);
  594. var list = new List<BaseItem>();
  595. AddChildrenToList(user, includeLinkedChildren, list, false, null);
  596. return list;
  597. }
  598. /// <summary>
  599. /// Adds the children to list.
  600. /// </summary>
  601. /// <param name="user">The user.</param>
  602. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  603. /// <param name="list">The list.</param>
  604. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  605. /// <param name="filter">The filter.</param>
  606. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  607. private bool AddChildrenToList(User user, bool includeLinkedChildren, List<BaseItem> list, bool recursive, Func<BaseItem, bool> filter)
  608. {
  609. var hasLinkedChildren = false;
  610. foreach (var child in Children)
  611. {
  612. if (child.IsVisible(user))
  613. {
  614. if (filter == null || filter(child))
  615. {
  616. list.Add(child);
  617. }
  618. }
  619. if (recursive && child.IsFolder)
  620. {
  621. var folder = (Folder)child;
  622. if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter))
  623. {
  624. hasLinkedChildren = true;
  625. }
  626. }
  627. }
  628. if (includeLinkedChildren)
  629. {
  630. foreach (var child in GetLinkedChildren())
  631. {
  632. if (filter != null && !filter(child))
  633. {
  634. continue;
  635. }
  636. if (child.IsVisible(user))
  637. {
  638. hasLinkedChildren = true;
  639. list.Add(child);
  640. }
  641. }
  642. }
  643. return hasLinkedChildren;
  644. }
  645. /// <summary>
  646. /// Gets allowed recursive children of an item
  647. /// </summary>
  648. /// <param name="user">The user.</param>
  649. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  650. /// <returns>IEnumerable{BaseItem}.</returns>
  651. /// <exception cref="System.ArgumentNullException"></exception>
  652. public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
  653. {
  654. return GetRecursiveChildren(user, null, includeLinkedChildren);
  655. }
  656. /// <summary>
  657. /// Gets the recursive children.
  658. /// </summary>
  659. /// <param name="user">The user.</param>
  660. /// <param name="filter">The filter.</param>
  661. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  662. /// <returns>IList{BaseItem}.</returns>
  663. /// <exception cref="System.ArgumentNullException"></exception>
  664. public IList<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter, bool includeLinkedChildren = true)
  665. {
  666. if (user == null)
  667. {
  668. throw new ArgumentNullException("user");
  669. }
  670. var list = new List<BaseItem>();
  671. var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, filter);
  672. return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list;
  673. }
  674. /// <summary>
  675. /// Gets the recursive children.
  676. /// </summary>
  677. /// <returns>IList{BaseItem}.</returns>
  678. public IList<BaseItem> GetRecursiveChildren()
  679. {
  680. return GetRecursiveChildren(i => true);
  681. }
  682. /// <summary>
  683. /// Gets the recursive children.
  684. /// </summary>
  685. /// <param name="filter">The filter.</param>
  686. /// <returns>IEnumerable{BaseItem}.</returns>
  687. public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter)
  688. {
  689. var list = new List<BaseItem>();
  690. AddChildrenToList(list, true, filter);
  691. return list;
  692. }
  693. /// <summary>
  694. /// Adds the children to list.
  695. /// </summary>
  696. /// <param name="list">The list.</param>
  697. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  698. /// <param name="filter">The filter.</param>
  699. private void AddChildrenToList(List<BaseItem> list, bool recursive, Func<BaseItem, bool> filter)
  700. {
  701. foreach (var child in Children)
  702. {
  703. if (filter == null || filter(child))
  704. {
  705. list.Add(child);
  706. }
  707. if (recursive && child.IsFolder)
  708. {
  709. var folder = (Folder)child;
  710. folder.AddChildrenToList(list, true, filter);
  711. }
  712. }
  713. }
  714. /// <summary>
  715. /// Gets the linked children.
  716. /// </summary>
  717. /// <returns>IEnumerable{BaseItem}.</returns>
  718. public IEnumerable<BaseItem> GetLinkedChildren()
  719. {
  720. return LinkedChildren
  721. .Select(GetLinkedChild)
  722. .Where(i => i != null);
  723. }
  724. /// <summary>
  725. /// Gets the linked child.
  726. /// </summary>
  727. /// <param name="info">The info.</param>
  728. /// <returns>BaseItem.</returns>
  729. private BaseItem GetLinkedChild(LinkedChild info)
  730. {
  731. if (string.IsNullOrEmpty(info.Path))
  732. {
  733. throw new ArgumentException("Encountered linked child with empty path.");
  734. }
  735. BaseItem item = null;
  736. // First get using the cached Id
  737. if (info.ItemId != Guid.Empty)
  738. {
  739. item = LibraryManager.GetItemById(info.ItemId);
  740. }
  741. // If still null, search by path
  742. if (item == null)
  743. {
  744. item = LibraryManager.RootFolder.FindByPath(info.Path);
  745. }
  746. // If still null, log
  747. if (item == null)
  748. {
  749. Logger.Warn("Unable to find linked item at {0}", info.Path);
  750. }
  751. else
  752. {
  753. // Cache the id for next time
  754. info.ItemId = item.Id;
  755. }
  756. return item;
  757. }
  758. public override async Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true)
  759. {
  760. var changed = await base.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs).ConfigureAwait(false);
  761. return (SupportsShortcutChildren && LocationType == LocationType.FileSystem && RefreshLinkedChildren()) || changed;
  762. }
  763. /// <summary>
  764. /// Refreshes the linked children.
  765. /// </summary>
  766. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  767. private bool RefreshLinkedChildren()
  768. {
  769. ItemResolveArgs resolveArgs;
  770. try
  771. {
  772. resolveArgs = ResolveArgs;
  773. if (!resolveArgs.IsDirectory)
  774. {
  775. return false;
  776. }
  777. }
  778. catch (IOException ex)
  779. {
  780. Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
  781. return false;
  782. }
  783. var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
  784. var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
  785. var newShortcutLinks = resolveArgs.FileSystemChildren
  786. .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
  787. .Select(i =>
  788. {
  789. try
  790. {
  791. Logger.Debug("Found shortcut at {0}", i.FullName);
  792. var resolvedPath = FileSystem.ResolveShortcut(i.FullName);
  793. if (!string.IsNullOrEmpty(resolvedPath))
  794. {
  795. return new LinkedChild
  796. {
  797. Path = resolvedPath,
  798. Type = LinkedChildType.Shortcut
  799. };
  800. }
  801. Logger.Error("Error resolving shortcut {0}", i.FullName);
  802. return null;
  803. }
  804. catch (IOException ex)
  805. {
  806. Logger.ErrorException("Error resolving shortcut {0}", ex, i.FullName);
  807. return null;
  808. }
  809. })
  810. .Where(i => i != null)
  811. .ToList();
  812. if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer()))
  813. {
  814. Logger.Info("Shortcut links have changed for {0}", Path);
  815. newShortcutLinks.AddRange(currentManualLinks);
  816. LinkedChildren = newShortcutLinks;
  817. return true;
  818. }
  819. return false;
  820. }
  821. /// <summary>
  822. /// Folders need to validate and refresh
  823. /// </summary>
  824. /// <returns>Task.</returns>
  825. public override async Task ChangedExternally()
  826. {
  827. await base.ChangedExternally().ConfigureAwait(false);
  828. var progress = new Progress<double>();
  829. await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false);
  830. }
  831. /// <summary>
  832. /// Marks the played.
  833. /// </summary>
  834. /// <param name="user">The user.</param>
  835. /// <param name="datePlayed">The date played.</param>
  836. /// <param name="userManager">The user manager.</param>
  837. /// <returns>Task.</returns>
  838. public override async Task MarkPlayed(User user, DateTime? datePlayed, IUserDataManager userManager)
  839. {
  840. // Sweep through recursively and update status
  841. var tasks = GetRecursiveChildren(user, true).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual).Select(c => c.MarkPlayed(user, datePlayed, userManager));
  842. await Task.WhenAll(tasks).ConfigureAwait(false);
  843. }
  844. /// <summary>
  845. /// Marks the unplayed.
  846. /// </summary>
  847. /// <param name="user">The user.</param>
  848. /// <param name="userManager">The user manager.</param>
  849. /// <returns>Task.</returns>
  850. public override async Task MarkUnplayed(User user, IUserDataManager userManager)
  851. {
  852. // Sweep through recursively and update status
  853. var tasks = GetRecursiveChildren(user, true).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual).Select(c => c.MarkUnplayed(user, userManager));
  854. await Task.WhenAll(tasks).ConfigureAwait(false);
  855. }
  856. /// <summary>
  857. /// Finds an item by path, recursively
  858. /// </summary>
  859. /// <param name="path">The path.</param>
  860. /// <returns>BaseItem.</returns>
  861. /// <exception cref="System.ArgumentNullException"></exception>
  862. public BaseItem FindByPath(string path)
  863. {
  864. if (string.IsNullOrEmpty(path))
  865. {
  866. throw new ArgumentNullException();
  867. }
  868. try
  869. {
  870. var locationType = LocationType;
  871. if (locationType == LocationType.Remote && string.Equals(Path, path, StringComparison.OrdinalIgnoreCase))
  872. {
  873. return this;
  874. }
  875. if (locationType != LocationType.Virtual && ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
  876. {
  877. return this;
  878. }
  879. }
  880. catch (IOException ex)
  881. {
  882. Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
  883. }
  884. return RecursiveChildren.Where(i => i.LocationType != LocationType.Virtual).FirstOrDefault(i =>
  885. {
  886. try
  887. {
  888. if (string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase))
  889. {
  890. return true;
  891. }
  892. if (i.LocationType != LocationType.Remote)
  893. {
  894. if (i.ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
  895. {
  896. return true;
  897. }
  898. }
  899. return false;
  900. }
  901. catch (IOException ex)
  902. {
  903. Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
  904. return false;
  905. }
  906. });
  907. }
  908. }
  909. }