123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626 |
- using MediaBrowser.Model.Entities;
- using MediaBrowser.Controller.IO;
- using MediaBrowser.Controller.Library;
- using MediaBrowser.Common.Logging;
- using MediaBrowser.Controller.Resolvers;
- using System;
- using System.Threading.Tasks;
- using System.Collections.Generic;
- using System.Linq;
- namespace MediaBrowser.Controller.Entities
- {
- public class Folder : BaseItem
- {
- #region Events
- /// <summary>
- /// Fires whenever a validation routine updates our children. The added and removed children are properties of the args.
- /// *** Will fire asynchronously. ***
- /// </summary>
- public event EventHandler<ChildrenChangedEventArgs> ChildrenChanged;
- protected void OnChildrenChanged(ChildrenChangedEventArgs args)
- {
- if (ChildrenChanged != null)
- {
- Task.Run( () =>
- {
- ChildrenChanged(this, args);
- Kernel.Instance.OnLibraryChanged(args);
- });
- }
- }
- #endregion
- public IEnumerable<string> PhysicalLocations { get; set; }
- public override bool IsFolder
- {
- get
- {
- return true;
- }
- }
- public bool IsRoot { get; set; }
- public bool IsVirtualFolder
- {
- get
- {
- return Parent != null && Parent.IsRoot;
- }
- }
- protected object childLock = new object();
- protected List<BaseItem> children;
- protected virtual List<BaseItem> ActualChildren
- {
- get
- {
- if (children == null)
- {
- LoadChildren();
- }
- return children;
- }
- set
- {
- children = value;
- }
- }
- /// <summary>
- /// thread-safe access to the actual children of this folder - without regard to user
- /// </summary>
- public IEnumerable<BaseItem> Children
- {
- get
- {
- lock (childLock)
- return ActualChildren.ToList();
- }
- }
- /// <summary>
- /// thread-safe access to all recursive children of this folder - without regard to user
- /// </summary>
- public IEnumerable<BaseItem> RecursiveChildren
- {
- get
- {
- foreach (var item in Children)
- {
- yield return item;
- var subFolder = item as Folder;
- if (subFolder != null)
- {
- foreach (var subitem in subFolder.RecursiveChildren)
- {
- yield return subitem;
- }
- }
- }
- }
- }
-
- /// <summary>
- /// Loads and validates our children
- /// </summary>
- protected virtual void LoadChildren()
- {
- //first - load our children from the repo
- lock (childLock)
- children = GetCachedChildren();
- //then kick off a validation against the actual file system
- Task.Run(() => ValidateChildren());
- }
- protected bool ChildrenValidating = false;
- /// <summary>
- /// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes
- /// ***Currently does not contain logic to maintain items that are unavailable in the file system***
- /// </summary>
- /// <returns></returns>
- protected async virtual void ValidateChildren()
- {
- if (ChildrenValidating) return; //only ever want one of these going at once and don't want them to fire off in sequence so don't use lock
- ChildrenValidating = true;
- bool changed = false; //this will save us a little time at the end if nothing changes
- var changedArgs = new ChildrenChangedEventArgs(this);
- //get the current valid children from filesystem (or wherever)
- var nonCachedChildren = await GetNonCachedChildren();
- if (nonCachedChildren == null) return; //nothing to validate
- //build a dictionary of the current children we have now by Id so we can compare quickly and easily
- Dictionary<Guid, BaseItem> currentChildren;
- lock (childLock)
- currentChildren = ActualChildren.ToDictionary(i => i.Id);
-
- //create a list for our validated children
- var validChildren = new List<BaseItem>();
- //now traverse the valid children and find any changed or new items
- foreach (var child in nonCachedChildren)
- {
- BaseItem currentChild;
- currentChildren.TryGetValue(child.Id, out currentChild);
- if (currentChild == null)
- {
- //brand new item - needs to be added
- changed = true;
- changedArgs.ItemsAdded.Add(child);
- //refresh it
- child.RefreshMetadata();
- //Logger.LogInfo("New Item Added to Library: ("+child.GetType().Name+") "+ child.Name + " (" + child.Path + ")");
- //save it in repo...
- //and add it to our valid children
- validChildren.Add(child);
- //fire an added event...?
- //if it is a folder we need to validate its children as well
- Folder folder = child as Folder;
- if (folder != null)
- {
- folder.ValidateChildren();
- //probably need to refresh too...
- }
- }
- else
- {
- //existing item - check if it has changed
- if (currentChild.IsChanged(child))
- {
- changed = true;
- //update resolve args and refresh meta
- // Note - we are refreshing the existing child instead of the newly found one so the "Except" operation below
- // will identify this item as the same one
- currentChild.ResolveArgs = child.ResolveArgs;
- currentChild.RefreshMetadata();
- Logger.LogInfo("Item Changed: ("+currentChild.GetType().Name+") "+ currentChild.Name + " (" + currentChild.Path + ")");
- //save it in repo...
- validChildren.Add(currentChild);
- }
- else
- {
- //current child that didn't change - just put it in the valid children
- validChildren.Add(currentChild);
- }
- }
- }
- //that's all the new and changed ones - now see if there are any that are missing
- changedArgs.ItemsRemoved = currentChildren.Values.Except(validChildren);
- changed |= changedArgs.ItemsRemoved != null;
- //now, if anything changed - replace our children
- if (changed)
- {
- lock (childLock)
- ActualChildren = validChildren;
- //and save children in repo...
- //and fire event
- this.OnChildrenChanged(changedArgs);
- }
- ChildrenValidating = false;
- }
- /// <summary>
- /// Get the children of this folder from the actual file system
- /// </summary>
- /// <returns></returns>
- protected async virtual Task<IEnumerable<BaseItem>> GetNonCachedChildren()
- {
- ItemResolveEventArgs args = new ItemResolveEventArgs()
- {
- FileInfo = FileData.GetFileData(this.Path),
- Parent = this.Parent,
- Cancel = false,
- Path = this.Path
- };
- // Gather child folder and files
- if (args.IsDirectory)
- {
- args.FileSystemChildren = FileData.GetFileSystemEntries(this.Path, "*").ToArray();
- bool isVirtualFolder = Parent != null && Parent.IsRoot;
- args = FileSystemHelper.FilterChildFileSystemEntries(args, isVirtualFolder);
- }
- else
- {
- Logger.LogError("Folder has a path that is not a directory: " + this.Path);
- return null;
- }
- if (!EntityResolutionHelper.ShouldResolvePathContents(args))
- {
- return null;
- }
- return (await Task.WhenAll<BaseItem>(GetChildren(args.FileSystemChildren)).ConfigureAwait(false))
- .Where(i => i != null).OrderBy(f =>
- {
- return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
- });
- }
- /// <summary>
- /// Resolves a path into a BaseItem
- /// </summary>
- protected async Task<BaseItem> GetChild(string path, WIN32_FIND_DATA? fileInfo = null)
- {
- ItemResolveEventArgs args = new ItemResolveEventArgs()
- {
- FileInfo = fileInfo ?? FileData.GetFileData(path),
- Parent = this,
- Cancel = false,
- Path = path
- };
- args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
- args = FileSystemHelper.FilterChildFileSystemEntries(args, false);
- return Kernel.Instance.ResolveItem(args);
- }
- /// <summary>
- /// Finds child BaseItems for a given Folder
- /// </summary>
- protected Task<BaseItem>[] GetChildren(WIN32_FIND_DATA[] fileSystemChildren)
- {
- Task<BaseItem>[] tasks = new Task<BaseItem>[fileSystemChildren.Length];
- for (int i = 0; i < fileSystemChildren.Length; i++)
- {
- var child = fileSystemChildren[i];
- tasks[i] = GetChild(child.Path, child);
- }
- return tasks;
- }
- /// <summary>
- /// Get our children from the repo - stubbed for now
- /// </summary>
- /// <returns></returns>
- protected virtual List<BaseItem> GetCachedChildren()
- {
- return new List<BaseItem>();
- }
- /// <summary>
- /// Gets allowed children of an item
- /// </summary>
- public IEnumerable<BaseItem> GetChildren(User user)
- {
- lock(childLock)
- return ActualChildren.Where(c => c.IsParentalAllowed(user));
- }
- /// <summary>
- /// Gets allowed recursive children of an item
- /// </summary>
- public IEnumerable<BaseItem> GetRecursiveChildren(User user)
- {
- foreach (var item in GetChildren(user))
- {
- yield return item;
- var subFolder = item as Folder;
- if (subFolder != null)
- {
- foreach (var subitem in subFolder.GetRecursiveChildren(user))
- {
- yield return subitem;
- }
- }
- }
- }
- /// <summary>
- /// Since it can be slow to make all of these calculations at once, this method will provide a way to get them all back together
- /// </summary>
- public ItemSpecialCounts GetSpecialCounts(User user)
- {
- var counts = new ItemSpecialCounts();
- IEnumerable<BaseItem> recursiveChildren = GetRecursiveChildren(user);
- var recentlyAddedItems = GetRecentlyAddedItems(recursiveChildren, user);
- counts.RecentlyAddedItemCount = recentlyAddedItems.Count;
- counts.RecentlyAddedUnPlayedItemCount = GetRecentlyAddedUnplayedItems(recentlyAddedItems, user).Count;
- counts.InProgressItemCount = GetInProgressItems(recursiveChildren, user).Count;
- counts.PlayedPercentage = GetPlayedPercentage(recursiveChildren, user);
- return counts;
- }
- /// <summary>
- /// Finds all recursive items within a top-level parent that contain the given genre and are allowed for the current user
- /// </summary>
- public IEnumerable<BaseItem> GetItemsWithGenre(string genre, User user)
- {
- return GetRecursiveChildren(user).Where(f => f.Genres != null && f.Genres.Any(s => s.Equals(genre, StringComparison.OrdinalIgnoreCase)));
- }
- /// <summary>
- /// Finds all recursive items within a top-level parent that contain the given year and are allowed for the current user
- /// </summary>
- public IEnumerable<BaseItem> GetItemsWithYear(int year, User user)
- {
- return GetRecursiveChildren(user).Where(f => f.ProductionYear.HasValue && f.ProductionYear == year);
- }
- /// <summary>
- /// Finds all recursive items within a top-level parent that contain the given studio and are allowed for the current user
- /// </summary>
- public IEnumerable<BaseItem> GetItemsWithStudio(string studio, User user)
- {
- return GetRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase)));
- }
- /// <summary>
- /// Finds all recursive items within a top-level parent that the user has marked as a favorite
- /// </summary>
- public IEnumerable<BaseItem> GetFavoriteItems(User user)
- {
- return GetRecursiveChildren(user).Where(c =>
- {
- UserItemData data = c.GetUserData(user, false);
- if (data != null)
- {
- return data.IsFavorite;
- }
- return false;
- });
- }
- /// <summary>
- /// Finds all recursive items within a top-level parent that contain the given person and are allowed for the current user
- /// </summary>
- public IEnumerable<BaseItem> GetItemsWithPerson(string person, User user)
- {
- return GetRecursiveChildren(user).Where(c =>
- {
- if (c.People != null)
- {
- return c.People.ContainsKey(person);
- }
- return false;
- });
- }
- /// <summary>
- /// Finds all recursive items within a top-level parent that contain the given person and are allowed for the current user
- /// </summary>
- /// <param name="personType">Specify this to limit results to a specific PersonType</param>
- public IEnumerable<BaseItem> GetItemsWithPerson(string person, string personType, User user)
- {
- return GetRecursiveChildren(user).Where(c =>
- {
- if (c.People != null)
- {
- return c.People.ContainsKey(person) && c.People[person].Type.Equals(personType, StringComparison.OrdinalIgnoreCase);
- }
- return false;
- });
- }
- /// <summary>
- /// Gets all recently added items (recursive) within a folder, based on configuration and parental settings
- /// </summary>
- public List<BaseItem> GetRecentlyAddedItems(User user)
- {
- return GetRecentlyAddedItems(GetRecursiveChildren(user), user);
- }
- /// <summary>
- /// Gets all recently added unplayed items (recursive) within a folder, based on configuration and parental settings
- /// </summary>
- public List<BaseItem> GetRecentlyAddedUnplayedItems(User user)
- {
- return GetRecentlyAddedUnplayedItems(GetRecursiveChildren(user), user);
- }
- /// <summary>
- /// Gets all in-progress items (recursive) within a folder
- /// </summary>
- public List<BaseItem> GetInProgressItems(User user)
- {
- return GetInProgressItems(GetRecursiveChildren(user), user);
- }
- /// <summary>
- /// Takes a list of items and returns the ones that are recently added
- /// </summary>
- private static List<BaseItem> GetRecentlyAddedItems(IEnumerable<BaseItem> itemSet, User user)
- {
- var list = new List<BaseItem>();
- foreach (var item in itemSet)
- {
- if (!item.IsFolder && item.IsRecentlyAdded(user))
- {
- list.Add(item);
- }
- }
- return list;
- }
- /// <summary>
- /// Takes a list of items and returns the ones that are recently added and unplayed
- /// </summary>
- private static List<BaseItem> GetRecentlyAddedUnplayedItems(IEnumerable<BaseItem> itemSet, User user)
- {
- var list = new List<BaseItem>();
- foreach (var item in itemSet)
- {
- if (!item.IsFolder && item.IsRecentlyAdded(user))
- {
- var userdata = item.GetUserData(user, false);
- if (userdata == null || userdata.PlayCount == 0)
- {
- list.Add(item);
- }
- }
- }
- return list;
- }
- /// <summary>
- /// Takes a list of items and returns the ones that are in progress
- /// </summary>
- private static List<BaseItem> GetInProgressItems(IEnumerable<BaseItem> itemSet, User user)
- {
- var list = new List<BaseItem>();
- foreach (var item in itemSet)
- {
- if (!item.IsFolder)
- {
- var userdata = item.GetUserData(user, false);
- if (userdata != null && userdata.PlaybackPositionTicks > 0)
- {
- list.Add(item);
- }
- }
- }
- return list;
- }
- /// <summary>
- /// Gets the total played percentage for a set of items
- /// </summary>
- private static decimal GetPlayedPercentage(IEnumerable<BaseItem> itemSet, User user)
- {
- itemSet = itemSet.Where(i => !(i.IsFolder));
- decimal totalPercent = 0;
- int count = 0;
- foreach (BaseItem item in itemSet)
- {
- count++;
-
- UserItemData data = item.GetUserData(user, false);
- if (data == null)
- {
- continue;
- }
- if (data.PlayCount > 0)
- {
- totalPercent += 100;
- }
- else if (data.PlaybackPositionTicks > 0 && item.RunTimeTicks.HasValue)
- {
- decimal itemPercent = data.PlaybackPositionTicks;
- itemPercent /= item.RunTimeTicks.Value;
- totalPercent += itemPercent;
- }
- }
- if (count == 0)
- {
- return 0;
- }
- return totalPercent / count;
- }
- /// <summary>
- /// Marks the item as either played or unplayed
- /// </summary>
- public override void SetPlayedStatus(User user, bool wasPlayed)
- {
- base.SetPlayedStatus(user, wasPlayed);
- // Now sweep through recursively and update status
- foreach (BaseItem item in GetChildren(user))
- {
- item.SetPlayedStatus(user, wasPlayed);
- }
- }
- /// <summary>
- /// Finds an item by ID, recursively
- /// </summary>
- public override BaseItem FindItemById(Guid id)
- {
- var result = base.FindItemById(id);
- if (result != null)
- {
- return result;
- }
- foreach (BaseItem item in ActualChildren)
- {
- result = item.FindItemById(id);
- if (result != null)
- {
- return result;
- }
- }
- return null;
- }
- /// <summary>
- /// Finds an item by path, recursively
- /// </summary>
- public BaseItem FindByPath(string path)
- {
- if (Path.Equals(path, StringComparison.OrdinalIgnoreCase))
- {
- return this;
- }
- foreach (BaseItem item in ActualChildren)
- {
- var folder = item as Folder;
- if (folder != null)
- {
- var foundItem = folder.FindByPath(path);
- if (foundItem != null)
- {
- return foundItem;
- }
- }
- else if (item.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
- {
- return item;
- }
- }
- return null;
- }
- }
- }
|