Folder.cs 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371
  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
  24. {
  25. public static IUserManager UserManager { get; set; }
  26. public Folder()
  27. {
  28. LinkedChildren = new List<LinkedChild>();
  29. }
  30. /// <summary>
  31. /// Gets a value indicating whether this instance is folder.
  32. /// </summary>
  33. /// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
  34. [IgnoreDataMember]
  35. public override bool IsFolder
  36. {
  37. get
  38. {
  39. return true;
  40. }
  41. }
  42. /// <summary>
  43. /// Gets or sets a value indicating whether this instance is physical root.
  44. /// </summary>
  45. /// <value><c>true</c> if this instance is physical root; otherwise, <c>false</c>.</value>
  46. public bool IsPhysicalRoot { get; set; }
  47. /// <summary>
  48. /// Gets or sets a value indicating whether this instance is root.
  49. /// </summary>
  50. /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
  51. public bool IsRoot { get; set; }
  52. /// <summary>
  53. /// Gets a value indicating whether this instance is virtual folder.
  54. /// </summary>
  55. /// <value><c>true</c> if this instance is virtual folder; otherwise, <c>false</c>.</value>
  56. [IgnoreDataMember]
  57. public virtual bool IsVirtualFolder
  58. {
  59. get
  60. {
  61. return false;
  62. }
  63. }
  64. public virtual List<LinkedChild> LinkedChildren { get; set; }
  65. protected virtual bool SupportsShortcutChildren
  66. {
  67. get { return true; }
  68. }
  69. /// <summary>
  70. /// Adds the child.
  71. /// </summary>
  72. /// <param name="item">The item.</param>
  73. /// <param name="cancellationToken">The cancellation token.</param>
  74. /// <returns>Task.</returns>
  75. /// <exception cref="System.InvalidOperationException">Unable to add + item.Name</exception>
  76. public async Task AddChild(BaseItem item, CancellationToken cancellationToken)
  77. {
  78. item.Parent = this;
  79. if (item.Id == Guid.Empty)
  80. {
  81. item.Id = item.Path.GetMBId(item.GetType());
  82. }
  83. if (ActualChildren.Any(i => i.Id == item.Id))
  84. {
  85. throw new ArgumentException(string.Format("A child with the Id {0} already exists.", item.Id));
  86. }
  87. if (item.DateCreated == DateTime.MinValue)
  88. {
  89. item.DateCreated = DateTime.UtcNow;
  90. }
  91. if (item.DateModified == DateTime.MinValue)
  92. {
  93. item.DateModified = DateTime.UtcNow;
  94. }
  95. AddChildInternal(item);
  96. await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
  97. await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
  98. }
  99. protected void AddChildrenInternal(IEnumerable<BaseItem> children)
  100. {
  101. lock (_childrenSyncLock)
  102. {
  103. var newChildren = ActualChildren.ToList();
  104. newChildren.AddRange(children);
  105. _children = newChildren;
  106. }
  107. }
  108. protected void AddChildInternal(BaseItem child)
  109. {
  110. lock (_childrenSyncLock)
  111. {
  112. var newChildren = ActualChildren.ToList();
  113. newChildren.Add(child);
  114. _children = newChildren;
  115. }
  116. }
  117. protected void RemoveChildrenInternal(IEnumerable<BaseItem> children)
  118. {
  119. lock (_childrenSyncLock)
  120. {
  121. _children = ActualChildren.Except(children).ToList();
  122. }
  123. }
  124. protected void ClearChildrenInternal()
  125. {
  126. lock (_childrenSyncLock)
  127. {
  128. _children = new List<BaseItem>();
  129. }
  130. }
  131. /// <summary>
  132. /// Never want folders to be blocked by "BlockNotRated"
  133. /// </summary>
  134. [IgnoreDataMember]
  135. public override string OfficialRatingForComparison
  136. {
  137. get
  138. {
  139. if (this is Series)
  140. {
  141. return base.OfficialRatingForComparison;
  142. }
  143. return !string.IsNullOrEmpty(base.OfficialRatingForComparison) ? base.OfficialRatingForComparison : "None";
  144. }
  145. }
  146. /// <summary>
  147. /// Removes the child.
  148. /// </summary>
  149. /// <param name="item">The item.</param>
  150. /// <param name="cancellationToken">The cancellation token.</param>
  151. /// <returns>Task.</returns>
  152. /// <exception cref="System.InvalidOperationException">Unable to remove + item.Name</exception>
  153. public Task RemoveChild(BaseItem item, CancellationToken cancellationToken)
  154. {
  155. RemoveChildrenInternal(new[] { item });
  156. item.Parent = null;
  157. LibraryManager.ReportItemRemoved(item);
  158. return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
  159. }
  160. /// <summary>
  161. /// Clears the children.
  162. /// </summary>
  163. /// <param name="cancellationToken">The cancellation token.</param>
  164. /// <returns>Task.</returns>
  165. public Task ClearChildren(CancellationToken cancellationToken)
  166. {
  167. var items = ActualChildren.ToList();
  168. ClearChildrenInternal();
  169. foreach (var item in items)
  170. {
  171. LibraryManager.ReportItemRemoved(item);
  172. }
  173. return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
  174. }
  175. #region Indexing
  176. /// <summary>
  177. /// The _index by options
  178. /// </summary>
  179. private Dictionary<string, Func<User, IEnumerable<BaseItem>>> _indexByOptions;
  180. /// <summary>
  181. /// Dictionary of index options - consists of a display value and an indexing function
  182. /// which takes User as a parameter and returns an IEnum of BaseItem
  183. /// </summary>
  184. /// <value>The index by options.</value>
  185. [IgnoreDataMember]
  186. public Dictionary<string, Func<User, IEnumerable<BaseItem>>> IndexByOptions
  187. {
  188. get { return _indexByOptions ?? (_indexByOptions = GetIndexByOptions()); }
  189. }
  190. /// <summary>
  191. /// Returns the valid set of index by options for this folder type.
  192. /// Override or extend to modify.
  193. /// </summary>
  194. /// <returns>Dictionary{System.StringFunc{UserIEnumerable{BaseItem}}}.</returns>
  195. protected virtual Dictionary<string, Func<User, IEnumerable<BaseItem>>> GetIndexByOptions()
  196. {
  197. return new Dictionary<string, Func<User, IEnumerable<BaseItem>>> {
  198. {LocalizedStrings.Instance.GetString("NoneDispPref"), null},
  199. {LocalizedStrings.Instance.GetString("PerformerDispPref"), GetIndexByPerformer},
  200. {LocalizedStrings.Instance.GetString("GenreDispPref"), GetIndexByGenre},
  201. {LocalizedStrings.Instance.GetString("DirectorDispPref"), GetIndexByDirector},
  202. {LocalizedStrings.Instance.GetString("YearDispPref"), GetIndexByYear},
  203. //{LocalizedStrings.Instance.GetString("OfficialRatingDispPref"), null},
  204. {LocalizedStrings.Instance.GetString("StudioDispPref"), GetIndexByStudio}
  205. };
  206. }
  207. /// <summary>
  208. /// Gets the index by actor.
  209. /// </summary>
  210. /// <param name="user">The user.</param>
  211. /// <returns>IEnumerable{BaseItem}.</returns>
  212. protected IEnumerable<BaseItem> GetIndexByPerformer(User user)
  213. {
  214. return GetIndexByPerson(user, new List<string> { PersonType.Actor, PersonType.GuestStar }, true, LocalizedStrings.Instance.GetString("PerformerDispPref"));
  215. }
  216. /// <summary>
  217. /// Gets the index by director.
  218. /// </summary>
  219. /// <param name="user">The user.</param>
  220. /// <returns>IEnumerable{BaseItem}.</returns>
  221. protected IEnumerable<BaseItem> GetIndexByDirector(User user)
  222. {
  223. return GetIndexByPerson(user, new List<string> { PersonType.Director }, false, LocalizedStrings.Instance.GetString("DirectorDispPref"));
  224. }
  225. /// <summary>
  226. /// Gets the index by person.
  227. /// </summary>
  228. /// <param name="user">The user.</param>
  229. /// <param name="personTypes">The person types we should match on</param>
  230. /// <param name="includeAudio">if set to <c>true</c> [include audio].</param>
  231. /// <param name="indexName">Name of the index.</param>
  232. /// <returns>IEnumerable{BaseItem}.</returns>
  233. private IEnumerable<BaseItem> GetIndexByPerson(User user, List<string> personTypes, bool includeAudio, string indexName)
  234. {
  235. // Even though this implementation means multiple iterations over the target list - it allows us to defer
  236. // the retrieval of the individual children for each index value until they are requested.
  237. using (new Profiler(indexName + " Index Build for " + Name, Logger))
  238. {
  239. // Put this in a local variable to avoid an implicitly captured closure
  240. var currentIndexName = indexName;
  241. var us = this;
  242. var recursiveChildren = GetRecursiveChildren(user).Where(i => i.IncludeInIndex).ToList();
  243. // Get the candidates, but handle audio separately
  244. var candidates = recursiveChildren.Where(i => i.AllPeople != null && !(i is Audio.Audio)).ToList();
  245. var indexFolders = candidates.AsParallel().SelectMany(i => i.AllPeople.Where(p => personTypes.Contains(p.Type))
  246. .Select(a => a.Name))
  247. .Distinct()
  248. .Select(i =>
  249. {
  250. try
  251. {
  252. return LibraryManager.GetPerson(i);
  253. }
  254. catch (IOException ex)
  255. {
  256. Logger.ErrorException("Error getting person {0}", ex, i);
  257. return null;
  258. }
  259. catch (AggregateException ex)
  260. {
  261. Logger.ErrorException("Error getting person {0}", ex, i);
  262. return null;
  263. }
  264. })
  265. .Where(i => i != null)
  266. .Select(a => new IndexFolder(us, a,
  267. candidates.Where(i => i.AllPeople.Any(p => personTypes.Contains(p.Type) && p.Name.Equals(a.Name, StringComparison.OrdinalIgnoreCase))
  268. ), currentIndexName)).AsEnumerable();
  269. if (includeAudio)
  270. {
  271. var songs = recursiveChildren.OfType<Audio.Audio>().ToList();
  272. indexFolders = songs.SelectMany(i => i.Artists)
  273. .Distinct(StringComparer.OrdinalIgnoreCase)
  274. .Select(i =>
  275. {
  276. try
  277. {
  278. return LibraryManager.GetArtist(i);
  279. }
  280. catch (IOException ex)
  281. {
  282. Logger.ErrorException("Error getting artist {0}", ex, i);
  283. return null;
  284. }
  285. catch (AggregateException ex)
  286. {
  287. Logger.ErrorException("Error getting artist {0}", ex, i);
  288. return null;
  289. }
  290. })
  291. .Where(i => i != null)
  292. .Select(a => new IndexFolder(us, a,
  293. songs.Where(i => i.Artists.Contains(a.Name, StringComparer.OrdinalIgnoreCase)
  294. ), currentIndexName)).Concat(indexFolders);
  295. }
  296. return indexFolders;
  297. }
  298. }
  299. /// <summary>
  300. /// Gets the index by studio.
  301. /// </summary>
  302. /// <param name="user">The user.</param>
  303. /// <returns>IEnumerable{BaseItem}.</returns>
  304. protected IEnumerable<BaseItem> GetIndexByStudio(User user)
  305. {
  306. // Even though this implementation means multiple iterations over the target list - it allows us to defer
  307. // the retrieval of the individual children for each index value until they are requested.
  308. using (new Profiler("Studio Index Build for " + Name, Logger))
  309. {
  310. var indexName = LocalizedStrings.Instance.GetString("StudioDispPref");
  311. var candidates = GetRecursiveChildren(user).Where(i => i.IncludeInIndex).ToList();
  312. return candidates.AsParallel().SelectMany(i => i.AllStudios)
  313. .Distinct()
  314. .Select(i =>
  315. {
  316. try
  317. {
  318. return LibraryManager.GetStudio(i);
  319. }
  320. catch (IOException ex)
  321. {
  322. Logger.ErrorException("Error getting studio {0}", ex, i);
  323. return null;
  324. }
  325. catch (AggregateException ex)
  326. {
  327. Logger.ErrorException("Error getting studio {0}", ex, i);
  328. return null;
  329. }
  330. })
  331. .Where(i => i != null)
  332. .Select(ndx => new IndexFolder(this, ndx, candidates.Where(i => i.AllStudios.Any(s => s.Equals(ndx.Name, StringComparison.OrdinalIgnoreCase))), indexName));
  333. }
  334. }
  335. /// <summary>
  336. /// Gets the index by genre.
  337. /// </summary>
  338. /// <param name="user">The user.</param>
  339. /// <returns>IEnumerable{BaseItem}.</returns>
  340. protected IEnumerable<BaseItem> GetIndexByGenre(User user)
  341. {
  342. // Even though this implementation means multiple iterations over the target list - it allows us to defer
  343. // the retrieval of the individual children for each index value until they are requested.
  344. using (new Profiler("Genre Index Build for " + Name, Logger))
  345. {
  346. var indexName = LocalizedStrings.Instance.GetString("GenreDispPref");
  347. //we need a copy of this so we don't double-recurse
  348. var candidates = GetRecursiveChildren(user).Where(i => i.IncludeInIndex).ToList();
  349. return candidates.AsParallel().SelectMany(i => i.AllGenres)
  350. .Distinct(StringComparer.OrdinalIgnoreCase)
  351. .Select(i =>
  352. {
  353. try
  354. {
  355. return LibraryManager.GetGenre(i);
  356. }
  357. catch (Exception ex)
  358. {
  359. Logger.ErrorException("Error getting genre {0}", ex, i);
  360. return null;
  361. }
  362. })
  363. .Where(i => i != null)
  364. .Select(genre => new IndexFolder(this, genre, candidates.Where(i => i.AllGenres.Any(g => g.Equals(genre.Name, StringComparison.OrdinalIgnoreCase))), indexName)
  365. );
  366. }
  367. }
  368. /// <summary>
  369. /// Gets the index by year.
  370. /// </summary>
  371. /// <param name="user">The user.</param>
  372. /// <returns>IEnumerable{BaseItem}.</returns>
  373. protected IEnumerable<BaseItem> GetIndexByYear(User user)
  374. {
  375. // Even though this implementation means multiple iterations over the target list - it allows us to defer
  376. // the retrieval of the individual children for each index value until they are requested.
  377. using (new Profiler("Production Year Index Build for " + Name, Logger))
  378. {
  379. var indexName = LocalizedStrings.Instance.GetString("YearDispPref");
  380. //we need a copy of this so we don't double-recurse
  381. var candidates = GetRecursiveChildren(user).Where(i => i.IncludeInIndex && i.ProductionYear.HasValue).ToList();
  382. return candidates.AsParallel().Select(i => i.ProductionYear.Value)
  383. .Distinct()
  384. .Select(i =>
  385. {
  386. try
  387. {
  388. return LibraryManager.GetYear(i);
  389. }
  390. catch (IOException ex)
  391. {
  392. Logger.ErrorException("Error getting year {0}", ex, i);
  393. return null;
  394. }
  395. catch (AggregateException ex)
  396. {
  397. Logger.ErrorException("Error getting year {0}", ex, i);
  398. return null;
  399. }
  400. })
  401. .Where(i => i != null)
  402. .Select(ndx => new IndexFolder(this, ndx, candidates.Where(i => i.ProductionYear == int.Parse(ndx.Name)), indexName));
  403. }
  404. }
  405. /// <summary>
  406. /// Returns the indexed children for this user from the cache. Caches them if not already there.
  407. /// </summary>
  408. /// <param name="user">The user.</param>
  409. /// <param name="indexBy">The index by.</param>
  410. /// <returns>IEnumerable{BaseItem}.</returns>
  411. private IEnumerable<BaseItem> GetIndexedChildren(User user, string indexBy)
  412. {
  413. List<BaseItem> result = null;
  414. var cacheKey = user.Name + indexBy;
  415. if (IndexCache != null)
  416. {
  417. IndexCache.TryGetValue(cacheKey, out result);
  418. }
  419. if (result == null)
  420. {
  421. //not cached - cache it
  422. Func<User, IEnumerable<BaseItem>> indexing;
  423. IndexByOptions.TryGetValue(indexBy, out indexing);
  424. result = BuildIndex(indexBy, indexing, user);
  425. }
  426. return result;
  427. }
  428. /// <summary>
  429. /// Get the list of indexy by choices for this folder (localized).
  430. /// </summary>
  431. /// <value>The index by option strings.</value>
  432. [IgnoreDataMember]
  433. public IEnumerable<string> IndexByOptionStrings
  434. {
  435. get { return IndexByOptions.Keys; }
  436. }
  437. /// <summary>
  438. /// The index cache
  439. /// </summary>
  440. protected ConcurrentDictionary<string, List<BaseItem>> IndexCache;
  441. /// <summary>
  442. /// Builds the index.
  443. /// </summary>
  444. /// <param name="indexKey">The index key.</param>
  445. /// <param name="indexFunction">The index function.</param>
  446. /// <param name="user">The user.</param>
  447. /// <returns>List{BaseItem}.</returns>
  448. protected virtual List<BaseItem> BuildIndex(string indexKey, Func<User, IEnumerable<BaseItem>> indexFunction, User user)
  449. {
  450. if (IndexCache == null)
  451. {
  452. IndexCache = new ConcurrentDictionary<string, List<BaseItem>>();
  453. }
  454. return indexFunction != null
  455. ? IndexCache[user.Name + indexKey] = indexFunction(user).ToList()
  456. : null;
  457. }
  458. #endregion
  459. /// <summary>
  460. /// The children
  461. /// </summary>
  462. private IReadOnlyList<BaseItem> _children;
  463. /// <summary>
  464. /// The _children sync lock
  465. /// </summary>
  466. private readonly object _childrenSyncLock = new object();
  467. /// <summary>
  468. /// Gets or sets the actual children.
  469. /// </summary>
  470. /// <value>The actual children.</value>
  471. protected virtual IEnumerable<BaseItem> ActualChildren
  472. {
  473. get
  474. {
  475. return _children ?? (_children = LoadChildrenInternal());
  476. }
  477. }
  478. /// <summary>
  479. /// thread-safe access to the actual children of this folder - without regard to user
  480. /// </summary>
  481. /// <value>The children.</value>
  482. [IgnoreDataMember]
  483. public IEnumerable<BaseItem> Children
  484. {
  485. get { return ActualChildren; }
  486. }
  487. /// <summary>
  488. /// thread-safe access to all recursive children of this folder - without regard to user
  489. /// </summary>
  490. /// <value>The recursive children.</value>
  491. [IgnoreDataMember]
  492. public IEnumerable<BaseItem> RecursiveChildren
  493. {
  494. get { return GetRecursiveChildren(); }
  495. }
  496. private List<BaseItem> LoadChildrenInternal()
  497. {
  498. return LoadChildren().ToList();
  499. }
  500. /// <summary>
  501. /// Loads our children. Validation will occur externally.
  502. /// We want this sychronous.
  503. /// </summary>
  504. protected virtual IEnumerable<BaseItem> LoadChildren()
  505. {
  506. //just load our children from the repo - the library will be validated and maintained in other processes
  507. return GetCachedChildren();
  508. }
  509. /// <summary>
  510. /// Gets or sets the current validation cancellation token source.
  511. /// </summary>
  512. /// <value>The current validation cancellation token source.</value>
  513. private CancellationTokenSource CurrentValidationCancellationTokenSource { get; set; }
  514. /// <summary>
  515. /// Validates that the children of the folder still exist
  516. /// </summary>
  517. /// <param name="progress">The progress.</param>
  518. /// <param name="cancellationToken">The cancellation token.</param>
  519. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  520. /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
  521. /// <returns>Task.</returns>
  522. public async Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
  523. {
  524. cancellationToken.ThrowIfCancellationRequested();
  525. // Cancel the current validation, if any
  526. if (CurrentValidationCancellationTokenSource != null)
  527. {
  528. CurrentValidationCancellationTokenSource.Cancel();
  529. }
  530. // Create an inner cancellation token. This can cancel all validations from this level on down,
  531. // but nothing above this
  532. var innerCancellationTokenSource = new CancellationTokenSource();
  533. try
  534. {
  535. CurrentValidationCancellationTokenSource = innerCancellationTokenSource;
  536. var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken);
  537. await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, forceRefreshMetadata).ConfigureAwait(false);
  538. }
  539. catch (OperationCanceledException ex)
  540. {
  541. Logger.Info("ValidateChildren cancelled for " + Name);
  542. // If the outer cancelletion token in the cause for the cancellation, throw it
  543. if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken)
  544. {
  545. throw;
  546. }
  547. }
  548. finally
  549. {
  550. // Null out the token source
  551. if (CurrentValidationCancellationTokenSource == innerCancellationTokenSource)
  552. {
  553. CurrentValidationCancellationTokenSource = null;
  554. }
  555. innerCancellationTokenSource.Dispose();
  556. }
  557. }
  558. /// <summary>
  559. /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes
  560. /// ***Currently does not contain logic to maintain items that are unavailable in the file system***
  561. /// </summary>
  562. /// <param name="progress">The progress.</param>
  563. /// <param name="cancellationToken">The cancellation token.</param>
  564. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  565. /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
  566. /// <returns>Task.</returns>
  567. protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
  568. {
  569. var locationType = LocationType;
  570. // Nothing to do here
  571. if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
  572. {
  573. return;
  574. }
  575. cancellationToken.ThrowIfCancellationRequested();
  576. IEnumerable<BaseItem> nonCachedChildren;
  577. try
  578. {
  579. nonCachedChildren = GetNonCachedChildren();
  580. }
  581. catch (IOException ex)
  582. {
  583. nonCachedChildren = new BaseItem[] { };
  584. Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
  585. }
  586. if (nonCachedChildren == null) return; //nothing to validate
  587. progress.Report(5);
  588. //build a dictionary of the current children we have now by Id so we can compare quickly and easily
  589. var currentChildren = ActualChildren.ToDictionary(i => i.Id);
  590. //create a list for our validated children
  591. var validChildren = new List<Tuple<BaseItem, bool>>();
  592. var newItems = new List<BaseItem>();
  593. cancellationToken.ThrowIfCancellationRequested();
  594. foreach (var child in nonCachedChildren)
  595. {
  596. BaseItem currentChild;
  597. if (currentChildren.TryGetValue(child.Id, out currentChild))
  598. {
  599. currentChild.ResetResolveArgs(child.ResolveArgs);
  600. //existing item - check if it has changed
  601. if (currentChild.HasChanged(child))
  602. {
  603. EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
  604. validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
  605. }
  606. else
  607. {
  608. validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false));
  609. }
  610. currentChild.IsOffline = false;
  611. }
  612. else
  613. {
  614. //brand new item - needs to be added
  615. newItems.Add(child);
  616. validChildren.Add(new Tuple<BaseItem, bool>(child, true));
  617. }
  618. }
  619. // If any items were added or removed....
  620. if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
  621. {
  622. var newChildren = validChildren.Select(c => c.Item1).ToList();
  623. // That's all the new and changed ones - now see if there are any that are missing
  624. var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
  625. var actualRemovals = new List<BaseItem>();
  626. foreach (var item in itemsRemoved)
  627. {
  628. if (item.LocationType == LocationType.Virtual ||
  629. item.LocationType == LocationType.Remote)
  630. {
  631. // Don't remove these because there's no way to accurately validate them.
  632. continue;
  633. }
  634. if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
  635. {
  636. item.IsOffline = true;
  637. validChildren.Add(new Tuple<BaseItem, bool>(item, false));
  638. }
  639. else
  640. {
  641. item.IsOffline = false;
  642. actualRemovals.Add(item);
  643. }
  644. }
  645. if (actualRemovals.Count > 0)
  646. {
  647. RemoveChildrenInternal(actualRemovals);
  648. foreach (var item in actualRemovals)
  649. {
  650. LibraryManager.ReportItemRemoved(item);
  651. }
  652. }
  653. await LibraryManager.CreateItems(newItems, cancellationToken).ConfigureAwait(false);
  654. AddChildrenInternal(newItems);
  655. await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
  656. //force the indexes to rebuild next time
  657. if (IndexCache != null)
  658. {
  659. IndexCache.Clear();
  660. }
  661. }
  662. progress.Report(10);
  663. cancellationToken.ThrowIfCancellationRequested();
  664. await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
  665. progress.Report(100);
  666. }
  667. /// <summary>
  668. /// Refreshes the children.
  669. /// </summary>
  670. /// <param name="children">The children.</param>
  671. /// <param name="progress">The progress.</param>
  672. /// <param name="cancellationToken">The cancellation token.</param>
  673. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  674. /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
  675. /// <returns>Task.</returns>
  676. private async Task RefreshChildren(IList<Tuple<BaseItem, bool>> children, IProgress<double> progress, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false)
  677. {
  678. var list = children;
  679. var percentages = new Dictionary<Guid, double>(list.Count);
  680. var tasks = new List<Task>();
  681. foreach (var tuple in list)
  682. {
  683. if (tasks.Count > 10)
  684. {
  685. await Task.WhenAll(tasks).ConfigureAwait(false);
  686. }
  687. Tuple<BaseItem, bool> currentTuple = tuple;
  688. tasks.Add(Task.Run(async () =>
  689. {
  690. cancellationToken.ThrowIfCancellationRequested();
  691. var child = currentTuple.Item1;
  692. try
  693. {
  694. //refresh it
  695. await child.RefreshMetadata(cancellationToken, forceSave: currentTuple.Item2, forceRefresh: forceRefreshMetadata, resetResolveArgs: false).ConfigureAwait(false);
  696. }
  697. catch (IOException ex)
  698. {
  699. Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
  700. }
  701. // Refresh children if a folder and the item changed or recursive is set to true
  702. var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value));
  703. if (refreshChildren)
  704. {
  705. // Don't refresh children if explicitly set to false
  706. if (recursive.HasValue && recursive.Value == false)
  707. {
  708. refreshChildren = false;
  709. }
  710. }
  711. if (refreshChildren)
  712. {
  713. cancellationToken.ThrowIfCancellationRequested();
  714. var innerProgress = new ActionableProgress<double>();
  715. innerProgress.RegisterAction(p =>
  716. {
  717. lock (percentages)
  718. {
  719. percentages[child.Id] = p / 100;
  720. var percent = percentages.Values.Sum();
  721. percent /= list.Count;
  722. progress.Report((90 * percent) + 10);
  723. }
  724. });
  725. await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
  726. try
  727. {
  728. // Some folder providers are unable to refresh until children have been refreshed.
  729. await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false);
  730. }
  731. catch (IOException ex)
  732. {
  733. Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
  734. }
  735. }
  736. else
  737. {
  738. lock (percentages)
  739. {
  740. percentages[child.Id] = 1;
  741. var percent = percentages.Values.Sum();
  742. percent /= list.Count;
  743. progress.Report((90 * percent) + 10);
  744. }
  745. }
  746. }, cancellationToken));
  747. }
  748. cancellationToken.ThrowIfCancellationRequested();
  749. await Task.WhenAll(tasks).ConfigureAwait(false);
  750. }
  751. /// <summary>
  752. /// Determines whether the specified path is offline.
  753. /// </summary>
  754. /// <param name="path">The path.</param>
  755. /// <returns><c>true</c> if the specified path is offline; otherwise, <c>false</c>.</returns>
  756. private bool IsPathOffline(string path)
  757. {
  758. if (File.Exists(path))
  759. {
  760. return false;
  761. }
  762. var originalPath = path;
  763. // Depending on whether the path is local or unc, it may return either null or '\' at the top
  764. while (!string.IsNullOrEmpty(path) && path.Length > 1)
  765. {
  766. if (Directory.Exists(path))
  767. {
  768. return false;
  769. }
  770. path = System.IO.Path.GetDirectoryName(path);
  771. }
  772. if (ContainsPath(LibraryManager.GetDefaultVirtualFolders(), originalPath))
  773. {
  774. return true;
  775. }
  776. return UserManager.Users.Any(user => ContainsPath(LibraryManager.GetVirtualFolders(user), originalPath));
  777. }
  778. /// <summary>
  779. /// Determines whether the specified folders contains path.
  780. /// </summary>
  781. /// <param name="folders">The folders.</param>
  782. /// <param name="path">The path.</param>
  783. /// <returns><c>true</c> if the specified folders contains path; otherwise, <c>false</c>.</returns>
  784. private bool ContainsPath(IEnumerable<VirtualFolderInfo> folders, string path)
  785. {
  786. return folders.SelectMany(i => i.Locations).Any(i => ContainsPath(i, path));
  787. }
  788. private bool ContainsPath(string parent, string path)
  789. {
  790. return string.Equals(parent, path, StringComparison.OrdinalIgnoreCase) || path.IndexOf(parent.TrimEnd(System.IO.Path.DirectorySeparatorChar) + System.IO.Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase) != -1;
  791. }
  792. /// <summary>
  793. /// Get the children of this folder from the actual file system
  794. /// </summary>
  795. /// <returns>IEnumerable{BaseItem}.</returns>
  796. protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
  797. {
  798. var resolveArgs = ResolveArgs;
  799. if (resolveArgs == null || resolveArgs.FileSystemDictionary == null)
  800. {
  801. Logger.Error("ResolveArgs null for {0}", Path);
  802. }
  803. return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this);
  804. }
  805. /// <summary>
  806. /// Get our children from the repo - stubbed for now
  807. /// </summary>
  808. /// <returns>IEnumerable{BaseItem}.</returns>
  809. protected IEnumerable<BaseItem> GetCachedChildren()
  810. {
  811. return ItemRepository.GetChildren(Id).Select(RetrieveChild).Where(i => i != null);
  812. }
  813. /// <summary>
  814. /// Retrieves the child.
  815. /// </summary>
  816. /// <param name="child">The child.</param>
  817. /// <returns>BaseItem.</returns>
  818. private BaseItem RetrieveChild(Guid child)
  819. {
  820. var item = LibraryManager.RetrieveItem(child);
  821. if (item != null)
  822. {
  823. if (item is IByReferenceItem)
  824. {
  825. return LibraryManager.GetOrAddByReferenceItem(item);
  826. }
  827. item.Parent = this;
  828. }
  829. return item;
  830. }
  831. /// <summary>
  832. /// Gets allowed children of an item
  833. /// </summary>
  834. /// <param name="user">The user.</param>
  835. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  836. /// <param name="indexBy">The index by.</param>
  837. /// <returns>IEnumerable{BaseItem}.</returns>
  838. /// <exception cref="System.ArgumentNullException"></exception>
  839. public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren, string indexBy = null)
  840. {
  841. if (user == null)
  842. {
  843. throw new ArgumentNullException();
  844. }
  845. //the true root should return our users root folder children
  846. if (IsPhysicalRoot) return user.RootFolder.GetChildren(user, includeLinkedChildren, indexBy);
  847. IEnumerable<BaseItem> result = null;
  848. if (!string.IsNullOrEmpty(indexBy))
  849. {
  850. result = GetIndexedChildren(user, indexBy);
  851. }
  852. if (result != null)
  853. {
  854. return result;
  855. }
  856. var list = new List<BaseItem>();
  857. AddChildrenToList(user, includeLinkedChildren, list, false, null);
  858. return list;
  859. }
  860. /// <summary>
  861. /// Adds the children to list.
  862. /// </summary>
  863. /// <param name="user">The user.</param>
  864. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  865. /// <param name="list">The list.</param>
  866. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  867. /// <param name="filter">The filter.</param>
  868. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  869. private bool AddChildrenToList(User user, bool includeLinkedChildren, List<BaseItem> list, bool recursive, Func<BaseItem, bool> filter)
  870. {
  871. var hasLinkedChildren = false;
  872. foreach (var child in Children)
  873. {
  874. if (child.IsVisible(user))
  875. {
  876. if (filter == null || filter(child))
  877. {
  878. list.Add(child);
  879. }
  880. }
  881. if (recursive && child.IsFolder)
  882. {
  883. var folder = (Folder)child;
  884. if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter))
  885. {
  886. hasLinkedChildren = true;
  887. }
  888. }
  889. }
  890. if (includeLinkedChildren)
  891. {
  892. foreach (var child in GetLinkedChildren())
  893. {
  894. if (filter != null && !filter(child))
  895. {
  896. continue;
  897. }
  898. if (child.IsVisible(user))
  899. {
  900. hasLinkedChildren = true;
  901. list.Add(child);
  902. }
  903. }
  904. }
  905. return hasLinkedChildren;
  906. }
  907. /// <summary>
  908. /// Gets allowed recursive children of an item
  909. /// </summary>
  910. /// <param name="user">The user.</param>
  911. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  912. /// <returns>IEnumerable{BaseItem}.</returns>
  913. /// <exception cref="System.ArgumentNullException"></exception>
  914. public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
  915. {
  916. return GetRecursiveChildren(user, null, includeLinkedChildren);
  917. }
  918. /// <summary>
  919. /// Gets the recursive children.
  920. /// </summary>
  921. /// <param name="user">The user.</param>
  922. /// <param name="filter">The filter.</param>
  923. /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
  924. /// <returns>IList{BaseItem}.</returns>
  925. /// <exception cref="System.ArgumentNullException"></exception>
  926. public IList<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter, bool includeLinkedChildren = true)
  927. {
  928. if (user == null)
  929. {
  930. throw new ArgumentNullException("user");
  931. }
  932. var list = new List<BaseItem>();
  933. var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, true, filter);
  934. return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list;
  935. }
  936. /// <summary>
  937. /// Gets the recursive children.
  938. /// </summary>
  939. /// <returns>IList{BaseItem}.</returns>
  940. public IList<BaseItem> GetRecursiveChildren()
  941. {
  942. return GetRecursiveChildren(i => true);
  943. }
  944. /// <summary>
  945. /// Gets the recursive children.
  946. /// </summary>
  947. /// <param name="filter">The filter.</param>
  948. /// <returns>IEnumerable{BaseItem}.</returns>
  949. public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter)
  950. {
  951. var list = new List<BaseItem>();
  952. AddChildrenToList(list, true, filter);
  953. return list;
  954. }
  955. /// <summary>
  956. /// Adds the children to list.
  957. /// </summary>
  958. /// <param name="list">The list.</param>
  959. /// <param name="recursive">if set to <c>true</c> [recursive].</param>
  960. /// <param name="filter">The filter.</param>
  961. private void AddChildrenToList(List<BaseItem> list, bool recursive, Func<BaseItem, bool> filter)
  962. {
  963. foreach (var child in Children)
  964. {
  965. if (filter == null || filter(child))
  966. {
  967. list.Add(child);
  968. }
  969. if (recursive && child.IsFolder)
  970. {
  971. var folder = (Folder)child;
  972. folder.AddChildrenToList(list, true, filter);
  973. }
  974. }
  975. }
  976. /// <summary>
  977. /// Gets the linked children.
  978. /// </summary>
  979. /// <returns>IEnumerable{BaseItem}.</returns>
  980. public IEnumerable<BaseItem> GetLinkedChildren()
  981. {
  982. return LinkedChildren
  983. .Select(GetLinkedChild)
  984. .Where(i => i != null);
  985. }
  986. /// <summary>
  987. /// Gets the linked child.
  988. /// </summary>
  989. /// <param name="info">The info.</param>
  990. /// <returns>BaseItem.</returns>
  991. private BaseItem GetLinkedChild(LinkedChild info)
  992. {
  993. if (string.IsNullOrEmpty(info.Path))
  994. {
  995. throw new ArgumentException("Encountered linked child with empty path.");
  996. }
  997. BaseItem item = null;
  998. // First get using the cached Id
  999. if (info.ItemId != Guid.Empty)
  1000. {
  1001. item = LibraryManager.GetItemById(info.ItemId);
  1002. }
  1003. // If still null, search by path
  1004. if (item == null)
  1005. {
  1006. item = LibraryManager.RootFolder.FindByPath(info.Path);
  1007. }
  1008. // If still null, log
  1009. if (item == null)
  1010. {
  1011. Logger.Warn("Unable to find linked item at {0}", info.Path);
  1012. }
  1013. else
  1014. {
  1015. // Cache the id for next time
  1016. info.ItemId = item.Id;
  1017. }
  1018. return item;
  1019. }
  1020. public override async Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true, bool resetResolveArgs = true)
  1021. {
  1022. var changed = await base.RefreshMetadata(cancellationToken, forceSave, forceRefresh, allowSlowProviders, resetResolveArgs).ConfigureAwait(false);
  1023. return changed || (SupportsShortcutChildren && LocationType == LocationType.FileSystem && RefreshLinkedChildren());
  1024. }
  1025. /// <summary>
  1026. /// Refreshes the linked children.
  1027. /// </summary>
  1028. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  1029. private bool RefreshLinkedChildren()
  1030. {
  1031. ItemResolveArgs resolveArgs;
  1032. try
  1033. {
  1034. resolveArgs = ResolveArgs;
  1035. if (!resolveArgs.IsDirectory)
  1036. {
  1037. return false;
  1038. }
  1039. }
  1040. catch (IOException ex)
  1041. {
  1042. Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
  1043. return false;
  1044. }
  1045. var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
  1046. var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
  1047. var newShortcutLinks = resolveArgs.FileSystemChildren
  1048. .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
  1049. .Select(i =>
  1050. {
  1051. try
  1052. {
  1053. Logger.Debug("Found shortcut at {0}", i.FullName);
  1054. var resolvedPath = FileSystem.ResolveShortcut(i.FullName);
  1055. if (!string.IsNullOrEmpty(resolvedPath))
  1056. {
  1057. return new LinkedChild
  1058. {
  1059. Path = resolvedPath,
  1060. Type = LinkedChildType.Shortcut
  1061. };
  1062. }
  1063. Logger.Error("Error resolving shortcut {0}", i.FullName);
  1064. return null;
  1065. }
  1066. catch (IOException ex)
  1067. {
  1068. Logger.ErrorException("Error resolving shortcut {0}", ex, i.FullName);
  1069. return null;
  1070. }
  1071. })
  1072. .Where(i => i != null)
  1073. .ToList();
  1074. if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer()))
  1075. {
  1076. Logger.Info("Shortcut links have changed for {0}", Path);
  1077. newShortcutLinks.AddRange(currentManualLinks);
  1078. LinkedChildren = newShortcutLinks;
  1079. return true;
  1080. }
  1081. return false;
  1082. }
  1083. /// <summary>
  1084. /// Folders need to validate and refresh
  1085. /// </summary>
  1086. /// <returns>Task.</returns>
  1087. public override async Task ChangedExternally()
  1088. {
  1089. await base.ChangedExternally().ConfigureAwait(false);
  1090. var progress = new Progress<double>();
  1091. await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false);
  1092. }
  1093. /// <summary>
  1094. /// Marks the played.
  1095. /// </summary>
  1096. /// <param name="user">The user.</param>
  1097. /// <param name="datePlayed">The date played.</param>
  1098. /// <param name="userManager">The user manager.</param>
  1099. /// <returns>Task.</returns>
  1100. public override async Task MarkPlayed(User user, DateTime? datePlayed, IUserDataManager userManager)
  1101. {
  1102. // Sweep through recursively and update status
  1103. var tasks = GetRecursiveChildren(user, true).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual).Select(c => c.MarkPlayed(user, datePlayed, userManager));
  1104. await Task.WhenAll(tasks).ConfigureAwait(false);
  1105. }
  1106. /// <summary>
  1107. /// Marks the unplayed.
  1108. /// </summary>
  1109. /// <param name="user">The user.</param>
  1110. /// <param name="userManager">The user manager.</param>
  1111. /// <returns>Task.</returns>
  1112. public override async Task MarkUnplayed(User user, IUserDataManager userManager)
  1113. {
  1114. // Sweep through recursively and update status
  1115. var tasks = GetRecursiveChildren(user, true).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual).Select(c => c.MarkUnplayed(user, userManager));
  1116. await Task.WhenAll(tasks).ConfigureAwait(false);
  1117. }
  1118. /// <summary>
  1119. /// Finds an item by path, recursively
  1120. /// </summary>
  1121. /// <param name="path">The path.</param>
  1122. /// <returns>BaseItem.</returns>
  1123. /// <exception cref="System.ArgumentNullException"></exception>
  1124. public BaseItem FindByPath(string path)
  1125. {
  1126. if (string.IsNullOrEmpty(path))
  1127. {
  1128. throw new ArgumentNullException();
  1129. }
  1130. try
  1131. {
  1132. var locationType = LocationType;
  1133. if (locationType == LocationType.Remote && string.Equals(Path, path, StringComparison.OrdinalIgnoreCase))
  1134. {
  1135. return this;
  1136. }
  1137. if (locationType != LocationType.Virtual && ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
  1138. {
  1139. return this;
  1140. }
  1141. }
  1142. catch (IOException ex)
  1143. {
  1144. Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
  1145. }
  1146. //this should be functionally equivilent to what was here since it is IEnum and works on a thread-safe copy
  1147. return RecursiveChildren.Where(i => i.LocationType != LocationType.Virtual).FirstOrDefault(i =>
  1148. {
  1149. if (i.LocationType == LocationType.Remote)
  1150. {
  1151. return string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase);
  1152. }
  1153. try
  1154. {
  1155. return i.ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase);
  1156. }
  1157. catch (IOException ex)
  1158. {
  1159. Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
  1160. return false;
  1161. }
  1162. });
  1163. }
  1164. }
  1165. }