aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/Entities
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller/Entities')
-rw-r--r--MediaBrowser.Controller/Entities/AdultVideo.cs12
-rw-r--r--MediaBrowser.Controller/Entities/AggregateFolder.cs13
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs25
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs47
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs150
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicGenre.cs25
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs560
-rw-r--r--MediaBrowser.Controller/Entities/Book.cs22
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs17
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs283
-rw-r--r--MediaBrowser.Controller/Entities/Game.cs21
-rw-r--r--MediaBrowser.Controller/Entities/GameGenre.cs26
-rw-r--r--MediaBrowser.Controller/Entities/GameSystem.cs12
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs25
-rw-r--r--MediaBrowser.Controller/Entities/IHasImages.cs76
-rw-r--r--MediaBrowser.Controller/Entities/IHasMetadata.cs59
-rw-r--r--MediaBrowser.Controller/Entities/IHasScreenshots.cs8
-rw-r--r--MediaBrowser.Controller/Entities/IHasSeries.cs12
-rw-r--r--MediaBrowser.Controller/Entities/IMetadataContainer.cs19
-rw-r--r--MediaBrowser.Controller/Entities/ItemImageInfo.cs14
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs71
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs28
-rw-r--r--MediaBrowser.Controller/Entities/MusicVideo.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Person.cs35
-rw-r--r--MediaBrowser.Controller/Entities/Studio.cs25
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs109
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs68
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs8
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs17
-rw-r--r--MediaBrowser.Controller/Entities/User.cs35
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs21
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs29
-rw-r--r--MediaBrowser.Controller/Entities/Year.cs25
33 files changed, 1320 insertions, 585 deletions
diff --git a/MediaBrowser.Controller/Entities/AdultVideo.cs b/MediaBrowser.Controller/Entities/AdultVideo.cs
index 475d7bc54..fc7632152 100644
--- a/MediaBrowser.Controller/Entities/AdultVideo.cs
+++ b/MediaBrowser.Controller/Entities/AdultVideo.cs
@@ -1,7 +1,8 @@
-
+using System.Collections.Generic;
+
namespace MediaBrowser.Controller.Entities
{
- public class AdultVideo : Video, IHasPreferredMetadataLanguage
+ public class AdultVideo : Video, IHasPreferredMetadataLanguage, IHasTaglines
{
/// <summary>
/// Gets or sets the preferred metadata language.
@@ -14,5 +15,12 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value>The preferred metadata country code.</value>
public string PreferredMetadataCountryCode { get; set; }
+
+ public List<string> Taglines { get; set; }
+
+ public AdultVideo()
+ {
+ Taglines = new List<string>();
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index ef455846e..5cabe1cfe 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
+using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Entities
{
@@ -56,12 +57,12 @@ namespace MediaBrowser.Controller.Entities
public List<string> PhysicalLocationsList { get; set; }
- protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
+ protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
{
- return CreateResolveArgs().FileSystemChildren;
+ return CreateResolveArgs(directoryService).FileSystemChildren;
}
- private ItemResolveArgs CreateResolveArgs()
+ private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
{
var path = ContainingFolderPath;
@@ -80,7 +81,7 @@ namespace MediaBrowser.Controller.Entities
// When resolving the root, we need it's grandchildren (children of user views)
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
- var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
+ var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
// Need to remove subpaths that may have been resolved from shortcuts
// Example: if \\server\movies exists, then strip out \\server\movies\action
@@ -118,9 +119,9 @@ namespace MediaBrowser.Controller.Entities
/// Get the children of this folder from the actual file system
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
- protected override IEnumerable<BaseItem> GetNonCachedChildren()
+ protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
- return base.GetNonCachedChildren().Concat(_virtualChildren);
+ return base.GetNonCachedChildren(directoryService).Concat(_virtualChildren);
}
/// <summary>
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 6f4a0c4d2..836874db9 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Class Audio
/// </summary>
- public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres
+ public class Audio : BaseItem, IHasMediaStreams, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasLookupInfo<SongInfo>, IHasSeries
{
public Audio()
{
@@ -50,6 +51,15 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
+ [IgnoreDataMember]
+ public string SeriesName
+ {
+ get
+ {
+ return Album;
+ }
+ }
+
/// <summary>
/// Gets or sets the artist.
/// </summary>
@@ -127,5 +137,16 @@ namespace MediaBrowser.Controller.Entities.Audio
{
return config.BlockUnratedMusic;
}
+
+ public SongInfo GetLookupInfo()
+ {
+ var info = GetItemLookupInfo<SongInfo>();
+
+ info.AlbumArtist = AlbumArtist;
+ info.Album = Album;
+ info.Artists = Artists;
+
+ return info;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index b3bf0d2b6..51c8a8727 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
@@ -10,10 +11,10 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Class MusicAlbum
/// </summary>
- public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasTags
+ public class MusicAlbum : Folder, IHasAlbumArtist, IHasArtist, IHasMusicGenres, IHasTags, IHasLookupInfo<AlbumInfo>, IHasSeries
{
public List<Guid> SoundtrackIds { get; set; }
-
+
public MusicAlbum()
{
Artists = new List<string>();
@@ -21,6 +22,15 @@ namespace MediaBrowser.Controller.Entities.Audio
Tags = new List<string>();
}
+ [IgnoreDataMember]
+ public MusicArtist MusicArtist
+ {
+ get
+ {
+ return Parents.OfType<MusicArtist>().FirstOrDefault();
+ }
+ }
+
/// <summary>
/// Gets or sets the tags.
/// </summary>
@@ -40,6 +50,15 @@ namespace MediaBrowser.Controller.Entities.Audio
}
}
+ [IgnoreDataMember]
+ public string SeriesName
+ {
+ get
+ {
+ return AlbumArtist;
+ }
+ }
+
/// <summary>
/// Override this to true if class should be grouped under a container in indicies
/// The container class should be defined via IndexContainer
@@ -98,7 +117,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return "MusicAlbum-MusicBrainzReleaseGroup-" + id;
}
- id = this.GetProviderId(MetadataProviders.Musicbrainz);
+ id = this.GetProviderId(MetadataProviders.MusicBrainzAlbum);
if (!string.IsNullOrEmpty(id))
{
@@ -112,6 +131,26 @@ namespace MediaBrowser.Controller.Entities.Audio
{
return config.BlockUnratedMusic;
}
+
+ public AlbumInfo GetLookupInfo()
+ {
+ var id = GetItemLookupInfo<AlbumInfo>();
+
+ id.AlbumArtist = AlbumArtist;
+
+ var artist = Parents.OfType<MusicArtist>().FirstOrDefault();
+
+ if (artist != null)
+ {
+ id.ArtistProviderIds = artist.ProviderIds;
+ }
+
+ id.SongInfos = RecursiveChildren.OfType<Audio>()
+ .Select(i => i.GetLookupInfo())
+ .ToList();
+
+ return id;
+ }
}
public class MusicAlbumDisc : Folder
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index 9b4e3a736..2b5570a80 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -1,8 +1,11 @@
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Common.Progress;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
@@ -12,7 +15,7 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <summary>
/// Class MusicArtist
/// </summary>
- public class MusicArtist : Folder, IItemByName, IHasMusicGenres, IHasDualAccess, IHasTags, IHasProductionLocations
+ public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasTags, IHasProductionLocations, IHasLookupInfo<ArtistInfo>
{
[IgnoreDataMember]
public List<ItemByNameCounts> UserItemCountList { get; set; }
@@ -49,7 +52,7 @@ namespace MediaBrowser.Controller.Entities.Audio
}
private readonly Task _cachedTask = Task.FromResult(true);
- protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
+ protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
if (IsAccessedByName)
{
@@ -57,17 +60,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return _cachedTask;
}
- return base.ValidateChildrenInternal(progress, cancellationToken, recursive, forceRefreshMetadata);
- }
-
- public override string GetClientTypeName()
- {
- if (IsAccessedByName)
- {
- //return "Artist";
- }
-
- return base.GetClientTypeName();
+ return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService);
}
public MusicArtist()
@@ -87,13 +80,38 @@ namespace MediaBrowser.Controller.Entities.Audio
}
/// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
/// Gets the user data key.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>System.String.</returns>
private static string GetUserDataKey(MusicArtist item)
{
- var id = item.GetProviderId(MetadataProviders.Musicbrainz);
+ var id = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
if (!string.IsNullOrEmpty(id))
{
@@ -107,5 +125,107 @@ namespace MediaBrowser.Controller.Entities.Audio
{
return config.BlockUnratedMusic;
}
+
+ public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var items = RecursiveChildren.ToList();
+
+ var songs = items.OfType<Audio>().ToList();
+
+ var others = items.Except(songs).ToList();
+
+ var totalItems = songs.Count + others.Count;
+ var percentages = new Dictionary<Guid, double>(totalItems);
+
+ var tasks = new List<Task>();
+
+ // Refresh songs
+ foreach (var item in songs)
+ {
+ if (tasks.Count >= 2)
+ {
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ tasks.Clear();
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+ var innerProgress = new ActionableProgress<double>();
+
+ // Avoid implicitly captured closure
+ var currentChild = item;
+ innerProgress.RegisterAction(p =>
+ {
+ lock (percentages)
+ {
+ percentages[currentChild.Id] = p / 100;
+
+ var percent = percentages.Values.Sum();
+ percent /= totalItems;
+ percent *= 100;
+ progress.Report(percent);
+ }
+ });
+
+ tasks.Add(RefreshItem(item, refreshOptions, innerProgress, cancellationToken));
+ }
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ tasks.Clear();
+
+ // Refresh current item
+ await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+
+ // Refresh all non-songs
+ foreach (var item in others)
+ {
+ if (tasks.Count > 3)
+ {
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ tasks.Clear();
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+ var innerProgress = new ActionableProgress<double>();
+
+ // Avoid implicitly captured closure
+ var currentChild = item;
+ innerProgress.RegisterAction(p =>
+ {
+ lock (percentages)
+ {
+ percentages[currentChild.Id] = p / 100;
+
+ var percent = percentages.Values.Sum();
+ percent /= totalItems;
+ percent *= 100;
+ progress.Report(percent);
+ }
+ });
+
+ tasks.Add(RefreshItem(item, refreshOptions, innerProgress, cancellationToken));
+ }
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+
+ progress.Report(100);
+ }
+
+ private async Task RefreshItem(BaseItem item, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+
+ progress.Report(100);
+ }
+
+ public ArtistInfo GetLookupInfo()
+ {
+ var info = GetItemLookupInfo<ArtistInfo>();
+
+ info.SongInfos = RecursiveChildren.OfType<Audio>()
+ .Select(i => i.GetLookupInfo())
+ .ToList();
+
+ return info;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
index b54e14f2d..5e1d4c3c9 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
@@ -26,5 +26,30 @@ namespace MediaBrowser.Controller.Entities.Audio
[IgnoreDataMember]
public List<ItemByNameCounts> UserItemCountList { get; set; }
+
+ /// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index de5516e29..8dcf08642 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -21,17 +21,16 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class BaseItem
/// </summary>
- public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData, IHasMetadata
+ public abstract class BaseItem : IHasProviderIds, ILibraryItem, IHasImages, IHasUserData, IHasMetadata, IHasLookupInfo<ItemLookupInfo>
{
protected BaseItem()
{
Genres = new List<string>();
Studios = new List<string>();
People = new List<PersonInfo>();
- BackdropImagePaths = new List<string>();
- Images = new Dictionary<ImageType, string>();
ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
LockedFields = new List<MetadataFields>();
+ ImageInfos = new List<ItemImageInfo>();
}
/// <summary>
@@ -48,6 +47,12 @@ namespace MediaBrowser.Controller.Entities
public const string ThemeVideosFolderName = "backdrops";
public const string XbmcTrailerFileSuffix = "-trailer";
+ public List<ItemImageInfo> ImageInfos { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is in mixed folder.
+ /// </summary>
+ /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
public bool IsInMixedFolder { get; set; }
private string _name;
@@ -119,7 +124,7 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
- public bool IsOwnedItem
+ public virtual bool IsOwnedItem
{
get
{
@@ -152,6 +157,16 @@ namespace MediaBrowser.Controller.Entities
}
}
+ public virtual bool SupportsLocalMetadata
+ {
+ get
+ {
+ var locationType = LocationType;
+
+ return locationType == LocationType.FileSystem || locationType == LocationType.Offline;
+ }
+ }
+
/// <summary>
/// This is just a helper for convenience
/// </summary>
@@ -160,16 +175,9 @@ namespace MediaBrowser.Controller.Entities
public string PrimaryImagePath
{
get { return this.GetImagePath(ImageType.Primary); }
- set { this.SetImagePath(ImageType.Primary, value); }
}
/// <summary>
- /// Gets or sets the images.
- /// </summary>
- /// <value>The images.</value>
- public Dictionary<ImageType, string> Images { get; set; }
-
- /// <summary>
/// Gets or sets the date created.
/// </summary>
/// <value>The date created.</value>
@@ -236,7 +244,7 @@ namespace MediaBrowser.Controller.Entities
{
var locationType = LocationType;
- if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
+ if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
{
return new string[] { };
}
@@ -258,7 +266,7 @@ namespace MediaBrowser.Controller.Entities
private string _sortName;
/// <summary>
- /// Gets or sets the name of the sort.
+ /// Gets the name of the sort.
/// </summary>
/// <value>The name of the sort.</value>
[IgnoreDataMember]
@@ -350,12 +358,6 @@ namespace MediaBrowser.Controller.Entities
public string DisplayMediaType { get; set; }
/// <summary>
- /// Gets or sets the backdrop image paths.
- /// </summary>
- /// <value>The backdrop image paths.</value>
- public List<string> BackdropImagePaths { get; set; }
-
- /// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
@@ -447,9 +449,23 @@ namespace MediaBrowser.Controller.Entities
}
[IgnoreDataMember]
- public virtual string CustomRatingForComparison
+ public string CustomRatingForComparison
{
- get { return CustomRating; }
+ get
+ {
+ if (!string.IsNullOrEmpty(CustomRating))
+ {
+ return CustomRating;
+ }
+
+ var parent = Parent;
+ if (parent != null)
+ {
+ return parent.CustomRatingForComparison;
+ }
+
+ return null;
+ }
}
/// <summary>
@@ -458,75 +474,30 @@ namespace MediaBrowser.Controller.Entities
/// <returns>List{Video}.</returns>
private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren)
{
- return new List<Trailer>();
- //ItemResolveArgs resolveArgs;
-
- //try
- //{
- // resolveArgs = ResolveArgs;
-
- // if (!resolveArgs.IsDirectory)
- // {
- // return new List<Trailer>();
- // }
- //}
- //catch (IOException ex)
- //{
- // Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
- // return new List<Trailer>();
- //}
-
- //var files = new List<FileSystemInfo>();
-
- //var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
-
- //// Path doesn't exist. No biggie
- //if (folder != null)
- //{
- // try
- // {
- // files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
- // }
- // catch (IOException ex)
- // {
- // Logger.ErrorException("Error loading trailers for {0}", ex, Name);
- // }
- //}
-
- //// Support xbmc trailers (-trailer suffix on video file names)
- //files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
- //{
- // try
- // {
- // if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
- // {
- // if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
- // {
- // return true;
- // }
- // }
- // }
- // catch (IOException ex)
- // {
- // Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
- // }
-
- // return false;
- //}));
-
- //return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
- //{
- // // Try to retrieve it from the db. If we don't find it, use the resolved version
- // var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
-
- // if (dbItem != null)
- // {
- // video = dbItem;
- // }
-
- // return video;
-
- //}).ToList();
+ var files = fileSystemChildren.OfType<DirectoryInfo>()
+ .Where(i => string.Equals(i.Name, TrailerFolderName, StringComparison.OrdinalIgnoreCase))
+ .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
+ .ToList();
+
+ // Support plex/xbmc convention
+ files.AddRange(fileSystemChildren.OfType<FileInfo>()
+ .Where(i => System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
+ );
+
+ return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
+ {
+ // Try to retrieve it from the db. If we don't find it, use the resolved version
+ var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
+
+ if (dbItem != null)
+ {
+ video = dbItem;
+ }
+
+ return video;
+
+ // Sort them so that the list can be easily compared for changes
+ }).OrderBy(i => i.Path).ToList();
}
/// <summary>
@@ -556,7 +527,9 @@ namespace MediaBrowser.Controller.Entities
}
return audio;
- }).ToList();
+
+ // Sort them so that the list can be easily compared for changes
+ }).OrderBy(i => i.Path).ToList();
}
/// <summary>
@@ -580,7 +553,9 @@ namespace MediaBrowser.Controller.Entities
}
return item;
- }).ToList();
+
+ // Sort them so that the list can be easily compared for changes
+ }).OrderBy(i => i.Path).ToList();
}
public Task RefreshMetadata(CancellationToken cancellationToken)
@@ -598,19 +573,44 @@ namespace MediaBrowser.Controller.Entities
{
var locationType = LocationType;
+ var requiresSave = false;
+
if (IsFolder || Parent != null)
{
+ options.DirectoryService = options.DirectoryService ?? new DirectoryService(Logger);
+
var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ?
- GetFileSystemChildren().ToList() :
+ GetFileSystemChildren(options.DirectoryService).ToList() :
new List<FileSystemInfo>();
- await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false);
+ var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
+
+ if (ownedItemsChanged)
+ {
+ requiresSave = true;
+ }
}
+ var dateLastSaved = DateLastSaved;
+
await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
+
+ // If it wasn't saved by the provider process, save now
+ if (requiresSave && dateLastSaved == DateLastSaved)
+ {
+ await UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+ }
}
- protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+ /// <summary>
+ /// Refreshes owned items such as trailers, theme videos, special features, etc.
+ /// Returns true or false indicating if changes were found.
+ /// </summary>
+ /// <param name="options"></param>
+ /// <param name="fileSystemChildren"></param>
+ /// <param name="cancellationToken"></param>
+ /// <returns></returns>
+ protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var themeSongsChanged = false;
@@ -637,18 +637,15 @@ namespace MediaBrowser.Controller.Entities
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
}
}
-
- if (themeSongsChanged || themeVideosChanged || localTrailersChanged)
- {
- options.ForceSave = true;
- }
+
+ return themeSongsChanged || themeVideosChanged || localTrailersChanged;
}
- protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren()
+ protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
{
var path = ContainingFolderPath;
- return new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly);
+ return directoryService.GetFileSystemEntries(path);
}
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
@@ -670,6 +667,7 @@ namespace MediaBrowser.Controller.Entities
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList();
+
var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList();
var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
@@ -885,29 +883,6 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Finds the particular item by searching through our parents and, if not found there, loading from repo
- /// </summary>
- /// <param name="id">The id.</param>
- /// <returns>BaseItem.</returns>
- /// <exception cref="System.ArgumentException"></exception>
- protected BaseItem FindParentItem(Guid id)
- {
- if (id == Guid.Empty)
- {
- throw new ArgumentException();
- }
-
- var parent = Parent;
- while (parent != null && !parent.IsRoot)
- {
- if (parent.Id == id) return parent;
- parent = parent.Parent;
- }
-
- return null;
- }
-
- /// <summary>
/// Gets a value indicating whether this instance is folder.
/// </summary>
/// <value><c>true</c> if this instance is folder; otherwise, <c>false</c>.</value>
@@ -1188,43 +1163,31 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
public bool HasImage(ImageType type, int imageIndex)
{
- if (type == ImageType.Backdrop)
- {
- return BackdropImagePaths.Count > imageIndex;
- }
- if (type == ImageType.Screenshot)
- {
- var hasScreenshots = this as IHasScreenshots;
- return hasScreenshots != null && hasScreenshots.ScreenshotImagePaths.Count > imageIndex;
- }
-
- return !string.IsNullOrEmpty(this.GetImagePath(type));
+ return GetImageInfo(type, imageIndex) != null;
}
- public void SetImagePath(ImageType type, int index, string path)
+ public void SetImagePath(ImageType type, int index, FileInfo file)
{
- if (type == ImageType.Backdrop)
- {
- throw new ArgumentException("Backdrops should be accessed using Item.Backdrops");
- }
- if (type == ImageType.Screenshot)
+ if (type == ImageType.Chapter)
{
- throw new ArgumentException("Screenshots should be accessed using Item.Screenshots");
+ throw new ArgumentException("Cannot set chapter images using SetImagePath");
}
- var typeKey = type;
+ var image = GetImageInfo(type, index);
- // If it's null remove the key from the dictionary
- if (string.IsNullOrEmpty(path))
+ if (image == null)
{
- if (Images.ContainsKey(typeKey))
+ ImageInfos.Add(new ItemImageInfo
{
- Images.Remove(typeKey);
- }
+ Path = file.FullName,
+ Type = type,
+ DateModified = FileSystem.GetLastWriteTimeUtc(file)
+ });
}
else
{
- Images[typeKey] = path;
+ image.Path = file.FullName;
+ image.DateModified = FileSystem.GetLastWriteTimeUtc(file);
}
}
@@ -1234,66 +1197,23 @@ namespace MediaBrowser.Controller.Entities
/// <param name="type">The type.</param>
/// <param name="index">The index.</param>
/// <returns>Task.</returns>
- public Task DeleteImage(ImageType type, int? index)
+ public Task DeleteImage(ImageType type, int index)
{
- if (type == ImageType.Backdrop)
- {
- if (!index.HasValue)
- {
- throw new ArgumentException("Please specify a backdrop image index to delete.");
- }
-
- var file = BackdropImagePaths[index.Value];
+ var info = GetImageInfo(type, index);
- BackdropImagePaths.Remove(file);
-
- // Delete the source file
- DeleteImagePath(file);
- }
- else if (type == ImageType.Screenshot)
+ if (info == null)
{
- if (!index.HasValue)
- {
- throw new ArgumentException("Please specify a screenshot image index to delete.");
- }
-
- var hasScreenshots = (IHasScreenshots)this;
- var file = hasScreenshots.ScreenshotImagePaths[index.Value];
-
- hasScreenshots.ScreenshotImagePaths.Remove(file);
-
- // Delete the source file
- DeleteImagePath(file);
+ // Nothing to do
+ return Task.FromResult(true);
}
- else
- {
- // Delete the source file
- DeleteImagePath(this.GetImagePath(type));
- // Remove it from the item
- this.SetImagePath(type, null);
- }
-
- // Refresh metadata
- // Need to disable slow providers or the image might get re-downloaded
- return RefreshMetadata(new MetadataRefreshOptions
- {
- ForceSave = true,
- ImageRefreshMode = ImageRefreshMode.ValidationOnly,
- MetadataRefreshMode = MetadataRefreshMode.None
+ // Remove it from the item
+ ImageInfos.Remove(info);
- }, CancellationToken.None);
- }
-
- /// <summary>
- /// Deletes the image path.
- /// </summary>
- /// <param name="path">The path.</param>
- private void DeleteImagePath(string path)
- {
- var currentFile = new FileInfo(path);
+ // Delete the source file
+ var currentFile = new FileInfo(info.Path);
- // This will fail if the file is hidden
+ // Deletion will fail if the file is hidden so remove the attribute first
if (currentFile.Exists)
{
if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
@@ -1303,90 +1223,33 @@ namespace MediaBrowser.Controller.Entities
currentFile.Delete();
}
- }
-
- /// <summary>
- /// Validates that images within the item are still on the file system
- /// </summary>
- public bool ValidateImages()
- {
- var changed = false;
-
- // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below
- var deletedKeys = Images
- .Where(image => !File.Exists(image.Value))
- .Select(i => i.Key)
- .ToList();
-
- // Now remove them from the dictionary
- foreach (var key in deletedKeys)
- {
- Images.Remove(key);
- changed = true;
- }
-
- if (ValidateBackdrops())
- {
- changed = true;
- }
- if (ValidateScreenshots())
- {
- changed = true;
- }
- return changed;
+ return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
}
- /// <summary>
- /// Validates that backdrops within the item are still on the file system
- /// </summary>
- private bool ValidateBackdrops()
+ public virtual Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken)
{
- var changed = false;
-
- // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below
- var deletedImages = BackdropImagePaths
- .Where(path => !File.Exists(path))
- .ToList();
-
- // Now remove them from the dictionary
- foreach (var path in deletedImages)
- {
- BackdropImagePaths.Remove(path);
-
- changed = true;
- }
-
- return changed;
+ return LibraryManager.UpdateItem(this, ItemUpdateType.ImageUpdate, cancellationToken);
}
/// <summary>
- /// Validates the screenshots.
+ /// Validates that images within the item are still on the file system
/// </summary>
- private bool ValidateScreenshots()
+ public bool ValidateImages(IDirectoryService directoryService)
{
- var changed = false;
+ var allDirectories = ImageInfos.Select(i => System.IO.Path.GetDirectoryName(i.Path)).Distinct(StringComparer.OrdinalIgnoreCase).ToList();
+ var allFiles = allDirectories.SelectMany(directoryService.GetFiles).Select(i => i.FullName).ToList();
- var hasScreenshots = this as IHasScreenshots;
-
- if (hasScreenshots == null)
- {
- return changed;
- }
-
- // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below
- var deletedImages = hasScreenshots.ScreenshotImagePaths
- .Where(path => !File.Exists(path))
+ var deletedImages = ImageInfos
+ .Where(image => !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase))
.ToList();
- // Now remove them from the dictionary
- foreach (var path in deletedImages)
+ if (deletedImages.Count > 0)
{
- hasScreenshots.ScreenshotImagePaths.Remove(path);
- changed = true;
+ ImageInfos = ImageInfos.Except(deletedImages).ToList();
}
- return changed;
+ return deletedImages.Count > 0;
}
/// <summary>
@@ -1400,42 +1263,87 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="System.ArgumentNullException">item</exception>
public string GetImagePath(ImageType imageType, int imageIndex)
{
- if (imageType == ImageType.Backdrop)
- {
- return BackdropImagePaths.Count > imageIndex ? BackdropImagePaths[imageIndex] : null;
- }
+ var info = GetImageInfo(imageType, imageIndex);
+
+ return info == null ? null : info.Path;
+ }
- if (imageType == ImageType.Screenshot)
+ /// <summary>
+ /// Gets the image information.
+ /// </summary>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="imageIndex">Index of the image.</param>
+ /// <returns>ItemImageInfo.</returns>
+ public ItemImageInfo GetImageInfo(ImageType imageType, int imageIndex)
+ {
+ if (imageType == ImageType.Chapter)
{
- var hasScreenshots = (IHasScreenshots)this;
- return hasScreenshots.ScreenshotImagePaths.Count > imageIndex ? hasScreenshots.ScreenshotImagePaths[imageIndex] : null;
+ var chapter = ItemRepository.GetChapter(Id, imageIndex);
+
+ if (chapter == null)
+ {
+ return null;
+ }
+
+ var path = chapter.ImagePath;
+
+ if (string.IsNullOrWhiteSpace(path))
+ {
+ return null;
+ }
+
+ return new ItemImageInfo
+ {
+ Path = path,
+ DateModified = FileSystem.GetLastWriteTimeUtc(path),
+ Type = imageType
+ };
}
+ return GetImages(imageType)
+ .ElementAtOrDefault(imageIndex);
+ }
+
+ public IEnumerable<ItemImageInfo> GetImages(ImageType imageType)
+ {
if (imageType == ImageType.Chapter)
{
- return ItemRepository.GetChapter(Id, imageIndex).ImagePath;
+ throw new ArgumentException("No image info for chapter images");
}
- string val;
- Images.TryGetValue(imageType, out val);
- return val;
+ return ImageInfos.Where(i => i.Type == imageType);
}
/// <summary>
- /// Gets the image date modified.
+ /// Adds the images.
/// </summary>
- /// <param name="imagePath">The image path.</param>
- /// <returns>DateTime.</returns>
- /// <exception cref="System.ArgumentNullException">item</exception>
- public DateTime GetImageDateModified(string imagePath)
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="images">The images.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ /// <exception cref="System.ArgumentException">Cannot call AddImages with chapter images</exception>
+ public bool AddImages(ImageType imageType, IEnumerable<FileInfo> images)
{
- if (string.IsNullOrEmpty(imagePath))
+ if (imageType == ImageType.Chapter)
{
- throw new ArgumentNullException("imagePath");
+ throw new ArgumentException("Cannot call AddImages with chapter images");
}
- // See if we can avoid a file system lookup by looking for the file in ResolveArgs
- return FileSystem.GetLastWriteTimeUtc(imagePath);
+ var existingImagePaths = GetImages(imageType)
+ .Select(i => i.Path)
+ .ToList();
+
+ var newImages = images
+ .Where(i => !existingImagePaths.Contains(i.FullName, StringComparer.OrdinalIgnoreCase))
+ .ToList();
+
+ ImageInfos.AddRange(newImages.Select(i => new ItemImageInfo
+ {
+ Path = i.FullName,
+ Type = imageType,
+ DateModified = FileSystem.GetLastWriteTimeUtc(i)
+ }));
+
+ return newImages.Count > 0;
}
/// <summary>
@@ -1447,25 +1355,37 @@ namespace MediaBrowser.Controller.Entities
return new[] { Path };
}
+ public bool AllowsMultipleImages(ImageType type)
+ {
+ return type == ImageType.Backdrop || type == ImageType.Screenshot || type == ImageType.Chapter;
+ }
+
public Task SwapImages(ImageType type, int index1, int index2)
{
- if (type != ImageType.Screenshot && type != ImageType.Backdrop)
+ if (!AllowsMultipleImages(type))
{
throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
}
- var file1 = GetImagePath(type, index1);
- var file2 = GetImagePath(type, index2);
-
- FileSystem.SwapFiles(file1, file2);
+ var info1 = GetImageInfo(type, index1);
+ var info2 = GetImageInfo(type, index2);
- // Directory watchers should repeat this, but do a quick refresh first
- return RefreshMetadata(new MetadataRefreshOptions
+ if (info1 == null || info2 == null)
{
- ForceSave = true,
- MetadataRefreshMode = MetadataRefreshMode.None
+ // Nothing to do
+ return Task.FromResult(true);
+ }
- }, CancellationToken.None);
+ var path1 = info1.Path;
+ var path2 = info2.Path;
+
+ FileSystem.SwapFiles(path1, path2);
+
+ // Refresh these values
+ info1.DateModified = FileSystem.GetLastWriteTimeUtc(info1.Path);
+ info2.DateModified = FileSystem.GetLastWriteTimeUtc(info2.Path);
+
+ return UpdateToRepository(ItemUpdateType.ImageUpdate, CancellationToken.None);
}
public virtual bool IsPlayed(User user)
@@ -1481,5 +1401,41 @@ namespace MediaBrowser.Controller.Entities
return userdata == null || !userdata.Played;
}
+
+ ItemLookupInfo IHasLookupInfo<ItemLookupInfo>.GetLookupInfo()
+ {
+ return GetItemLookupInfo<ItemLookupInfo>();
+ }
+
+ protected T GetItemLookupInfo<T>()
+ where T : ItemLookupInfo, new()
+ {
+ return new T
+ {
+ MetadataCountryCode = GetPreferredMetadataCountryCode(),
+ MetadataLanguage = GetPreferredMetadataLanguage(),
+ Name = Name,
+ ProviderIds = ProviderIds,
+ IndexNumber = IndexNumber,
+ ParentIndexNumber = ParentIndexNumber
+ };
+ }
+
+ /// <summary>
+ /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what.
+ /// </summary>
+ /// <returns>ItemUpdateType.</returns>
+ public virtual ItemUpdateType BeforeMetadataRefresh()
+ {
+ var updateType = ItemUpdateType.None;
+
+ if (string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Path))
+ {
+ Name = System.IO.Path.GetFileNameWithoutExtension(Path);
+ updateType = updateType | ItemUpdateType.MetadataEdit;
+ }
+
+ return updateType;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs
index 28ccf687c..0405fc484 100644
--- a/MediaBrowser.Controller/Entities/Book.cs
+++ b/MediaBrowser.Controller/Entities/Book.cs
@@ -1,9 +1,11 @@
-using MediaBrowser.Model.Configuration;
+using System.Linq;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
- public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage
+ public class Book : BaseItem, IHasTags, IHasPreferredMetadataLanguage, IHasLookupInfo<BookInfo>, IHasSeries
{
public override string MediaType
{
@@ -38,5 +40,21 @@ namespace MediaBrowser.Controller.Entities
{
return config.BlockUnratedBooks;
}
+
+ public BookInfo GetLookupInfo()
+ {
+ var info = GetItemLookupInfo<BookInfo>();
+
+ if (string.IsNullOrEmpty(SeriesName))
+ {
+ info.SeriesName = Parents.Select(i => i.Name).FirstOrDefault();
+ }
+ else
+ {
+ info.SeriesName = SeriesName;
+ }
+
+ return info;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 9c6b60969..416796b69 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
using System;
using System.Collections.Generic;
using System.IO;
@@ -60,12 +61,12 @@ namespace MediaBrowser.Controller.Entities
public List<string> PhysicalLocationsList { get; set; }
- protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
+ protected override IEnumerable<FileSystemInfo> GetFileSystemChildren(IDirectoryService directoryService)
{
- return CreateResolveArgs().FileSystemChildren;
+ return CreateResolveArgs(directoryService).FileSystemChildren;
}
- private ItemResolveArgs CreateResolveArgs()
+ private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
{
var path = ContainingFolderPath;
@@ -84,7 +85,7 @@ namespace MediaBrowser.Controller.Entities
// When resolving the root, we need it's grandchildren (children of user views)
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
- var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
+ var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
// Need to remove subpaths that may have been resolved from shortcuts
// Example: if \\server\movies exists, then strip out \\server\movies\action
@@ -116,11 +117,13 @@ namespace MediaBrowser.Controller.Entities
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="recursive">if set to <c>true</c> [recursive].</param>
- /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
+ /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
+ /// <param name="refreshOptions">The refresh options.</param>
+ /// <param name="directoryService">The directory service.</param>
/// <returns>Task.</returns>
- protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
+ protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
- CreateResolveArgs();
+ CreateResolveArgs(directoryService);
ResetDynamicChildren();
return NullTaskResult;
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 63a1c2bab..cb14ed099 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1,11 +1,9 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MoreLinq;
using System;
@@ -301,15 +299,27 @@ namespace MediaBrowser.Controller.Entities
/// <value>The current validation cancellation token source.</value>
private CancellationTokenSource CurrentValidationCancellationTokenSource { get; set; }
+ public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ return ValidateChildren(progress, cancellationToken, new MetadataRefreshOptions());
+ }
+
/// <summary>
/// Validates that the children of the folder still exist
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
+ /// <param name="metadataRefreshOptions">The metadata refresh options.</param>
/// <param name="recursive">if set to <c>true</c> [recursive].</param>
- /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
/// <returns>Task.</returns>
- public async Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
+ public Task ValidateChildren(IProgress<double> progress, CancellationToken cancellationToken, MetadataRefreshOptions metadataRefreshOptions, bool recursive = true)
+ {
+ metadataRefreshOptions.DirectoryService = metadataRefreshOptions.DirectoryService ?? new DirectoryService(Logger);
+
+ return ValidateChildrenWithCancellationSupport(progress, cancellationToken, recursive, true, metadataRefreshOptions, metadataRefreshOptions.DirectoryService);
+ }
+
+ private async Task ValidateChildrenWithCancellationSupport(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
cancellationToken.ThrowIfCancellationRequested();
@@ -329,7 +339,7 @@ namespace MediaBrowser.Controller.Entities
var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken);
- await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, forceRefreshMetadata).ConfigureAwait(false);
+ await ValidateChildrenInternal(progress, linkedCancellationTokenSource.Token, recursive, refreshChildMetadata, refreshOptions, directoryService).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
@@ -354,21 +364,22 @@ namespace MediaBrowser.Controller.Entities
}
/// <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***
+ /// Validates the children internal.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="recursive">if set to <c>true</c> [recursive].</param>
- /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
+ /// <param name="refreshChildMetadata">if set to <c>true</c> [refresh child metadata].</param>
+ /// <param name="refreshOptions">The refresh options.</param>
+ /// <param name="directoryService">The directory service.</param>
/// <returns>Task.</returns>
- protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
+ protected async virtual Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
{
var locationType = LocationType;
cancellationToken.ThrowIfCancellationRequested();
- var validChildren = new List<Tuple<BaseItem, bool>>();
+ var validChildren = new List<BaseItem>();
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
{
@@ -376,7 +387,7 @@ namespace MediaBrowser.Controller.Entities
try
{
- nonCachedChildren = GetNonCachedChildren();
+ nonCachedChildren = GetNonCachedChildren(directoryService);
}
catch (IOException ex)
{
@@ -403,43 +414,30 @@ namespace MediaBrowser.Controller.Entities
if (currentChildren.TryGetValue(child.Id, out currentChild))
{
- //existing item - check if it has changed
- if (currentChild.HasChanged(child))
- {
- var currentChildLocationType = currentChild.LocationType;
- if (currentChildLocationType != LocationType.Remote &&
- currentChildLocationType != LocationType.Virtual)
- {
- currentChild.DateModified = child.DateModified;
- }
-
- currentChild.IsInMixedFolder = child.IsInMixedFolder;
- validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
- }
- else
+ var currentChildLocationType = currentChild.LocationType;
+ if (currentChildLocationType != LocationType.Remote &&
+ currentChildLocationType != LocationType.Virtual)
{
- validChildren.Add(new Tuple<BaseItem, bool>(currentChild, false));
+ currentChild.DateModified = child.DateModified;
}
+ currentChild.IsInMixedFolder = child.IsInMixedFolder;
currentChild.IsOffline = false;
}
else
{
//brand new item - needs to be added
newItems.Add(child);
-
- validChildren.Add(new Tuple<BaseItem, bool>(child, true));
}
+
+ validChildren.Add(currentChild);
}
// If any items were added or removed....
if (newItems.Count > 0 || currentChildren.Count != validChildren.Count)
{
- var newChildren = validChildren.Select(c => c.Item1).ToList();
-
// That's all the new and changed ones - now see if there are any that are missing
- var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
-
+ var itemsRemoved = currentChildren.Values.Except(validChildren).ToList();
var actualRemovals = new List<BaseItem>();
foreach (var item in itemsRemoved)
@@ -448,14 +446,13 @@ namespace MediaBrowser.Controller.Entities
item.LocationType == LocationType.Remote)
{
// Don't remove these because there's no way to accurately validate them.
- validChildren.Add(new Tuple<BaseItem, bool>(item, false));
+ validChildren.Add(item);
}
else if (!string.IsNullOrEmpty(item.Path) && IsPathOffline(item.Path))
{
item.IsOffline = true;
-
- validChildren.Add(new Tuple<BaseItem, bool>(item, false));
+ validChildren.Add(item);
}
else
{
@@ -481,88 +478,134 @@ namespace MediaBrowser.Controller.Entities
await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
}
}
- else
- {
- validChildren.AddRange(ActualChildren.Select(i => new Tuple<BaseItem, bool>(i, false)));
- }
progress.Report(10);
cancellationToken.ThrowIfCancellationRequested();
- await RefreshChildren(validChildren, progress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
+ if (recursive)
+ {
+ await ValidateSubFolders(ActualChildren.OfType<Folder>().ToList(), directoryService, progress, cancellationToken).ConfigureAwait(false);
+ }
+
+ progress.Report(20);
+
+ if (refreshChildMetadata)
+ {
+ var container = this as IMetadataContainer;
+
+ var innerProgress = new ActionableProgress<double>();
+
+ innerProgress.RegisterAction(p => progress.Report((.80 * p) + 20));
+
+ if (container != null)
+ {
+ await container.RefreshAllMetadata(refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await RefreshMetadataRecursive(refreshOptions, recursive, innerProgress, cancellationToken);
+ }
+ }
progress.Report(100);
}
- /// <summary>
- /// Refreshes the children.
- /// </summary>
- /// <param name="children">The children.</param>
- /// <param name="progress">The progress.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <param name="recursive">if set to <c>true</c> [recursive].</param>
- /// <param name="forceRefreshMetadata">if set to <c>true</c> [force refresh metadata].</param>
- /// <returns>Task.</returns>
- private async Task RefreshChildren(IList<Tuple<BaseItem, bool>> children, IProgress<double> progress, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false)
+ private async Task RefreshMetadataRecursive(MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
- var list = children;
+ var children = ActualChildren.ToList();
- var percentages = new Dictionary<Guid, double>(list.Count);
+ var percentages = new Dictionary<Guid, double>(children.Count);
var tasks = new List<Task>();
- foreach (var tuple in list)
+ foreach (var child in children)
{
- if (tasks.Count > 10)
+ if (tasks.Count >= 8)
{
await Task.WhenAll(tasks).ConfigureAwait(false);
+ tasks.Clear();
}
- tasks.Add(RefreshChild(tuple, progress, percentages, list.Count, cancellationToken, recursive, forceRefreshMetadata));
- }
+ cancellationToken.ThrowIfCancellationRequested();
+ var innerProgress = new ActionableProgress<double>();
- cancellationToken.ThrowIfCancellationRequested();
+ // Avoid implicitly captured closure
+ var currentChild = child;
+ innerProgress.RegisterAction(p =>
+ {
+ lock (percentages)
+ {
+ percentages[currentChild.Id] = p / 100;
+
+ var percent = percentages.Values.Sum();
+ percent /= children.Count;
+ percent *= 100;
+ progress.Report(percent);
+ }
+ });
+
+ if (child.IsFolder)
+ {
+ await RefreshChildMetadata(child, refreshOptions, recursive, innerProgress, cancellationToken)
+ .ConfigureAwait(false);
+ }
+ else
+ {
+ tasks.Add(RefreshChildMetadata(child, refreshOptions, false, innerProgress, cancellationToken));
+ }
+ }
await Task.WhenAll(tasks).ConfigureAwait(false);
+ progress.Report(100);
}
- private async Task RefreshChild(Tuple<BaseItem, bool> currentTuple, IProgress<double> progress, Dictionary<Guid, double> percentages, int childCount, CancellationToken cancellationToken, bool? recursive, bool forceRefreshMetadata = false)
+ private async Task RefreshChildMetadata(BaseItem child, MetadataRefreshOptions refreshOptions, bool recursive, IProgress<double> progress, CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
+ var container = child as IMetadataContainer;
- var child = currentTuple.Item1;
- try
+ if (container != null)
{
- //refresh it
- await child.RefreshMetadata(new MetadataRefreshOptions
- {
- ForceSave = currentTuple.Item2,
- ReplaceAllMetadata = forceRefreshMetadata
-
- }, cancellationToken).ConfigureAwait(false);
+ await container.RefreshAllMetadata(refreshOptions, progress, cancellationToken).ConfigureAwait(false);
}
- catch (IOException ex)
+ else
{
- Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
- }
-
- // Refresh children if a folder and the item changed or recursive is set to true
- var refreshChildren = child.IsFolder && (currentTuple.Item2 || (recursive.HasValue && recursive.Value));
+ await child.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
- if (refreshChildren)
- {
- // Don't refresh children if explicitly set to false
- if (recursive.HasValue && recursive.Value == false)
+ if (recursive)
{
- refreshChildren = false;
+ var folder = child as Folder;
+
+ if (folder != null)
+ {
+ await folder.RefreshMetadataRecursive(refreshOptions, true, progress, cancellationToken);
+ }
}
}
+ progress.Report(100);
+ }
+
+ /// <summary>
+ /// Refreshes the children.
+ /// </summary>
+ /// <param name="children">The children.</param>
+ /// <param name="directoryService">The directory service.</param>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ private async Task ValidateSubFolders(IList<Folder> children, IDirectoryService directoryService, IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ var list = children;
+ var childCount = list.Count;
+
+ var percentages = new Dictionary<Guid, double>(list.Count);
- if (refreshChildren)
+ foreach (var item in list)
{
cancellationToken.ThrowIfCancellationRequested();
+ var child = item;
+
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p =>
@@ -574,23 +617,12 @@ namespace MediaBrowser.Controller.Entities
var percent = percentages.Values.Sum();
percent /= childCount;
- progress.Report((90 * percent) + 10);
+ progress.Report((10 * percent) + 10);
}
});
- await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
- }
- else
- {
- lock (percentages)
- {
- percentages[child.Id] = 1;
-
- var percent = percentages.Values.Sum();
- percent /= childCount;
-
- progress.Report((90 * percent) + 10);
- }
+ await child.ValidateChildrenWithCancellationSupport(innerProgress, cancellationToken, true, false, null, directoryService)
+ .ConfigureAwait(false);
}
}
@@ -647,9 +679,9 @@ namespace MediaBrowser.Controller.Entities
/// Get the children of this folder from the actual file system
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
- protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
+ protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
- return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this);
+ return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(directoryService), this);
}
/// <summary>
@@ -895,17 +927,21 @@ namespace MediaBrowser.Controller.Entities
return item;
}
- protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+ protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
+ var changesFound = false;
+
if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
{
if (RefreshLinkedChildren(fileSystemChildren))
{
- options.ForceSave = true;
+ changesFound = true;
}
}
- return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken);
+ var baseHasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+
+ return baseHasChanges || changesFound;
}
/// <summary>
@@ -967,11 +1003,11 @@ namespace MediaBrowser.Controller.Entities
/// <returns>Task.</returns>
public override async Task ChangedExternally()
{
- await base.ChangedExternally().ConfigureAwait(false);
-
var progress = new Progress<double>();
await ValidateChildren(progress, CancellationToken.None).ConfigureAwait(false);
+
+ await base.ChangedExternally().ConfigureAwait(false);
}
/// <summary>
@@ -1016,50 +1052,17 @@ namespace MediaBrowser.Controller.Entities
throw new ArgumentNullException();
}
- try
- {
- var locationType = LocationType;
-
- if (locationType == LocationType.Remote && string.Equals(Path, path, StringComparison.OrdinalIgnoreCase))
- {
- return this;
- }
-
- if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
- {
- return this;
- }
- }
- catch (IOException ex)
+ if (string.Equals(Path, path, StringComparison.OrdinalIgnoreCase))
{
- Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
+ return this;
}
- return RecursiveChildren.Where(i => i.LocationType != LocationType.Virtual).FirstOrDefault(i =>
+ if (PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
{
- try
- {
- if (string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase))
- {
- return true;
- }
-
- if (i.LocationType != LocationType.Remote)
- {
- if (i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
- {
- return true;
- }
- }
+ return this;
+ }
- return false;
- }
- catch (IOException ex)
- {
- Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
- return false;
- }
- });
+ return RecursiveChildren.FirstOrDefault(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) || i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase));
}
public override bool IsPlayed(User user)
diff --git a/MediaBrowser.Controller/Entities/Game.cs b/MediaBrowser.Controller/Entities/Game.cs
index 1b5176362..cc9d9a1a4 100644
--- a/MediaBrowser.Controller/Entities/Game.cs
+++ b/MediaBrowser.Controller/Entities/Game.cs
@@ -1,11 +1,12 @@
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
- public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, IHasPreferredMetadataLanguage
+ public class Game : BaseItem, IHasSoundtracks, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, IHasPreferredMetadataLanguage, IHasLookupInfo<GameInfo>
{
public List<Guid> SoundtrackIds { get; set; }
@@ -29,18 +30,11 @@ namespace MediaBrowser.Controller.Entities
ThemeSongIds = new List<Guid>();
ThemeVideoIds = new List<Guid>();
Tags = new List<string>();
- ScreenshotImagePaths = new List<string>();
}
public List<Guid> LocalTrailerIds { get; set; }
/// <summary>
- /// Gets or sets the screenshot image paths.
- /// </summary>
- /// <value>The screenshot image paths.</value>
- public List<string> ScreenshotImagePaths { get; set; }
-
- /// <summary>
/// Gets or sets the tags.
/// </summary>
/// <value>The tags.</value>
@@ -115,5 +109,14 @@ namespace MediaBrowser.Controller.Entities
{
return config.BlockUnratedGames;
}
+
+ public GameInfo GetLookupInfo()
+ {
+ var id = GetItemLookupInfo<GameInfo>();
+
+ id.GameSystem = GameSystem;
+
+ return id;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs
index ffe62ba03..3a3c575cd 100644
--- a/MediaBrowser.Controller/Entities/GameGenre.cs
+++ b/MediaBrowser.Controller/Entities/GameGenre.cs
@@ -1,5 +1,4 @@
using MediaBrowser.Model.Dto;
-using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
@@ -23,5 +22,30 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public List<ItemByNameCounts> UserItemCountList { get; set; }
+
+ /// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/GameSystem.cs b/MediaBrowser.Controller/Entities/GameSystem.cs
index 69cb5e974..f2fec4397 100644
--- a/MediaBrowser.Controller/Entities/GameSystem.cs
+++ b/MediaBrowser.Controller/Entities/GameSystem.cs
@@ -1,4 +1,5 @@
using System.Runtime.Serialization;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using System;
@@ -7,7 +8,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class GameSystem
/// </summary>
- public class GameSystem : Folder
+ public class GameSystem : Folder, IHasLookupInfo<GameSystemInfo>
{
/// <summary>
/// Return the id that should be used to key display prefs for this item.
@@ -47,5 +48,14 @@ namespace MediaBrowser.Controller.Entities
// Don't block. Determine by game
return false;
}
+
+ public GameSystemInfo GetLookupInfo()
+ {
+ var id = GetItemLookupInfo<GameSystemInfo>();
+
+ id.Path = Path;
+
+ return id;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 53bc64194..c15ca0aa2 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -25,5 +25,30 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public List<ItemByNameCounts> UserItemCountList { get; set; }
+
+ /// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs
index dd6194bc7..d53eba11a 100644
--- a/MediaBrowser.Controller/Entities/IHasImages.cs
+++ b/MediaBrowser.Controller/Entities/IHasImages.cs
@@ -1,6 +1,8 @@
-using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Entities
@@ -32,6 +34,13 @@ namespace MediaBrowser.Controller.Entities
LocationType LocationType { get; }
/// <summary>
+ /// Gets the images.
+ /// </summary>
+ /// <param name="imageType">Type of the image.</param>
+ /// <returns>IEnumerable{ItemImageInfo}.</returns>
+ IEnumerable<ItemImageInfo> GetImages(ImageType imageType);
+
+ /// <summary>
/// Gets the image path.
/// </summary>
/// <param name="imageType">Type of the image.</param>
@@ -40,19 +49,20 @@ namespace MediaBrowser.Controller.Entities
string GetImagePath(ImageType imageType, int imageIndex);
/// <summary>
- /// Gets the image date modified.
+ /// Gets the image information.
/// </summary>
- /// <param name="imagePath">The image path.</param>
- /// <returns>DateTime.</returns>
- DateTime GetImageDateModified(string imagePath);
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="imageIndex">Index of the image.</param>
+ /// <returns>ItemImageInfo.</returns>
+ ItemImageInfo GetImageInfo(ImageType imageType, int imageIndex);
/// <summary>
/// Sets the image.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="index">The index.</param>
- /// <param name="path">The path.</param>
- void SetImagePath(ImageType type, int index, string path);
+ /// <param name="file">The file.</param>
+ void SetImagePath(ImageType type, int index, FileInfo file);
/// <summary>
/// Determines whether the specified type has image.
@@ -63,6 +73,13 @@ namespace MediaBrowser.Controller.Entities
bool HasImage(ImageType type, int imageIndex);
/// <summary>
+ /// Allowses the multiple images.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ bool AllowsMultipleImages(ImageType type);
+
+ /// <summary>
/// Swaps the images.
/// </summary>
/// <param name="type">The type.</param>
@@ -92,13 +109,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Validates the images and returns true or false indicating if any were removed.
/// </summary>
- bool ValidateImages();
-
- /// <summary>
- /// Gets or sets the backdrop image paths.
- /// </summary>
- /// <value>The backdrop image paths.</value>
- List<string> BackdropImagePaths { get; set; }
+ bool ValidateImages(IDirectoryService directoryService);
/// <summary>
/// Gets a value indicating whether this instance is owned item.
@@ -111,6 +122,26 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <value>The containing folder path.</value>
string ContainingFolderPath { get; }
+
+ /// <summary>
+ /// Adds the images.
+ /// </summary>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="images">The images.</param>
+ /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ bool AddImages(ImageType imageType, IEnumerable<FileInfo> images);
+
+ /// <summary>
+ /// Determines whether [is save local metadata enabled].
+ /// </summary>
+ /// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns>
+ bool IsSaveLocalMetadataEnabled();
+
+ /// <summary>
+ /// Gets a value indicating whether [supports local metadata].
+ /// </summary>
+ /// <value><c>true</c> if [supports local metadata]; otherwise, <c>false</c>.</value>
+ bool SupportsLocalMetadata { get; }
}
public static class HasImagesExtensions
@@ -136,10 +167,21 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
/// <param name="item">The item.</param>
/// <param name="imageType">Type of the image.</param>
- /// <param name="path">The path.</param>
- public static void SetImagePath(this IHasImages item, ImageType imageType, string path)
+ /// <param name="file">The file.</param>
+ public static void SetImagePath(this IHasImages item, ImageType imageType, FileInfo file)
+ {
+ item.SetImagePath(imageType, 0, file);
+ }
+
+ /// <summary>
+ /// Sets the image path.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="imageType">Type of the image.</param>
+ /// <param name="file">The file.</param>
+ public static void SetImagePath(this IHasImages item, ImageType imageType, string file)
{
- item.SetImagePath(imageType, 0, path);
+ item.SetImagePath(imageType, new FileInfo(file));
}
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasMetadata.cs b/MediaBrowser.Controller/Entities/IHasMetadata.cs
new file mode 100644
index 000000000..0285b6749
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasMetadata.cs
@@ -0,0 +1,59 @@
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Entities
+{
+ /// <summary>
+ /// Interface IHasMetadata
+ /// </summary>
+ public interface IHasMetadata : IHasImages
+ {
+ /// <summary>
+ /// Gets the preferred metadata country code.
+ /// </summary>
+ /// <returns>System.String.</returns>
+ string GetPreferredMetadataCountryCode();
+
+ /// <summary>
+ /// Gets the date modified.
+ /// </summary>
+ /// <value>The date modified.</value>
+ DateTime DateModified { get; }
+
+ /// <summary>
+ /// Gets the locked fields.
+ /// </summary>
+ /// <value>The locked fields.</value>
+ List<MetadataFields> LockedFields { get; }
+
+ /// <summary>
+ /// Gets or sets the date last saved.
+ /// </summary>
+ /// <value>The date last saved.</value>
+ DateTime DateLastSaved { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is in mixed folder.
+ /// </summary>
+ /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
+ bool IsInMixedFolder { get; }
+
+ /// <summary>
+ /// Updates to repository.
+ /// </summary>
+ /// <param name="updateReason">The update reason.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken);
+
+ /// <summary>
+ /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what.
+ /// </summary>
+ /// <returns>ItemUpdateType.</returns>
+ ItemUpdateType BeforeMetadataRefresh();
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/IHasScreenshots.cs b/MediaBrowser.Controller/Entities/IHasScreenshots.cs
index 341d6403f..2fd402bc2 100644
--- a/MediaBrowser.Controller/Entities/IHasScreenshots.cs
+++ b/MediaBrowser.Controller/Entities/IHasScreenshots.cs
@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-
+
namespace MediaBrowser.Controller.Entities
{
/// <summary>
@@ -7,10 +6,5 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public interface IHasScreenshots
{
- /// <summary>
- /// Gets or sets the screenshot image paths.
- /// </summary>
- /// <value>The screenshot image paths.</value>
- List<string> ScreenshotImagePaths { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasSeries.cs b/MediaBrowser.Controller/Entities/IHasSeries.cs
new file mode 100644
index 000000000..64c33a376
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IHasSeries.cs
@@ -0,0 +1,12 @@
+
+namespace MediaBrowser.Controller.Entities
+{
+ public interface IHasSeries
+ {
+ /// <summary>
+ /// Gets the name of the series.
+ /// </summary>
+ /// <value>The name of the series.</value>
+ string SeriesName { get; }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/IMetadataContainer.cs b/MediaBrowser.Controller/Entities/IMetadataContainer.cs
new file mode 100644
index 000000000..33aa08425
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/IMetadataContainer.cs
@@ -0,0 +1,19 @@
+using MediaBrowser.Controller.Providers;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Entities
+{
+ public interface IMetadataContainer
+ {
+ /// <summary>
+ /// Refreshes all metadata.
+ /// </summary>
+ /// <param name="refreshOptions">The refresh options.</param>
+ /// <param name="progress">The progress.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken);
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs
new file mode 100644
index 000000000..80aec6482
--- /dev/null
+++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs
@@ -0,0 +1,14 @@
+using MediaBrowser.Model.Entities;
+using System;
+
+namespace MediaBrowser.Controller.Entities
+{
+ public class ItemImageInfo
+ {
+ public string Path { get; set; }
+
+ public ImageType Type { get; set; }
+
+ public DateTime DateModified { get; set; }
+ }
+}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 19d0d6682..a00bda772 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -1,15 +1,20 @@
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Common.Progress;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
namespace MediaBrowser.Controller.Entities.Movies
{
/// <summary>
/// Class BoxSet
/// </summary>
- public class BoxSet : Folder, IHasTrailers, IHasTags, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder
+ public class BoxSet : Folder, IHasTrailers, IHasTags, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IMetadataContainer
{
public BoxSet()
{
@@ -74,5 +79,67 @@ namespace MediaBrowser.Controller.Entities.Movies
// Default sorting
return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending);
}
+
+ public BoxSetInfo GetLookupInfo()
+ {
+ return GetItemLookupInfo<BoxSetInfo>();
+ }
+
+ public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ // Refresh bottom up, children first, then the boxset
+ // By then hopefully the movies within will have Tmdb collection values
+ var items = RecursiveChildren.ToList();
+
+ var totalItems = items.Count;
+ var percentages = new Dictionary<Guid, double>(totalItems);
+
+ var tasks = new List<Task>();
+
+ // Refresh songs
+ foreach (var item in items)
+ {
+ if (tasks.Count >= 2)
+ {
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ tasks.Clear();
+ }
+
+ cancellationToken.ThrowIfCancellationRequested();
+ var innerProgress = new ActionableProgress<double>();
+
+ // Avoid implicitly captured closure
+ var currentChild = item;
+ innerProgress.RegisterAction(p =>
+ {
+ lock (percentages)
+ {
+ percentages[currentChild.Id] = p / 100;
+
+ var percent = percentages.Values.Sum();
+ percent /= totalItems;
+ percent *= 100;
+ progress.Report(percent);
+ }
+ });
+
+ tasks.Add(RefreshItem(item, refreshOptions, innerProgress, cancellationToken));
+ }
+
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ tasks.Clear();
+
+ // Refresh current item
+ await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+
+ progress.Report(100);
+ }
+
+ private async Task RefreshItem(BaseItem item, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
+ {
+ await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+
+ progress.Report(100);
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index 41a1969d6..8eba21df0 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <summary>
/// Class Movie
/// </summary>
- public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore
+ public class Movie : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasKeywords, IHasTrailers, IHasThemeMedia, IHasTaglines, IHasPreferredMetadataLanguage, IHasAwards, IHasMetascore, IHasLookupInfo<MovieInfo>
{
public List<Guid> SpecialFeatureIds { get; set; }
@@ -39,7 +39,6 @@ namespace MediaBrowser.Controller.Entities.Movies
ThemeSongIds = new List<Guid>();
ThemeVideoIds = new List<Guid>();
Taglines = new List<string>();
- Tags = new List<string>();
Keywords = new List<string>();
}
@@ -53,12 +52,6 @@ namespace MediaBrowser.Controller.Entities.Movies
public List<MediaUrl> RemoteTrailers { get; set; }
/// <summary>
- /// Gets or sets the tags.
- /// </summary>
- /// <value>The tags.</value>
- public List<string> Tags { get; set; }
-
- /// <summary>
/// Gets or sets the taglines.
/// </summary>
/// <value>The taglines.</value>
@@ -103,9 +96,9 @@ namespace MediaBrowser.Controller.Entities.Movies
return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
}
- protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+ protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
- await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+ var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
// Must have a parent to have special features
// In other words, it must be part of the Parent/Child tree
@@ -115,12 +108,14 @@ namespace MediaBrowser.Controller.Entities.Movies
if (specialFeaturesChanged)
{
- options.ForceSave = true;
+ hasChanges = true;
}
}
+
+ return hasChanges;
}
- private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+ private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newItems = LoadSpecialFeatures(fileSystemChildren).ToList();
var newItemIds = newItems.Select(i => i.Id).ToList();
@@ -157,12 +152,19 @@ namespace MediaBrowser.Controller.Entities.Movies
}
return video;
- });
+
+ // Sort them so that the list can be easily compared for changes
+ }).OrderBy(i => i.Path).ToList();
}
protected override bool GetBlockUnratedValue(UserConfiguration config)
{
return config.BlockUnratedMovies;
}
+
+ public MovieInfo GetLookupInfo()
+ {
+ return GetItemLookupInfo<MovieInfo>();
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/MusicVideo.cs b/MediaBrowser.Controller/Entities/MusicVideo.cs
index d9eff8fbe..56cd71d49 100644
--- a/MediaBrowser.Controller/Entities/MusicVideo.cs
+++ b/MediaBrowser.Controller/Entities/MusicVideo.cs
@@ -1,11 +1,12 @@
using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System;
namespace MediaBrowser.Controller.Entities
{
- public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget
+ public class MusicVideo : Video, IHasArtist, IHasMusicGenres, IHasBudget, IHasLookupInfo<MusicVideoInfo>
{
/// <summary>
/// Gets or sets the artist.
@@ -54,5 +55,10 @@ namespace MediaBrowser.Controller.Entities
{
return config.BlockUnratedMusic;
}
+
+ public MusicVideoInfo GetLookupInfo()
+ {
+ return GetItemLookupInfo<MusicVideoInfo>();
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs
index 832586ab9..c1dc81136 100644
--- a/MediaBrowser.Controller/Entities/Person.cs
+++ b/MediaBrowser.Controller/Entities/Person.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
using System.Collections.Generic;
using System.Runtime.Serialization;
@@ -7,7 +8,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// This is the full Person object that can be retrieved with all of it's data.
/// </summary>
- public class Person : BaseItem, IItemByName
+ public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo>
{
public Person()
{
@@ -31,6 +32,36 @@ namespace MediaBrowser.Controller.Entities
{
return "Person-" + Name;
}
+
+ public PersonLookupInfo GetLookupInfo()
+ {
+ return GetItemLookupInfo<PersonLookupInfo>();
+ }
+
+ /// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
}
/// <summary>
diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs
index 7bc17549f..5c3946f9b 100644
--- a/MediaBrowser.Controller/Entities/Studio.cs
+++ b/MediaBrowser.Controller/Entities/Studio.cs
@@ -26,5 +26,30 @@ namespace MediaBrowser.Controller.Entities
[IgnoreDataMember]
public List<ItemByNameCounts> UserItemCountList { get; set; }
+
+ /// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 73726a4e2..daff3dd6c 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -1,4 +1,7 @@
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,7 +12,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Episode
/// </summary>
- public class Episode : Video
+ public class Episode : Video, IHasLookupInfo<EpisodeInfo>, IHasSeries
{
/// <summary>
/// Gets the season in which it aired.
@@ -41,7 +44,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary>
/// <value>The index number.</value>
public int? IndexNumberEnd { get; set; }
-
+
/// <summary>
/// We want to group into series not show individually in an index
/// </summary>
@@ -98,9 +101,11 @@ namespace MediaBrowser.Controller.Entities.TV
/// <returns>System.String.</returns>
public override string GetUserDataKey()
{
- if (Series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
+ var series = Series;
+
+ if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
{
- return Series.GetUserDataKey() + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000");
+ return series.GetUserDataKey() + ParentIndexNumber.Value.ToString("000") + IndexNumber.Value.ToString("000");
}
return base.GetUserDataKey();
@@ -112,16 +117,11 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public override string OfficialRatingForComparison
{
- get { return Series != null ? Series.OfficialRatingForComparison : base.OfficialRatingForComparison; }
- }
-
- /// <summary>
- /// Our rating comes from our series
- /// </summary>
- [IgnoreDataMember]
- public override string CustomRatingForComparison
- {
- get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; }
+ get
+ {
+ var series = Series;
+ return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison;
+ }
}
/// <summary>
@@ -140,6 +140,16 @@ namespace MediaBrowser.Controller.Entities.TV
get { return FindParent<Season>(); }
}
+ [IgnoreDataMember]
+ public string SeriesName
+ {
+ get
+ {
+ var series = Series;
+ return series == null ? null : series.Name;
+ }
+ }
+
/// <summary>
/// Creates the name of the sort.
/// </summary>
@@ -175,7 +185,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
get
{
- return LocationType == Model.Entities.LocationType.Virtual && PremiereDate.HasValue && PremiereDate.Value < DateTime.UtcNow;
+ return LocationType == LocationType.Virtual && PremiereDate.HasValue && PremiereDate.Value < DateTime.UtcNow;
}
}
@@ -188,7 +198,7 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public bool IsVirtualUnaired
{
- get { return LocationType == Model.Entities.LocationType.Virtual && IsUnaired; }
+ get { return LocationType == LocationType.Virtual && IsUnaired; }
}
[IgnoreDataMember]
@@ -236,5 +246,70 @@ namespace MediaBrowser.Controller.Entities.TV
{
return config.BlockUnratedSeries;
}
+
+ public EpisodeInfo GetLookupInfo()
+ {
+ var id = GetItemLookupInfo<EpisodeInfo>();
+
+ var series = Series;
+
+ if (series != null)
+ {
+ id.SeriesProviderIds = series.ProviderIds;
+ }
+
+ id.IndexNumberEnd = IndexNumberEnd;
+
+ return id;
+ }
+
+ public override ItemUpdateType BeforeMetadataRefresh()
+ {
+ var updateType = base.BeforeMetadataRefresh();
+
+ var locationType = LocationType;
+ if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
+ {
+ if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
+ {
+ IndexNumber = IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(Path, Parent is Season);
+
+ // If a change was made record it
+ if (IndexNumber.HasValue)
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+ }
+
+ if (!IndexNumberEnd.HasValue && !string.IsNullOrEmpty(Path))
+ {
+ IndexNumberEnd = IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(Path);
+
+ // If a change was made record it
+ if (IndexNumberEnd.HasValue)
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+ }
+ }
+
+ if (!ParentIndexNumber.HasValue)
+ {
+ var season = Season;
+
+ if (season != null)
+ {
+ ParentIndexNumber = season.IndexNumber;
+ }
+
+ // If a change was made record it
+ if (ParentIndexNumber.HasValue)
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+ }
+
+ return updateType;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 744416560..830ccb8a2 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -1,11 +1,10 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
-using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Runtime.Serialization;
@@ -14,7 +13,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Season
/// </summary>
- public class Season : Folder
+ public class Season : Folder, IHasSeries, IHasLookupInfo<SeasonInfo>
{
/// <summary>
@@ -119,16 +118,11 @@ namespace MediaBrowser.Controller.Entities.TV
[IgnoreDataMember]
public override string OfficialRatingForComparison
{
- get { return Series != null ? Series.OfficialRatingForComparison : base.OfficialRatingForComparison; }
- }
-
- /// <summary>
- /// Our rating comes from our series
- /// </summary>
- [IgnoreDataMember]
- public override string CustomRatingForComparison
- {
- get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; }
+ get
+ {
+ var series = Series;
+ return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison;
+ }
}
/// <summary>
@@ -223,7 +217,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
episodes = episodes.Where(i => !i.IsVirtualUnaired);
}
-
+
return LibraryManager
.Sort(episodes, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
.Cast<Episode>();
@@ -239,5 +233,51 @@ namespace MediaBrowser.Controller.Entities.TV
// Don't block. Let either the entire series rating or episode rating determine it
return false;
}
+
+ [IgnoreDataMember]
+ public string SeriesName
+ {
+ get
+ {
+ var series = Series;
+ return series == null ? null : series.Name;
+ }
+ }
+
+ /// <summary>
+ /// Gets the lookup information.
+ /// </summary>
+ /// <returns>SeasonInfo.</returns>
+ public SeasonInfo GetLookupInfo()
+ {
+ return GetItemLookupInfo<SeasonInfo>();
+ }
+
+ /// <summary>
+ /// This is called before any metadata refresh and returns ItemUpdateType indictating if changes were made, and what.
+ /// </summary>
+ /// <returns>ItemUpdateType.</returns>
+ public override ItemUpdateType BeforeMetadataRefresh()
+ {
+ var updateType = base.BeforeMetadataRefresh();
+
+ var locationType = LocationType;
+
+ if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
+ {
+ if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
+ {
+ IndexNumber = IndexNumber ?? TVUtils.GetSeasonNumberFromPath(Path);
+
+ // If a change was made record it
+ if (IndexNumber.HasValue)
+ {
+ updateType = updateType | ItemUpdateType.MetadataImport;
+ }
+ }
+ }
+
+ return updateType;
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index efb3c393b..0e07654d6 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -1,5 +1,6 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
@@ -14,7 +15,7 @@ namespace MediaBrowser.Controller.Entities.TV
/// <summary>
/// Class Series
/// </summary>
- public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage, IHasDisplayOrder
+ public class Series : Folder, IHasSoundtracks, IHasTrailers, IHasTags, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo<SeriesInfo>
{
public List<Guid> SpecialFeatureIds { get; set; }
public List<Guid> SoundtrackIds { get; set; }
@@ -222,5 +223,10 @@ namespace MediaBrowser.Controller.Entities.TV
}
public string PreferredMetadataLanguage { get; set; }
+
+ public SeriesInfo GetLookupInfo()
+ {
+ return GetItemLookupInfo<SeriesInfo>();
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index d6d193442..d655c275d 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Configuration;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
@@ -9,7 +10,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class Trailer
/// </summary>
- public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasKeywords, IHasTaglines, IHasTags, IHasPreferredMetadataLanguage, IHasMetascore
+ public class Trailer : Video, IHasCriticRating, IHasSoundtracks, IHasBudget, IHasTrailers, IHasKeywords, IHasTaglines, IHasPreferredMetadataLanguage, IHasMetascore, IHasLookupInfo<TrailerInfo>
{
public List<Guid> SoundtrackIds { get; set; }
@@ -27,7 +28,6 @@ namespace MediaBrowser.Controller.Entities
Taglines = new List<string>();
SoundtrackIds = new List<Guid>();
LocalTrailerIds = new List<Guid>();
- Tags = new List<string>();
Keywords = new List<string>();
}
@@ -40,12 +40,6 @@ namespace MediaBrowser.Controller.Entities
public List<string> Keywords { get; set; }
/// <summary>
- /// Gets or sets the tags.
- /// </summary>
- /// <value>The tags.</value>
- public List<string> Tags { get; set; }
-
- /// <summary>
/// Gets or sets the taglines.
/// </summary>
/// <value>The taglines.</value>
@@ -105,5 +99,10 @@ namespace MediaBrowser.Controller.Entities
{
return config.BlockUnratedTrailers;
}
+
+ public TrailerInfo GetLookupInfo()
+ {
+ return GetItemLookupInfo<TrailerInfo>();
+ }
}
}
diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs
index 5feb000af..66ef8c7dc 100644
--- a/MediaBrowser.Controller/Entities/User.cs
+++ b/MediaBrowser.Controller/Entities/User.cs
@@ -73,6 +73,31 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
/// The _root folder
/// </summary>
private UserRootFolder _rootFolder;
@@ -215,12 +240,18 @@ namespace MediaBrowser.Controller.Entities
return RefreshMetadata(new MetadataRefreshOptions
{
- ForceSave = true,
- ReplaceAllMetadata = true
+ ReplaceAllMetadata = true,
+ ImageRefreshMode = ImageRefreshMode.FullRefresh,
+ MetadataRefreshMode = MetadataRefreshMode.FullRefresh
}, CancellationToken.None);
}
+ public override Task UpdateToRepository(ItemUpdateType updateReason, CancellationToken cancellationToken)
+ {
+ return UserManager.UpdateUser(this);
+ }
+
/// <summary>
/// Gets the path to the user's configuration directory
/// </summary>
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index 8fe5f43f1..dc3d4c384 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities
@@ -13,9 +15,22 @@ namespace MediaBrowser.Controller.Entities
/// Get the children of this folder from the actual file system
/// </summary>
/// <returns>IEnumerable{BaseItem}.</returns>
- protected override IEnumerable<BaseItem> GetNonCachedChildren()
+ protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
{
- return base.GetNonCachedChildren().Concat(LibraryManager.RootFolder.VirtualChildren);
+ return base.GetNonCachedChildren(directoryService).Concat(LibraryManager.RootFolder.VirtualChildren);
+ }
+
+ public override ItemUpdateType BeforeMetadataRefresh()
+ {
+ var updateType = base.BeforeMetadataRefresh();
+
+ if (string.Equals("default", Name, System.StringComparison.OrdinalIgnoreCase))
+ {
+ Name = "Default Media Library";
+ updateType = updateType | ItemUpdateType.MetadataEdit;
+ }
+
+ return updateType;
}
}
}
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index de78068b3..e778b38bd 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -16,7 +16,7 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Class Video
/// </summary>
- public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio
+ public class Video : BaseItem, IHasMediaStreams, IHasAspectRatio, IHasTags
{
public bool IsMultiPart { get; set; }
@@ -26,15 +26,29 @@ namespace MediaBrowser.Controller.Entities
{
PlayableStreamFileNames = new List<string>();
AdditionalPartIds = new List<Guid>();
+ Tags = new List<string>();
+ SubtitleFiles = new List<string>();
}
/// <summary>
+ /// Gets or sets the subtitle paths.
+ /// </summary>
+ /// <value>The subtitle paths.</value>
+ public List<string> SubtitleFiles { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether this instance has subtitles.
/// </summary>
/// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
public bool HasSubtitles { get; set; }
/// <summary>
+ /// Gets or sets the tags.
+ /// </summary>
+ /// <value>The tags.</value>
+ public List<string> Tags { get; set; }
+
+ /// <summary>
/// Gets or sets the video bit rate.
/// </summary>
/// <value>The video bit rate.</value>
@@ -149,9 +163,9 @@ namespace MediaBrowser.Controller.Entities
}
}
- protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+ protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
- await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+ var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
// Must have a parent to have additional parts
// In other words, it must be part of the Parent/Child tree
@@ -162,9 +176,11 @@ namespace MediaBrowser.Controller.Entities
if (additionalPartsChanged)
{
- options.ForceSave = true;
+ hasChanges = true;
}
}
+
+ return hasChanges;
}
/// <summary>
@@ -174,7 +190,7 @@ namespace MediaBrowser.Controller.Entities
/// <param name="fileSystemChildren">The file system children.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task{System.Boolean}.</returns>
- private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
+ private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
{
var newItems = LoadAdditionalParts(fileSystemChildren).ToList();
@@ -238,7 +254,8 @@ namespace MediaBrowser.Controller.Entities
return video;
- }).ToList();
+ // Sort them so that the list can be easily compared for changes
+ }).OrderBy(i => i.Path).ToList();
}
public override IEnumerable<string> GetDeletePaths()
diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs
index cd50a1c60..c6ca028ae 100644
--- a/MediaBrowser.Controller/Entities/Year.cs
+++ b/MediaBrowser.Controller/Entities/Year.cs
@@ -26,5 +26,30 @@ namespace MediaBrowser.Controller.Entities
{
return "Year-" + Name;
}
+
+ /// <summary>
+ /// Returns the folder containing the item.
+ /// If the item is a folder, it returns the folder itself
+ /// </summary>
+ /// <value>The containing folder path.</value>
+ public override string ContainingFolderPath
+ {
+ get
+ {
+ return Path;
+ }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is owned item.
+ /// </summary>
+ /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+ public override bool IsOwnedItem
+ {
+ get
+ {
+ return false;
+ }
+ }
}
}