2013-09-17 03:41:49 +00:00
using MediaBrowser.Common.Extensions ;
2013-08-25 17:18:56 +00:00
using MediaBrowser.Controller.Configuration ;
2013-09-18 18:49:06 +00:00
using MediaBrowser.Controller.Entities.TV ;
2013-02-21 01:33:05 +00:00
using MediaBrowser.Controller.IO ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Localization ;
2013-04-13 18:02:30 +00:00
using MediaBrowser.Controller.Persistence ;
2013-02-21 01:33:05 +00:00
using MediaBrowser.Controller.Providers ;
2013-03-03 16:53:58 +00:00
using MediaBrowser.Controller.Resolvers ;
2013-02-21 01:33:05 +00:00
using MediaBrowser.Model.Entities ;
2013-02-22 01:26:35 +00:00
using MediaBrowser.Model.Logging ;
2013-02-21 01:33:05 +00:00
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Runtime.Serialization ;
using System.Threading ;
using System.Threading.Tasks ;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Class BaseItem
/// </summary>
2013-09-27 15:23:27 +00:00
public abstract class BaseItem : IHasProviderIds , ILibraryItem
2013-02-21 01:33:05 +00:00
{
2013-08-30 23:55:17 +00:00
/// <summary>
/// MusicAlbums in the library that are the soundtrack for this item
/// </summary>
public List < Guid > SoundtrackIds { get ; set ; }
2013-09-04 13:19:03 +00:00
2013-04-22 04:38:03 +00:00
protected BaseItem ( )
{
Genres = new List < string > ( ) ;
Studios = new List < string > ( ) ;
People = new List < PersonInfo > ( ) ;
2013-04-30 14:51:34 +00:00
ScreenshotImagePaths = new List < string > ( ) ;
BackdropImagePaths = new List < string > ( ) ;
ProductionLocations = new List < string > ( ) ;
2013-05-02 22:32:15 +00:00
Images = new Dictionary < ImageType , string > ( ) ;
ProviderIds = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
2013-05-06 22:52:13 +00:00
Tags = new List < string > ( ) ;
2013-05-08 20:58:52 +00:00
ThemeSongIds = new List < Guid > ( ) ;
ThemeVideoIds = new List < Guid > ( ) ;
2013-08-30 23:55:17 +00:00
SoundtrackIds = new List < Guid > ( ) ;
2013-05-08 20:58:52 +00:00
LocalTrailerIds = new List < Guid > ( ) ;
2013-06-09 14:16:43 +00:00
LockedFields = new List < MetadataFields > ( ) ;
2013-09-11 17:54:59 +00:00
Taglines = new List < string > ( ) ;
RemoteTrailers = new List < MediaUrl > ( ) ;
2013-04-22 04:38:03 +00:00
}
2013-05-30 22:22:15 +00:00
/// <summary>
/// The supported image extensions
/// </summary>
2013-10-13 05:56:44 +00:00
public static readonly string [ ] SupportedImageExtensions = new [ ] { ".png" , ".jpg" , ".jpeg" , ".tbn" } ;
2013-06-13 18:17:42 +00:00
2013-03-15 19:13:22 +00:00
/// <summary>
2013-02-21 01:33:05 +00:00
/// The trailer folder name
/// </summary>
public const string TrailerFolderName = "trailers" ;
2013-04-24 16:03:10 +00:00
public const string ThemeSongsFolderName = "theme-music" ;
2013-06-01 22:18:27 +00:00
public const string ThemeSongFilename = "theme" ;
2013-04-28 18:30:58 +00:00
public const string ThemeVideosFolderName = "backdrops" ;
2013-05-20 17:04:39 +00:00
public const string XbmcTrailerFileSuffix = "-trailer" ;
2013-02-21 01:33:05 +00:00
2013-08-15 19:09:52 +00:00
public bool IsInMixedFolder { get ; set ; }
2013-09-04 13:19:03 +00:00
2013-05-06 02:40:12 +00:00
private string _name ;
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
2013-08-07 19:15:55 +00:00
public string Name
2013-05-06 02:40:12 +00:00
{
get
{
return _name ;
}
set
{
_name = value ;
// lazy load this again
_sortName = null ;
}
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
2013-06-13 18:17:42 +00:00
public Guid Id { get ; set ; }
2013-02-21 01:33:05 +00:00
2013-09-11 17:54:59 +00:00
/// <summary>
/// Gets or sets the budget.
/// </summary>
/// <value>The budget.</value>
public double? Budget { get ; set ; }
/// <summary>
/// Gets or sets the taglines.
/// </summary>
/// <value>The taglines.</value>
public List < string > Taglines { get ; set ; }
/// <summary>
/// Gets or sets the revenue.
/// </summary>
/// <value>The revenue.</value>
public double? Revenue { get ; set ; }
/// <summary>
/// Gets or sets the critic rating.
/// </summary>
/// <value>The critic rating.</value>
public float? CriticRating { get ; set ; }
/// <summary>
/// Gets or sets the critic rating summary.
/// </summary>
/// <value>The critic rating summary.</value>
public string CriticRatingSummary { get ; set ; }
/// <summary>
/// Gets or sets the trailer URL.
/// </summary>
/// <value>The trailer URL.</value>
public List < MediaUrl > RemoteTrailers { get ; set ; }
2013-09-13 15:04:19 +00:00
2013-08-25 17:18:56 +00:00
/// <summary>
/// Return the id that should be used to key display prefs for this item.
/// Default is based on the type for everything except actual generic folders.
/// </summary>
/// <value>The display prefs id.</value>
[IgnoreDataMember]
public virtual Guid DisplayPreferencesId
{
get
{
var thisType = GetType ( ) ;
return thisType = = typeof ( Folder ) ? Id : thisType . FullName . GetMD5 ( ) ;
}
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
public virtual string Path { get ; set ; }
2013-07-05 14:54:14 +00:00
[IgnoreDataMember]
protected internal bool IsOffline { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the type of the location.
/// </summary>
/// <value>The type of the location.</value>
public virtual LocationType LocationType
{
get
{
2013-07-05 14:54:14 +00:00
if ( IsOffline )
{
return LocationType . Offline ;
}
2013-02-21 01:33:05 +00:00
if ( string . IsNullOrEmpty ( Path ) )
{
return LocationType . Virtual ;
}
return System . IO . Path . IsPathRooted ( Path ) ? LocationType . FileSystem : LocationType . Remote ;
}
}
/// <summary>
/// This is just a helper for convenience
/// </summary>
/// <value>The primary image path.</value>
[IgnoreDataMember]
2013-04-05 04:12:05 +00:00
public string PrimaryImagePath
2013-02-21 01:33:05 +00:00
{
get { return GetImage ( ImageType . Primary ) ; }
set { SetImage ( ImageType . Primary , value ) ; }
}
/// <summary>
/// Gets or sets the images.
/// </summary>
/// <value>The images.</value>
2013-09-04 13:19:03 +00:00
public Dictionary < ImageType , string > Images { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the date created.
/// </summary>
/// <value>The date created.</value>
public DateTime DateCreated { get ; set ; }
/// <summary>
/// Gets or sets the date modified.
/// </summary>
/// <value>The date modified.</value>
public DateTime DateModified { get ; set ; }
2013-02-21 20:26:35 +00:00
/// <summary>
/// The logger
/// </summary>
2013-03-08 05:08:27 +00:00
public static ILogger Logger { get ; set ; }
public static ILibraryManager LibraryManager { get ; set ; }
public static IServerConfigurationManager ConfigurationManager { get ; set ; }
public static IProviderManager ProviderManager { get ; set ; }
2013-06-10 17:46:11 +00:00
public static ILocalizationManager LocalizationManager { get ; set ; }
2013-06-20 16:44:24 +00:00
public static IItemRepository ItemRepository { get ; set ; }
2013-02-21 20:26:35 +00:00
2013-02-21 01:33:05 +00:00
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>A <see cref="System.String" /> that represents this instance.</returns>
public override string ToString ( )
{
return Name ;
}
/// <summary>
/// Returns true if this item should not attempt to fetch metadata
/// </summary>
/// <value><c>true</c> if [dont fetch meta]; otherwise, <c>false</c>.</value>
2013-06-09 13:31:23 +00:00
public bool DontFetchMeta { get ; set ; }
2013-02-21 01:33:05 +00:00
2013-06-09 14:15:59 +00:00
/// <summary>
/// Gets or sets the locked fields.
/// </summary>
/// <value>The locked fields.</value>
public List < MetadataFields > LockedFields { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// Determines whether the item has a saved local image of the specified name (jpg or png).
/// </summary>
/// <param name="name">The name.</param>
/// <returns><c>true</c> if [has local image] [the specified item]; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException">name</exception>
public bool HasLocalImage ( string name )
{
if ( string . IsNullOrEmpty ( name ) )
{
throw new ArgumentNullException ( "name" ) ;
}
return ResolveArgs . ContainsMetaFileByName ( name + ".jpg" ) | |
ResolveArgs . ContainsMetaFileByName ( name + ".png" ) ;
}
/// <summary>
/// Should be overridden to return the proper folder where metadata lives
/// </summary>
/// <value>The meta location.</value>
[IgnoreDataMember]
public virtual string MetaLocation
{
get
{
return Path ? ? "" ;
}
}
/// <summary>
/// The _provider data
/// </summary>
private Dictionary < Guid , BaseProviderInfo > _providerData ;
/// <summary>
/// Holds persistent data for providers like last refresh date.
/// Providers can use this to determine if they need to refresh.
/// The BaseProviderInfo class can be extended to hold anything a provider may need.
/// Keyed by a unique provider ID.
/// </summary>
/// <value>The provider data.</value>
public Dictionary < Guid , BaseProviderInfo > ProviderData
{
get
{
return _providerData ? ? ( _providerData = new Dictionary < Guid , BaseProviderInfo > ( ) ) ;
}
set
{
_providerData = value ;
}
}
/// <summary>
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
[IgnoreDataMember]
public virtual string MediaType
{
get
{
return null ;
}
}
/// <summary>
/// The _resolve args
/// </summary>
private ItemResolveArgs _resolveArgs ;
/// <summary>
/// We attach these to the item so that we only ever have to hit the file system once
/// (this includes the children of the containing folder)
/// </summary>
/// <value>The resolve args.</value>
[IgnoreDataMember]
public ItemResolveArgs ResolveArgs
{
get
{
2013-09-25 22:41:25 +00:00
if ( _resolveArgs = = null )
2013-02-21 01:33:05 +00:00
{
2013-09-25 22:41:25 +00:00
try
{
_resolveArgs = CreateResolveArgs ( ) ;
}
catch ( IOException ex )
{
Logger . ErrorException ( "Error creating resolve args for {0}" , ex , Path ) ;
2013-07-07 15:53:38 +00:00
2013-09-25 22:41:25 +00:00
IsOffline = true ;
2013-02-21 01:33:05 +00:00
2013-09-25 22:41:25 +00:00
throw ;
}
2013-02-21 01:33:05 +00:00
}
return _resolveArgs ;
}
2013-09-26 15:49:37 +00:00
set
{
_resolveArgs = value ;
}
2013-02-21 01:33:05 +00:00
}
/// <summary>
/// Resets the resolve args.
/// </summary>
/// <param name="pathInfo">The path info.</param>
2013-04-28 05:29:27 +00:00
public void ResetResolveArgs ( FileSystemInfo pathInfo )
2013-02-21 01:33:05 +00:00
{
2013-09-25 18:05:21 +00:00
ResetResolveArgs ( CreateResolveArgs ( pathInfo ) ) ;
}
2013-09-26 15:49:37 +00:00
/// <summary>
/// Resets the resolve args.
/// </summary>
2013-09-25 18:05:21 +00:00
public void ResetResolveArgs ( )
{
2013-09-25 22:41:25 +00:00
_resolveArgs = null ;
2013-09-25 18:05:21 +00:00
}
2013-09-26 15:49:37 +00:00
/// <summary>
/// Resets the resolve args.
/// </summary>
/// <param name="args">The args.</param>
2013-09-25 18:05:21 +00:00
public void ResetResolveArgs ( ItemResolveArgs args )
{
2013-09-25 22:41:25 +00:00
_resolveArgs = args ;
2013-02-21 01:33:05 +00:00
}
/// <summary>
/// Creates ResolveArgs on demand
/// </summary>
/// <param name="pathInfo">The path info.</param>
/// <returns>ItemResolveArgs.</returns>
/// <exception cref="System.IO.IOException">Unable to retrieve file system info for + path</exception>
2013-04-28 05:29:27 +00:00
protected internal virtual ItemResolveArgs CreateResolveArgs ( FileSystemInfo pathInfo = null )
2013-02-21 01:33:05 +00:00
{
var path = Path ;
2013-07-07 15:53:38 +00:00
if ( LocationType = = LocationType . Remote | | LocationType = = LocationType . Virtual )
2013-02-21 01:33:05 +00:00
{
2013-09-04 17:07:35 +00:00
return new ItemResolveArgs ( ConfigurationManager . ApplicationPaths , LibraryManager ) ;
2013-02-21 01:33:05 +00:00
}
2013-06-11 20:35:54 +00:00
var isDirectory = false ;
2013-02-21 01:33:05 +00:00
if ( UseParentPathToCreateResolveArgs )
{
path = System . IO . Path . GetDirectoryName ( path ) ;
2013-06-11 20:35:54 +00:00
isDirectory = true ;
2013-02-21 01:33:05 +00:00
}
2013-07-07 15:53:38 +00:00
pathInfo = pathInfo ? ? ( isDirectory ? new DirectoryInfo ( path ) : FileSystem . GetFileSystemInfo ( path ) ) ;
2013-02-21 01:33:05 +00:00
2013-04-28 05:29:27 +00:00
if ( pathInfo = = null | | ! pathInfo . Exists )
2013-02-21 01:33:05 +00:00
{
throw new IOException ( "Unable to retrieve file system info for " + path ) ;
}
2013-06-23 17:48:30 +00:00
2013-09-04 17:07:35 +00:00
var args = new ItemResolveArgs ( ConfigurationManager . ApplicationPaths , LibraryManager )
2013-02-21 01:33:05 +00:00
{
2013-04-28 05:29:27 +00:00
FileInfo = pathInfo ,
2013-02-21 01:33:05 +00:00
Path = path ,
Parent = Parent
} ;
// Gather child folder and files
if ( args . IsDirectory )
{
2013-04-19 18:03:21 +00:00
var isPhysicalRoot = args . IsPhysicalRoot ;
2013-02-21 01:33:05 +00:00
// When resolving the root, we need it's grandchildren (children of user views)
2013-04-19 18:03:21 +00:00
var flattenFolderDepth = isPhysicalRoot ? 2 : 0 ;
2013-02-21 01:33:05 +00:00
2013-07-12 19:56:40 +00:00
args . FileSystemDictionary = FileData . GetFilteredFileSystemEntries ( args . Path , Logger , args , flattenFolderDepth : flattenFolderDepth , resolveShortcuts : isPhysicalRoot | | args . IsVf ) ;
2013-05-24 17:48:48 +00:00
// Need to remove subpaths that may have been resolved from shortcuts
// Example: if \\server\movies exists, then strip out \\server\movies\action
if ( isPhysicalRoot )
{
var paths = args . FileSystemDictionary . Keys . ToList ( ) ;
2013-05-27 01:24:07 +00:00
foreach ( var subPath in paths
. Where ( subPath = > ! subPath . EndsWith ( ":\\" , StringComparison . OrdinalIgnoreCase ) & & paths . Any ( i = > subPath . StartsWith ( i . TrimEnd ( System . IO . Path . DirectorySeparatorChar ) + System . IO . Path . DirectorySeparatorChar , StringComparison . OrdinalIgnoreCase ) ) ) )
2013-05-24 17:48:48 +00:00
{
2013-05-27 01:24:07 +00:00
Logger . Info ( "Ignoring duplicate path: {0}" , subPath ) ;
2013-05-24 17:48:48 +00:00
args . FileSystemDictionary . Remove ( subPath ) ;
}
}
2013-02-21 01:33:05 +00:00
}
//update our dates
2013-08-14 12:17:45 +00:00
EntityResolutionHelper . EnsureDates ( this , args , false ) ;
2013-02-21 01:33:05 +00:00
2013-07-07 15:53:38 +00:00
IsOffline = false ;
2013-02-21 01:33:05 +00:00
return args ;
}
/// <summary>
/// Some subclasses will stop resolving at a directory and point their Path to a file within. This will help ensure the on-demand resolve args are identical to the
/// original ones.
/// </summary>
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
protected virtual bool UseParentPathToCreateResolveArgs
{
get
{
return false ;
}
}
2013-03-12 01:46:46 +00:00
/// <summary>
/// Gets or sets the name of the forced sort.
/// </summary>
/// <value>The name of the forced sort.</value>
public string ForcedSortName { get ; set ; }
private string _sortName ;
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the name of the sort.
/// </summary>
/// <value>The name of the sort.</value>
2013-03-12 01:46:46 +00:00
[IgnoreDataMember]
public string SortName
{
get
{
2013-08-25 19:54:18 +00:00
if ( ! string . IsNullOrEmpty ( ForcedSortName ) )
{
return ForcedSortName ;
}
return _sortName ? ? ( _sortName = CreateSortName ( ) ) ;
2013-03-12 01:46:46 +00:00
}
}
/// <summary>
/// Creates the name of the sort.
/// </summary>
/// <returns>System.String.</returns>
protected virtual string CreateSortName ( )
{
if ( Name = = null ) return null ; //some items may not have name filled in properly
var sortable = Name . Trim ( ) . ToLower ( ) ;
sortable = ConfigurationManager . Configuration . SortRemoveCharacters . Aggregate ( sortable , ( current , search ) = > current . Replace ( search . ToLower ( ) , string . Empty ) ) ;
sortable = ConfigurationManager . Configuration . SortReplaceCharacters . Aggregate ( sortable , ( current , search ) = > current . Replace ( search . ToLower ( ) , " " ) ) ;
foreach ( var search in ConfigurationManager . Configuration . SortRemoveWords )
{
var searchLower = search . ToLower ( ) ;
// Remove from beginning if a space follows
if ( sortable . StartsWith ( searchLower + " " ) )
{
sortable = sortable . Remove ( 0 , searchLower . Length + 1 ) ;
}
// Remove from middle if surrounded by spaces
sortable = sortable . Replace ( " " + searchLower + " " , " " ) ;
// Remove from end if followed by a space
if ( sortable . EndsWith ( " " + searchLower ) )
{
sortable = sortable . Remove ( sortable . Length - ( searchLower . Length + 1 ) ) ;
}
}
return sortable ;
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the parent.
/// </summary>
/// <value>The parent.</value>
[IgnoreDataMember]
public Folder Parent { get ; set ; }
/// <summary>
/// When the item first debuted. For movies this could be premiere date, episodes would be first aired
/// </summary>
/// <value>The premiere date.</value>
public DateTime ? PremiereDate { get ; set ; }
2013-04-12 14:13:47 +00:00
/// <summary>
/// Gets or sets the end date.
/// </summary>
/// <value>The end date.</value>
public DateTime ? EndDate { get ; set ; }
2013-04-13 23:43:41 +00:00
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the display type of the media.
/// </summary>
/// <value>The display type of the media.</value>
2013-06-14 12:18:40 +00:00
public string DisplayMediaType { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <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 screenshot image paths.
/// </summary>
/// <value>The screenshot image paths.</value>
public List < string > ScreenshotImagePaths { get ; set ; }
/// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
2013-08-03 13:24:23 +00:00
public string OfficialRating { get ; set ; }
2013-02-21 01:33:05 +00:00
2013-06-23 17:48:30 +00:00
/// <summary>
/// Gets or sets the official rating description.
/// </summary>
/// <value>The official rating description.</value>
public string OfficialRatingDescription { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the custom rating.
/// </summary>
/// <value>The custom rating.</value>
2013-08-03 13:24:23 +00:00
public string CustomRating { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the language.
/// </summary>
/// <value>The language.</value>
public string Language { get ; set ; }
/// <summary>
/// Gets or sets the overview.
/// </summary>
/// <value>The overview.</value>
public string Overview { get ; set ; }
/// <summary>
/// Gets or sets the people.
/// </summary>
/// <value>The people.</value>
public List < PersonInfo > People { get ; set ; }
2013-05-06 22:52:13 +00:00
/// <summary>
/// Gets or sets the tags.
/// </summary>
/// <value>The tags.</value>
public List < string > Tags { get ; set ; }
2013-05-08 20:58:52 +00:00
2013-02-21 01:33:05 +00:00
/// <summary>
/// Override this if you need to combine/collapse person information
/// </summary>
/// <value>All people.</value>
[IgnoreDataMember]
public virtual IEnumerable < PersonInfo > AllPeople
{
get { return People ; }
}
2013-08-03 13:24:23 +00:00
[IgnoreDataMember]
public virtual IEnumerable < string > AllStudios
{
get { return Studios ; }
}
[IgnoreDataMember]
public virtual IEnumerable < string > AllGenres
{
get { return Genres ; }
}
2013-08-07 15:59:13 +00:00
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the studios.
/// </summary>
/// <value>The studios.</value>
2013-08-07 15:59:13 +00:00
public List < string > Studios { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the genres.
/// </summary>
/// <value>The genres.</value>
2013-08-07 17:11:02 +00:00
public List < string > Genres { get ; set ; }
2013-02-21 01:33:05 +00:00
2013-04-12 14:13:47 +00:00
/// <summary>
/// Gets or sets the home page URL.
/// </summary>
/// <value>The home page URL.</value>
public string HomePageUrl { get ; set ; }
/// <summary>
/// Gets or sets the production locations.
/// </summary>
/// <value>The production locations.</value>
public List < string > ProductionLocations { get ; set ; }
2013-04-13 23:43:41 +00:00
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the community rating.
/// </summary>
/// <value>The community rating.</value>
public float? CommunityRating { get ; set ; }
2013-07-05 17:40:51 +00:00
/// <summary>
/// Gets or sets the community rating vote count.
/// </summary>
/// <value>The community rating vote count.</value>
2013-07-06 00:19:44 +00:00
public int? VoteCount { get ; set ; }
2013-07-05 17:40:51 +00:00
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>
/// <value>The run time ticks.</value>
public long? RunTimeTicks { get ; set ; }
2013-05-17 19:18:54 +00:00
/// <summary>
/// Gets or sets the original run time ticks.
/// </summary>
/// <value>The original run time ticks.</value>
public long? OriginalRunTimeTicks { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the aspect ratio.
/// </summary>
/// <value>The aspect ratio.</value>
public string AspectRatio { get ; set ; }
/// <summary>
/// Gets or sets the production year.
/// </summary>
/// <value>The production year.</value>
2013-08-07 15:59:13 +00:00
public int? ProductionYear { get ; set ; }
2013-02-21 01:33:05 +00:00
/// <summary>
/// If the item is part of a series, this is it's number in the series.
/// This could be episode number, album track number, etc.
/// </summary>
/// <value>The index number.</value>
public int? IndexNumber { get ; set ; }
/// <summary>
/// For an episode this could be the season number, or for a song this could be the disc number.
/// </summary>
/// <value>The parent index number.</value>
public int? ParentIndexNumber { get ; set ; }
2013-05-08 20:58:52 +00:00
public List < Guid > ThemeSongIds { get ; set ; }
public List < Guid > ThemeVideoIds { get ; set ; }
public List < Guid > LocalTrailerIds { get ; set ; }
2013-04-28 16:25:14 +00:00
2013-08-03 13:24:23 +00:00
[IgnoreDataMember]
public virtual string OfficialRatingForComparison
{
get { return OfficialRating ; }
}
[IgnoreDataMember]
public virtual string CustomRatingForComparison
{
get { return CustomRating ; }
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Loads local trailers from the file system
/// </summary>
/// <returns>List{Video}.</returns>
2013-05-09 17:38:02 +00:00
private IEnumerable < Trailer > LoadLocalTrailers ( )
2013-02-21 01:33:05 +00:00
{
ItemResolveArgs resolveArgs ;
try
{
resolveArgs = ResolveArgs ;
2013-07-05 13:47:10 +00:00
if ( ! resolveArgs . IsDirectory )
{
return new List < Trailer > ( ) ;
}
2013-02-21 01:33:05 +00:00
}
catch ( IOException ex )
{
2013-02-21 20:26:35 +00:00
Logger . ErrorException ( "Error getting ResolveArgs for {0}" , ex , Path ) ;
2013-04-25 19:47:38 +00:00
return new List < Trailer > ( ) ;
2013-02-21 01:33:05 +00:00
}
2013-05-20 17:04:39 +00:00
var files = new List < FileSystemInfo > ( ) ;
2013-02-21 01:33:05 +00:00
var folder = resolveArgs . GetFileSystemEntryByName ( TrailerFolderName ) ;
// Path doesn't exist. No biggie
2013-05-20 17:04:39 +00:00
if ( folder ! = null )
2013-02-21 01:33:05 +00:00
{
2013-05-20 17:04:39 +00:00
try
{
files . AddRange ( new DirectoryInfo ( folder . FullName ) . EnumerateFiles ( ) ) ;
}
catch ( IOException ex )
{
Logger . ErrorException ( "Error loading trailers for {0}" , ex , Name ) ;
}
2013-02-21 01:33:05 +00:00
}
2013-05-20 17:04:39 +00:00
// Support xbmc trailers (-trailer suffix on video file names)
files . AddRange ( resolveArgs . FileSystemChildren . Where ( i = >
2013-02-21 01:33:05 +00:00
{
2013-07-05 14:54:14 +00:00
try
2013-05-20 17:04:39 +00:00
{
2013-07-05 14:54:14 +00:00
if ( ( i . Attributes & FileAttributes . Directory ) ! = FileAttributes . Directory )
2013-05-20 17:04:39 +00:00
{
2013-07-05 14:54:14 +00:00
if ( System . IO . Path . GetFileNameWithoutExtension ( i . Name ) . EndsWith ( XbmcTrailerFileSuffix , StringComparison . OrdinalIgnoreCase ) & & ! string . Equals ( Path , i . FullName , StringComparison . OrdinalIgnoreCase ) )
{
return true ;
}
2013-05-20 17:04:39 +00:00
}
}
2013-07-05 14:54:14 +00:00
catch ( IOException ex )
{
Logger . ErrorException ( "Error accessing path {0}" , ex , i . FullName ) ;
}
2013-02-21 01:33:05 +00:00
2013-05-20 17:04:39 +00:00
return false ;
} ) ) ;
2013-06-13 18:45:58 +00:00
return LibraryManager . ResolvePaths < Trailer > ( files , null ) . Select ( video = >
2013-02-21 01:33:05 +00:00
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
2013-06-26 16:08:16 +00:00
var dbItem = LibraryManager . RetrieveItem ( video . Id ) as Trailer ;
2013-02-21 01:33:05 +00:00
if ( dbItem ! = null )
{
2013-09-25 22:41:25 +00:00
dbItem . ResetResolveArgs ( video . ResolveArgs ) ;
2013-02-21 01:33:05 +00:00
video = dbItem ;
}
return video ;
2013-05-20 17:04:39 +00:00
2013-06-13 18:45:58 +00:00
} ) . ToList ( ) ;
2013-02-21 01:33:05 +00:00
}
2013-04-24 16:03:10 +00:00
/// <summary>
/// Loads the theme songs.
/// </summary>
/// <returns>List{Audio.Audio}.</returns>
2013-05-09 17:38:02 +00:00
private IEnumerable < Audio . Audio > LoadThemeSongs ( )
2013-04-24 16:03:10 +00:00
{
ItemResolveArgs resolveArgs ;
try
{
resolveArgs = ResolveArgs ;
2013-07-05 13:47:10 +00:00
if ( ! resolveArgs . IsDirectory )
{
return new List < Audio . Audio > ( ) ;
}
2013-04-24 16:03:10 +00:00
}
catch ( IOException ex )
{
Logger . ErrorException ( "Error getting ResolveArgs for {0}" , ex , Path ) ;
return new List < Audio . Audio > ( ) ;
}
2013-05-30 18:19:30 +00:00
var files = new List < FileSystemInfo > ( ) ;
2013-04-24 16:03:10 +00:00
var folder = resolveArgs . GetFileSystemEntryByName ( ThemeSongsFolderName ) ;
// Path doesn't exist. No biggie
2013-05-30 18:19:30 +00:00
if ( folder ! = null )
2013-04-24 16:03:10 +00:00
{
2013-05-30 18:19:30 +00:00
try
{
files . AddRange ( new DirectoryInfo ( folder . FullName ) . EnumerateFiles ( ) ) ;
}
catch ( IOException ex )
{
Logger . ErrorException ( "Error loading theme songs for {0}" , ex , Name ) ;
}
2013-04-24 16:03:10 +00:00
}
2013-05-30 18:19:30 +00:00
// Support plex/xbmc convention
files . AddRange ( resolveArgs . FileSystemChildren
2013-06-18 14:47:15 +00:00
. Where ( i = > string . Equals ( System . IO . Path . GetFileNameWithoutExtension ( i . Name ) , ThemeSongFilename , StringComparison . OrdinalIgnoreCase ) & & EntityResolutionHelper . IsAudioFile ( i . Name ) )
2013-05-30 18:19:30 +00:00
) ;
2013-04-24 16:03:10 +00:00
return LibraryManager . ResolvePaths < Audio . Audio > ( files , null ) . Select ( audio = >
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
2013-06-26 16:08:16 +00:00
var dbItem = LibraryManager . RetrieveItem ( audio . Id ) as Audio . Audio ;
2013-04-24 16:03:10 +00:00
if ( dbItem ! = null )
{
2013-09-25 22:41:25 +00:00
dbItem . ResetResolveArgs ( audio . ResolveArgs ) ;
2013-04-24 16:03:10 +00:00
audio = dbItem ;
}
return audio ;
} ) . ToList ( ) ;
}
2013-04-28 16:25:14 +00:00
/// <summary>
/// Loads the video backdrops.
/// </summary>
/// <returns>List{Video}.</returns>
2013-05-09 17:38:02 +00:00
private IEnumerable < Video > LoadThemeVideos ( )
2013-04-28 16:25:14 +00:00
{
ItemResolveArgs resolveArgs ;
try
{
resolveArgs = ResolveArgs ;
2013-07-05 13:47:10 +00:00
if ( ! resolveArgs . IsDirectory )
{
return new List < Video > ( ) ;
}
2013-04-28 16:25:14 +00:00
}
catch ( IOException ex )
{
Logger . ErrorException ( "Error getting ResolveArgs for {0}" , ex , Path ) ;
return new List < Video > ( ) ;
}
2013-04-28 18:30:58 +00:00
var folder = resolveArgs . GetFileSystemEntryByName ( ThemeVideosFolderName ) ;
2013-04-28 16:25:14 +00:00
// Path doesn't exist. No biggie
if ( folder = = null )
{
return new List < Video > ( ) ;
}
IEnumerable < FileSystemInfo > files ;
try
{
files = new DirectoryInfo ( folder . FullName ) . EnumerateFiles ( ) ;
}
catch ( IOException ex )
{
Logger . ErrorException ( "Error loading video backdrops for {0}" , ex , Name ) ;
return new List < Video > ( ) ;
}
return LibraryManager . ResolvePaths < Video > ( files , null ) . Select ( item = >
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
2013-06-26 16:08:16 +00:00
var dbItem = LibraryManager . RetrieveItem ( item . Id ) as Video ;
2013-04-28 16:25:14 +00:00
if ( dbItem ! = null )
{
2013-09-25 22:41:25 +00:00
dbItem . ResetResolveArgs ( item . ResolveArgs ) ;
2013-04-28 16:25:14 +00:00
item = dbItem ;
}
return item ;
} ) . ToList ( ) ;
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Overrides the base implementation to refresh metadata for local trailers
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="forceSave">if set to <c>true</c> [is new item].</param>
/// <param name="forceRefresh">if set to <c>true</c> [force].</param>
/// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
2013-06-20 19:07:58 +00:00
/// <param name="resetResolveArgs">if set to <c>true</c> [reset resolve args].</param>
2013-02-21 01:33:05 +00:00
/// <returns>true if a provider reports we changed</returns>
2013-06-20 19:07:58 +00:00
public virtual async Task < bool > RefreshMetadata ( CancellationToken cancellationToken , bool forceSave = false , bool forceRefresh = false , bool allowSlowProviders = true , bool resetResolveArgs = true )
2013-02-21 01:33:05 +00:00
{
2013-09-25 18:05:21 +00:00
if ( resetResolveArgs | | ResolveArgs = = null )
2013-06-20 19:07:58 +00:00
{
// Reload this
2013-09-25 18:05:21 +00:00
ResetResolveArgs ( ) ;
2013-06-20 19:07:58 +00:00
}
2013-02-21 01:33:05 +00:00
// Refresh for the item
2013-03-08 05:08:27 +00:00
var itemRefreshTask = ProviderManager . ExecuteMetadataProviders ( this , cancellationToken , forceRefresh , allowSlowProviders ) ;
2013-02-21 01:33:05 +00:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
2013-06-17 20:35:43 +00:00
var themeSongsChanged = false ;
2013-02-21 01:33:05 +00:00
2013-06-17 20:35:43 +00:00
var themeVideosChanged = false ;
2013-04-28 16:25:14 +00:00
2013-06-17 20:35:43 +00:00
var localTrailersChanged = false ;
2013-02-21 01:33:05 +00:00
2013-06-17 20:35:43 +00:00
if ( LocationType = = LocationType . FileSystem & & Parent ! = null )
{
themeSongsChanged = await RefreshThemeSongs ( cancellationToken , forceSave , forceRefresh , allowSlowProviders ) . ConfigureAwait ( false ) ;
themeVideosChanged = await RefreshThemeVideos ( cancellationToken , forceSave , forceRefresh , allowSlowProviders ) . ConfigureAwait ( false ) ;
localTrailersChanged = await RefreshLocalTrailers ( cancellationToken , forceSave , forceRefresh , allowSlowProviders ) . ConfigureAwait ( false ) ;
}
2013-02-21 01:33:05 +00:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
// Get the result from the item task
2013-06-25 01:22:21 +00:00
var updateReason = await itemRefreshTask . ConfigureAwait ( false ) ;
var changed = updateReason . HasValue ;
2013-02-21 01:33:05 +00:00
2013-05-08 20:58:52 +00:00
if ( changed | | forceSave | | themeSongsChanged | | themeVideosChanged | | localTrailersChanged )
2013-02-21 01:33:05 +00:00
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
2013-06-25 01:22:21 +00:00
await LibraryManager . UpdateItem ( this , updateReason ? ? ItemUpdateType . Unspecified , cancellationToken ) . ConfigureAwait ( false ) ;
2013-02-21 01:33:05 +00:00
}
return changed ;
}
2013-05-08 20:58:52 +00:00
private async Task < bool > RefreshLocalTrailers ( CancellationToken cancellationToken , bool forceSave = false , bool forceRefresh = false , bool allowSlowProviders = true )
{
var newItems = LoadLocalTrailers ( ) . ToList ( ) ;
var newItemIds = newItems . Select ( i = > i . Id ) . ToList ( ) ;
var itemsChanged = ! LocalTrailerIds . SequenceEqual ( newItemIds ) ;
2013-09-18 18:49:06 +00:00
var tasks = newItems . Select ( i = > i . RefreshMetadata ( cancellationToken , forceSave , forceRefresh , allowSlowProviders , resetResolveArgs : false ) ) ;
2013-05-08 20:58:52 +00:00
var results = await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
LocalTrailerIds = newItemIds ;
return itemsChanged | | results . Contains ( true ) ;
}
2013-06-13 18:17:42 +00:00
2013-05-08 20:58:52 +00:00
private async Task < bool > RefreshThemeVideos ( CancellationToken cancellationToken , bool forceSave = false , bool forceRefresh = false , bool allowSlowProviders = true )
{
var newThemeVideos = LoadThemeVideos ( ) . ToList ( ) ;
var newThemeVideoIds = newThemeVideos . Select ( i = > i . Id ) . ToList ( ) ;
var themeVideosChanged = ! ThemeVideoIds . SequenceEqual ( newThemeVideoIds ) ;
2013-09-18 18:49:06 +00:00
var tasks = newThemeVideos . Select ( i = > i . RefreshMetadata ( cancellationToken , forceSave , forceRefresh , allowSlowProviders , resetResolveArgs : false ) ) ;
2013-05-08 20:58:52 +00:00
var results = await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
ThemeVideoIds = newThemeVideoIds ;
return themeVideosChanged | | results . Contains ( true ) ;
}
2013-06-13 18:17:42 +00:00
2013-05-08 20:58:52 +00:00
/// <summary>
/// Refreshes the theme songs.
/// </summary>
private async Task < bool > RefreshThemeSongs ( CancellationToken cancellationToken , bool forceSave = false , bool forceRefresh = false , bool allowSlowProviders = true )
{
var newThemeSongs = LoadThemeSongs ( ) . ToList ( ) ;
var newThemeSongIds = newThemeSongs . Select ( i = > i . Id ) . ToList ( ) ;
var themeSongsChanged = ! ThemeSongIds . SequenceEqual ( newThemeSongIds ) ;
2013-09-18 18:49:06 +00:00
var tasks = newThemeSongs . Select ( i = > i . RefreshMetadata ( cancellationToken , forceSave , forceRefresh , allowSlowProviders , resetResolveArgs : false ) ) ;
2013-05-08 20:58:52 +00:00
var results = await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
ThemeSongIds = newThemeSongIds ;
return themeSongsChanged | | results . Contains ( true ) ;
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Gets or sets the provider ids.
/// </summary>
/// <value>The provider ids.</value>
public Dictionary < string , string > ProviderIds { get ; set ; }
/// <summary>
/// Override this to false if class should be ignored for indexing purposes
/// </summary>
/// <value><c>true</c> if [include in index]; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
public virtual bool IncludeInIndex
{
get { return true ; }
}
/// <summary>
/// Override this to true if class should be grouped under a container in indicies
/// The container class should be defined via IndexContainer
/// </summary>
/// <value><c>true</c> if [group in index]; otherwise, <c>false</c>.</value>
[IgnoreDataMember]
public virtual bool GroupInIndex
{
get { return false ; }
}
/// <summary>
/// Override this to return the folder that should be used to construct a container
/// for this item in an index. GroupInIndex should be true as well.
/// </summary>
/// <value>The index container.</value>
[IgnoreDataMember]
public virtual Folder IndexContainer
{
get { return null ; }
}
/// <summary>
2013-04-13 18:02:30 +00:00
/// Gets the user data key.
2013-02-21 01:33:05 +00:00
/// </summary>
2013-04-13 18:02:30 +00:00
/// <returns>System.String.</returns>
public virtual string GetUserDataKey ( )
2013-02-21 01:33:05 +00:00
{
2013-04-13 18:02:30 +00:00
return Id . ToString ( ) ;
2013-02-21 01:33:05 +00:00
}
/// <summary>
/// Determines if a given user has access to this item
/// </summary>
/// <param name="user">The user.</param>
2013-06-10 17:46:11 +00:00
/// <param name="localizationManager">The localization manager.</param>
2013-02-21 01:33:05 +00:00
/// <returns><c>true</c> if [is parental allowed] [the specified user]; otherwise, <c>false</c>.</returns>
2013-06-10 17:46:11 +00:00
/// <exception cref="System.ArgumentNullException">user</exception>
public bool IsParentalAllowed ( User user , ILocalizationManager localizationManager )
2013-02-21 01:33:05 +00:00
{
if ( user = = null )
{
2013-04-22 15:38:38 +00:00
throw new ArgumentNullException ( "user" ) ;
}
2013-09-18 23:33:21 +00:00
var maxAllowedRating = user . Configuration . MaxParentalRating ;
if ( maxAllowedRating = = null )
2013-04-22 15:38:38 +00:00
{
return true ;
2013-02-21 01:33:05 +00:00
}
2013-08-03 13:24:23 +00:00
var rating = CustomRatingForComparison ;
2013-06-10 17:46:11 +00:00
2013-07-16 17:18:32 +00:00
if ( string . IsNullOrEmpty ( rating ) )
{
2013-08-03 13:24:23 +00:00
rating = OfficialRatingForComparison ;
2013-07-16 17:18:32 +00:00
}
2013-08-07 15:59:13 +00:00
2013-06-12 02:59:57 +00:00
if ( string . IsNullOrEmpty ( rating ) )
2013-05-23 15:07:25 +00:00
{
2013-06-12 02:59:57 +00:00
return ! user . Configuration . BlockNotRated ;
2013-05-23 15:07:25 +00:00
}
2013-06-10 17:46:11 +00:00
var value = localizationManager . GetRatingLevel ( rating ) ;
// Could not determine the integer value
if ( ! value . HasValue )
{
2013-08-01 12:00:47 +00:00
return true ;
2013-06-10 17:46:11 +00:00
}
2013-09-18 23:33:21 +00:00
return value . Value < = maxAllowedRating . Value ;
2013-02-21 01:33:05 +00:00
}
/// <summary>
/// Determines if this folder should be visible to a given user.
/// Default is just parental allowed. Can be overridden for more functionality.
/// </summary>
/// <param name="user">The user.</param>
/// <returns><c>true</c> if the specified user is visible; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException">user</exception>
public virtual bool IsVisible ( User user )
{
if ( user = = null )
{
throw new ArgumentNullException ( "user" ) ;
}
2013-06-10 17:46:11 +00:00
return IsParentalAllowed ( user , LocalizationManager ) ;
2013-02-21 01:33:05 +00:00
}
/// <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 ;
}
2013-06-17 20:35:43 +00:00
return null ;
2013-02-21 01:33:05 +00:00
}
/// <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>
[IgnoreDataMember]
public virtual bool IsFolder
{
get
{
return false ;
}
}
/// <summary>
/// Determine if we have changed vs the passed in copy
/// </summary>
/// <param name="copy">The copy.</param>
/// <returns><c>true</c> if the specified copy has changed; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
public virtual bool HasChanged ( BaseItem copy )
{
if ( copy = = null )
{
throw new ArgumentNullException ( ) ;
}
var changed = copy . DateModified ! = DateModified ;
if ( changed )
{
2013-02-21 20:26:35 +00:00
Logger . Debug ( Name + " changed - original creation: " + DateCreated + " new creation: " + copy . DateCreated + " original modified: " + DateModified + " new modified: " + copy . DateModified ) ;
2013-02-21 01:33:05 +00:00
}
return changed ;
}
/// <summary>
/// Determines if the item is considered new based on user settings
/// </summary>
/// <returns><c>true</c> if [is recently added] [the specified user]; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
2013-04-22 04:38:03 +00:00
public bool IsRecentlyAdded ( )
2013-02-21 01:33:05 +00:00
{
2013-03-04 05:43:06 +00:00
return ( DateTime . UtcNow - DateCreated ) . TotalDays < ConfigurationManager . Configuration . RecentItemDays ;
2013-02-21 01:33:05 +00:00
}
/// <summary>
/// Adds a person to the item
/// </summary>
/// <param name="person">The person.</param>
/// <exception cref="System.ArgumentNullException"></exception>
public void AddPerson ( PersonInfo person )
{
if ( person = = null )
{
2013-04-14 15:03:12 +00:00
throw new ArgumentNullException ( "person" ) ;
2013-02-21 01:33:05 +00:00
}
if ( string . IsNullOrWhiteSpace ( person . Name ) )
{
throw new ArgumentNullException ( ) ;
}
2013-07-12 19:56:40 +00:00
// Normalize
if ( string . Equals ( person . Role , PersonType . GuestStar , StringComparison . OrdinalIgnoreCase ) )
{
person . Type = PersonType . GuestStar ;
}
else if ( string . Equals ( person . Role , PersonType . Director , StringComparison . OrdinalIgnoreCase ) )
{
person . Type = PersonType . Director ;
}
else if ( string . Equals ( person . Role , PersonType . Producer , StringComparison . OrdinalIgnoreCase ) )
{
person . Type = PersonType . Producer ;
}
else if ( string . Equals ( person . Role , PersonType . Writer , StringComparison . OrdinalIgnoreCase ) )
{
person . Type = PersonType . Writer ;
}
2013-04-14 15:03:12 +00:00
// If the type is GuestStar and there's already an Actor entry, then update it to avoid dupes
if ( string . Equals ( person . Type , PersonType . GuestStar , StringComparison . OrdinalIgnoreCase ) )
{
var existing = People . FirstOrDefault ( p = > p . Name . Equals ( person . Name , StringComparison . OrdinalIgnoreCase ) & & p . Type . Equals ( PersonType . Actor , StringComparison . OrdinalIgnoreCase ) ) ;
if ( existing ! = null )
{
existing . Type = PersonType . GuestStar ;
return ;
}
}
2013-02-21 01:33:05 +00:00
2013-04-14 15:03:12 +00:00
if ( string . Equals ( person . Type , PersonType . Actor , StringComparison . OrdinalIgnoreCase ) )
2013-02-21 01:33:05 +00:00
{
2013-04-30 15:25:30 +00:00
// If the actor already exists without a role and we have one, fill it in
var existing = People . FirstOrDefault ( p = > p . Name . Equals ( person . Name , StringComparison . OrdinalIgnoreCase ) & & ( p . Type . Equals ( PersonType . Actor , StringComparison . OrdinalIgnoreCase ) | | p . Type . Equals ( PersonType . GuestStar , StringComparison . OrdinalIgnoreCase ) ) ) ;
if ( existing = = null )
2013-04-14 15:03:12 +00:00
{
2013-04-30 15:25:30 +00:00
// Wasn't there - add it
2013-04-14 15:03:12 +00:00
People . Add ( person ) ;
}
2013-04-30 15:25:30 +00:00
else
{
// Was there, if no role and we have one - fill it in
if ( string . IsNullOrWhiteSpace ( existing . Role ) & & ! string . IsNullOrWhiteSpace ( person . Role ) ) existing . Role = person . Role ;
}
2013-04-14 15:03:12 +00:00
}
else
{
// Check for dupes based on the combination of Name and Type
2013-08-29 21:00:27 +00:00
if ( ! People . Any ( p = > string . Equals ( p . Name , person . Name , StringComparison . OrdinalIgnoreCase ) & & string . Equals ( p . Type , person . Type , StringComparison . OrdinalIgnoreCase ) ) )
2013-04-14 15:03:12 +00:00
{
People . Add ( person ) ;
}
2013-02-21 01:33:05 +00:00
}
}
2013-09-13 15:04:19 +00:00
/// <summary>
/// Adds the tagline.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="tagline">The tagline.</param>
/// <exception cref="System.ArgumentNullException">tagline</exception>
public void AddTagline ( string tagline )
{
if ( string . IsNullOrWhiteSpace ( tagline ) )
{
throw new ArgumentNullException ( "tagline" ) ;
}
if ( ! Taglines . Contains ( tagline , StringComparer . OrdinalIgnoreCase ) )
{
Taglines . Add ( tagline ) ;
}
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Adds a studio to the item
/// </summary>
/// <param name="name">The name.</param>
/// <exception cref="System.ArgumentNullException"></exception>
public void AddStudio ( string name )
{
if ( string . IsNullOrWhiteSpace ( name ) )
{
2013-04-05 04:12:05 +00:00
throw new ArgumentNullException ( "name" ) ;
2013-02-21 01:33:05 +00:00
}
if ( ! Studios . Contains ( name , StringComparer . OrdinalIgnoreCase ) )
{
Studios . Add ( name ) ;
}
}
2013-06-23 18:55:30 +00:00
public void AddTag ( string name )
{
if ( string . IsNullOrWhiteSpace ( name ) )
{
throw new ArgumentNullException ( "name" ) ;
}
if ( ! Tags . Contains ( name , StringComparer . OrdinalIgnoreCase ) )
{
Tags . Add ( name ) ;
}
}
2013-02-21 01:33:05 +00:00
/// <summary>
/// Adds a genre to the item
/// </summary>
/// <param name="name">The name.</param>
/// <exception cref="System.ArgumentNullException"></exception>
public void AddGenre ( string name )
{
if ( string . IsNullOrWhiteSpace ( name ) )
{
2013-04-12 14:13:47 +00:00
throw new ArgumentNullException ( "name" ) ;
2013-02-21 01:33:05 +00:00
}
if ( ! Genres . Contains ( name , StringComparer . OrdinalIgnoreCase ) )
{
Genres . Add ( name ) ;
}
}
2013-04-12 14:13:47 +00:00
/// <summary>
/// Adds the production location.
/// </summary>
/// <param name="location">The location.</param>
/// <exception cref="System.ArgumentNullException">location</exception>
public void AddProductionLocation ( string location )
{
if ( string . IsNullOrWhiteSpace ( location ) )
{
throw new ArgumentNullException ( "location" ) ;
}
if ( ! ProductionLocations . Contains ( location , StringComparer . OrdinalIgnoreCase ) )
{
ProductionLocations . Add ( location ) ;
}
}
2013-02-21 01:33:05 +00:00
/// <summary>
2013-09-21 19:24:50 +00:00
/// Marks the played.
2013-02-21 01:33:05 +00:00
/// </summary>
/// <param name="user">The user.</param>
2013-09-21 19:24:50 +00:00
/// <param name="datePlayed">The date played.</param>
2013-04-02 19:25:16 +00:00
/// <param name="userManager">The user manager.</param>
2013-02-21 01:33:05 +00:00
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
2013-10-02 16:08:58 +00:00
public virtual async Task MarkPlayed ( User user , DateTime ? datePlayed , IUserDataManager userManager )
2013-02-21 01:33:05 +00:00
{
if ( user = = null )
{
throw new ArgumentNullException ( ) ;
}
2013-04-13 18:02:30 +00:00
var key = GetUserDataKey ( ) ;
2013-06-17 20:35:43 +00:00
var data = userManager . GetUserData ( user . Id , key ) ;
2013-02-21 01:33:05 +00:00
2013-09-21 19:24:50 +00:00
data . PlayCount = Math . Max ( data . PlayCount , 1 ) ;
data . LastPlayedDate = datePlayed ? ? data . LastPlayedDate ;
data . Played = true ;
2013-10-02 17:23:10 +00:00
await userManager . SaveUserData ( user . Id , key , data , UserDataSaveReason . TogglePlayed , CancellationToken . None ) . ConfigureAwait ( false ) ;
2013-09-21 19:24:50 +00:00
}
/// <summary>
/// Marks the unplayed.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="userManager">The user manager.</param>
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
2013-10-02 16:08:58 +00:00
public virtual async Task MarkUnplayed ( User user , IUserDataManager userManager )
2013-09-21 19:24:50 +00:00
{
if ( user = = null )
2013-02-21 01:33:05 +00:00
{
2013-09-21 19:24:50 +00:00
throw new ArgumentNullException ( ) ;
2013-02-21 01:33:05 +00:00
}
2013-09-21 19:24:50 +00:00
var key = GetUserDataKey ( ) ;
var data = userManager . GetUserData ( user . Id , key ) ;
//I think it is okay to do this here.
// if this is only called when a user is manually forcing something to un-played
// then it probably is what we want to do...
data . PlayCount = 0 ;
data . PlaybackPositionTicks = 0 ;
data . LastPlayedDate = null ;
data . Played = false ;
2013-02-21 01:33:05 +00:00
2013-10-02 17:23:10 +00:00
await userManager . SaveUserData ( user . Id , key , data , UserDataSaveReason . TogglePlayed , CancellationToken . None ) . ConfigureAwait ( false ) ;
2013-02-21 01:33:05 +00:00
}
/// <summary>
/// Do whatever refreshing is necessary when the filesystem pertaining to this item has changed.
/// </summary>
/// <returns>Task.</returns>
public virtual Task ChangedExternally ( )
{
return RefreshMetadata ( CancellationToken . None ) ;
}
/// <summary>
/// Finds a parent of a given type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>``0.</returns>
public T FindParent < T > ( )
where T : Folder
{
var parent = Parent ;
while ( parent ! = null )
{
var result = parent as T ;
if ( result ! = null )
{
return result ;
}
parent = parent . Parent ;
}
return null ;
}
/// <summary>
/// Gets an image
/// </summary>
/// <param name="type">The type.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
public string GetImage ( ImageType type )
{
if ( type = = ImageType . Backdrop )
{
throw new ArgumentException ( "Backdrops should be accessed using Item.Backdrops" ) ;
}
if ( type = = ImageType . Screenshot )
{
throw new ArgumentException ( "Screenshots should be accessed using Item.Screenshots" ) ;
}
string val ;
2013-04-25 19:47:38 +00:00
Images . TryGetValue ( type , out val ) ;
2013-02-21 01:33:05 +00:00
return val ;
}
/// <summary>
/// Gets an image
/// </summary>
/// <param name="type">The type.</param>
/// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
public bool HasImage ( ImageType type )
{
if ( type = = ImageType . Backdrop )
{
throw new ArgumentException ( "Backdrops should be accessed using Item.Backdrops" ) ;
}
if ( type = = ImageType . Screenshot )
{
throw new ArgumentException ( "Screenshots should be accessed using Item.Screenshots" ) ;
}
return ! string . IsNullOrEmpty ( GetImage ( type ) ) ;
}
/// <summary>
/// Sets an image
/// </summary>
/// <param name="type">The type.</param>
/// <param name="path">The path.</param>
/// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
public void SetImage ( ImageType type , string path )
{
if ( type = = ImageType . Backdrop )
{
throw new ArgumentException ( "Backdrops should be accessed using Item.Backdrops" ) ;
}
if ( type = = ImageType . Screenshot )
{
throw new ArgumentException ( "Screenshots should be accessed using Item.Screenshots" ) ;
}
2013-04-25 19:47:38 +00:00
var typeKey = type ;
2013-02-21 01:33:05 +00:00
// If it's null remove the key from the dictionary
if ( string . IsNullOrEmpty ( path ) )
{
2013-06-12 02:59:57 +00:00
if ( Images . ContainsKey ( typeKey ) )
2013-02-21 01:33:05 +00:00
{
2013-06-12 02:59:57 +00:00
Images . Remove ( typeKey ) ;
2013-02-21 01:33:05 +00:00
}
}
else
{
Images [ typeKey ] = path ;
}
}
/// <summary>
/// Deletes the image.
/// </summary>
/// <param name="type">The type.</param>
2013-05-05 04:49:49 +00:00
/// <param name="index">The index.</param>
2013-02-21 01:33:05 +00:00
/// <returns>Task.</returns>
2013-05-05 04:49:49 +00:00
public Task DeleteImage ( ImageType type , int? index )
2013-02-21 01:33:05 +00:00
{
2013-05-05 04:49:49 +00:00
if ( type = = ImageType . Backdrop )
2013-02-21 01:33:05 +00:00
{
2013-05-05 04:49:49 +00:00
if ( ! index . HasValue )
{
throw new ArgumentException ( "Please specify a backdrop image index to delete." ) ;
}
var file = BackdropImagePaths [ index . Value ] ;
BackdropImagePaths . Remove ( file ) ;
// Delete the source file
File . Delete ( file ) ;
2013-02-21 01:33:05 +00:00
}
2013-05-05 04:49:49 +00:00
else if ( type = = ImageType . Screenshot )
{
if ( ! index . HasValue )
{
throw new ArgumentException ( "Please specify a screenshot image index to delete." ) ;
}
var file = ScreenshotImagePaths [ index . Value ] ;
2013-02-21 01:33:05 +00:00
2013-05-05 04:49:49 +00:00
ScreenshotImagePaths . Remove ( file ) ;
// Delete the source file
File . Delete ( file ) ;
}
else
{
// Delete the source file
File . Delete ( GetImage ( type ) ) ;
2013-02-21 01:33:05 +00:00
2013-05-05 04:49:49 +00:00
// Remove it from the item
SetImage ( type , null ) ;
}
2013-02-21 01:33:05 +00:00
// Refresh metadata
2013-05-18 21:47:00 +00:00
return RefreshMetadata ( CancellationToken . None , forceSave : true ) ;
2013-02-21 01:33:05 +00:00
}
2013-09-10 18:56:00 +00:00
/// <summary>
/// Validates that images within the item are still on the file system
/// </summary>
public void ValidateImages ( )
{
// 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 ) ;
}
}
/// <summary>
/// Validates that backdrops within the item are still on the file system
/// </summary>
public void ValidateBackdrops ( )
{
// 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 ) ;
}
}
/// <summary>
/// Validates the screenshots.
/// </summary>
public void ValidateScreenshots ( )
{
// 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 = ScreenshotImagePaths
. Where ( path = > ! File . Exists ( path ) )
. ToList ( ) ;
// Now remove them from the dictionary
foreach ( var path in deletedImages )
{
ScreenshotImagePaths . Remove ( path ) ;
}
}
2013-09-18 18:49:06 +00:00
/// <summary>
/// Gets the image path.
/// </summary>
/// <param name="imageType">Type of the image.</param>
/// <param name="imageIndex">Index of the image.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.InvalidOperationException">
/// </exception>
/// <exception cref="System.ArgumentNullException">item</exception>
public string GetImagePath ( ImageType imageType , int imageIndex )
{
if ( imageType = = ImageType . Backdrop )
{
return BackdropImagePaths [ imageIndex ] ;
}
if ( imageType = = ImageType . Screenshot )
{
return ScreenshotImagePaths [ imageIndex ] ;
}
if ( imageType = = ImageType . Chapter )
{
return ItemRepository . GetChapter ( Id , imageIndex ) . ImagePath ;
}
return GetImage ( imageType ) ;
}
/// <summary>
/// Gets the image date modified.
/// </summary>
/// <param name="imagePath">The image path.</param>
/// <returns>DateTime.</returns>
/// <exception cref="System.ArgumentNullException">item</exception>
public DateTime GetImageDateModified ( string imagePath )
{
if ( string . IsNullOrEmpty ( imagePath ) )
{
throw new ArgumentNullException ( "imagePath" ) ;
}
var metaFileEntry = ResolveArgs . GetMetaFileByPath ( imagePath ) ;
// If we didn't the metafile entry, check the Season
if ( metaFileEntry = = null )
{
var episode = this as Episode ;
if ( episode ! = null & & episode . Season ! = null )
{
episode . Season . ResolveArgs . GetMetaFileByPath ( imagePath ) ;
}
}
// See if we can avoid a file system lookup by looking for the file in ResolveArgs
return metaFileEntry = = null ? File . GetLastWriteTimeUtc ( imagePath ) : metaFileEntry . LastWriteTimeUtc ;
}
2013-02-21 01:33:05 +00:00
}
}