2013-09-16 20:41:49 -07:00
using MediaBrowser.Common.Extensions ;
2013-10-31 07:03:23 -07:00
using MediaBrowser.Common.IO ;
2013-08-25 10:18:56 -07:00
using MediaBrowser.Controller.Configuration ;
2013-02-20 18:33:05 -07:00
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.Localization ;
2013-04-13 11:02:30 -07:00
using MediaBrowser.Controller.Persistence ;
2013-02-20 18:33:05 -07:00
using MediaBrowser.Controller.Providers ;
2013-12-26 09:53:23 -07:00
using MediaBrowser.Model.Configuration ;
2013-02-20 18:33:05 -07:00
using MediaBrowser.Model.Entities ;
2013-02-21 18:26:35 -07:00
using MediaBrowser.Model.Logging ;
2013-02-20 18:33:05 -07: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>
2014-02-06 20:10:13 -07:00
public abstract class BaseItem : IHasProviderIds , ILibraryItem , IHasImages , IHasUserData , IHasMetadata , IHasLookupInfo < ItemLookupInfo >
2013-02-20 18:33:05 -07:00
{
2013-04-21 21:38:03 -07:00
protected BaseItem ( )
{
Genres = new List < string > ( ) ;
Studios = new List < string > ( ) ;
People = new List < PersonInfo > ( ) ;
2013-05-02 15:32:15 -07:00
ProviderIds = new Dictionary < string , string > ( StringComparer . OrdinalIgnoreCase ) ;
2013-06-09 07:16:43 -07:00
LockedFields = new List < MetadataFields > ( ) ;
2014-02-07 13:30:41 -07:00
ImageInfos = new List < ItemImageInfo > ( ) ;
2013-04-21 21:38:03 -07:00
}
2013-05-30 15:22:15 -07:00
/// <summary>
/// The supported image extensions
/// </summary>
2013-10-12 22:56:44 -07:00
public static readonly string [ ] SupportedImageExtensions = new [ ] { ".png" , ".jpg" , ".jpeg" , ".tbn" } ;
2013-06-13 11:17:42 -07:00
2013-03-15 12:13:22 -07:00
/// <summary>
2013-02-20 18:33:05 -07:00
/// The trailer folder name
/// </summary>
public const string TrailerFolderName = "trailers" ;
2013-04-24 09:03:10 -07:00
public const string ThemeSongsFolderName = "theme-music" ;
2013-06-01 15:18:27 -07:00
public const string ThemeSongFilename = "theme" ;
2013-04-28 11:30:58 -07:00
public const string ThemeVideosFolderName = "backdrops" ;
2013-05-20 10:04:39 -07:00
public const string XbmcTrailerFileSuffix = "-trailer" ;
2013-02-20 18:33:05 -07:00
2014-02-07 13:30:41 -07:00
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>
2013-08-15 12:09:52 -07:00
public bool IsInMixedFolder { get ; set ; }
2013-09-04 06:19:03 -07:00
2013-05-05 19:40:12 -07:00
private string _name ;
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the name.
/// </summary>
/// <value>The name.</value>
2013-08-07 12:15:55 -07:00
public string Name
2013-05-05 19:40:12 -07:00
{
get
{
return _name ;
}
set
{
_name = value ;
// lazy load this again
_sortName = null ;
}
}
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the id.
/// </summary>
/// <value>The id.</value>
2013-06-13 11:17:42 -07:00
public Guid Id { get ; set ; }
2013-02-20 18:33:05 -07:00
2013-08-25 10:18:56 -07: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-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
public virtual string Path { get ; set ; }
2013-07-05 07:54:14 -07:00
[IgnoreDataMember]
protected internal bool IsOffline { get ; set ; }
2014-02-05 21:39:16 -07:00
/// <summary>
/// Returns the folder containing the item.
/// If the item is a folder, it returns the folder itself
/// </summary>
[IgnoreDataMember]
public virtual string ContainingFolderPath
{
get
{
if ( IsFolder )
{
return Path ;
}
return System . IO . Path . GetDirectoryName ( Path ) ;
}
}
[IgnoreDataMember]
2014-02-07 15:40:03 -07:00
public virtual bool IsOwnedItem
2014-02-05 21:39:16 -07:00
{
get
{
// Local trailer, special feature, theme video, etc.
// An item that belongs to another item but is not part of the Parent-Child tree
return ! IsFolder & & Parent = = null ;
}
}
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the type of the location.
/// </summary>
/// <value>The type of the location.</value>
2013-12-01 19:24:14 -07:00
[IgnoreDataMember]
2013-02-20 18:33:05 -07:00
public virtual LocationType LocationType
{
get
{
2013-07-05 07:54:14 -07:00
if ( IsOffline )
{
return LocationType . Offline ;
}
2013-02-20 18:33:05 -07:00
if ( string . IsNullOrEmpty ( Path ) )
{
return LocationType . Virtual ;
}
return System . IO . Path . IsPathRooted ( Path ) ? LocationType . FileSystem : LocationType . Remote ;
}
}
2014-02-10 13:11:46 -07:00
public virtual bool SupportsLocalMetadata
{
get
{
var locationType = LocationType ;
return locationType = = LocationType . FileSystem | | locationType = = LocationType . Offline ;
}
}
2013-02-20 18:33:05 -07:00
/// <summary>
/// This is just a helper for convenience
/// </summary>
/// <value>The primary image path.</value>
[IgnoreDataMember]
2013-04-04 21:12:05 -07:00
public string PrimaryImagePath
2013-02-20 18:33:05 -07:00
{
2013-12-19 14:51:32 -07:00
get { return this . GetImagePath ( ImageType . Primary ) ; }
2013-02-20 18:33:05 -07: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-12-06 08:59:40 -07:00
public DateTime DateLastSaved { get ; set ; }
2014-01-01 11:26:31 -07:00
2013-02-21 13:26:35 -07:00
/// <summary>
/// The logger
/// </summary>
2013-03-07 22:08:27 -07: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 10:46:11 -07:00
public static ILocalizationManager LocalizationManager { get ; set ; }
2013-06-20 09:44:24 -07:00
public static IItemRepository ItemRepository { get ; set ; }
2013-10-30 07:40:14 -07:00
public static IFileSystem FileSystem { get ; set ; }
2014-01-15 15:19:37 -07:00
public static IUserDataManager UserDataManager { get ; set ; }
2013-02-21 13:26:35 -07:00
2013-02-20 18:33:05 -07: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 06:31:23 -07:00
public bool DontFetchMeta { get ; set ; }
2013-02-20 18:33:05 -07:00
2013-06-09 07:15:59 -07:00
/// <summary>
/// Gets or sets the locked fields.
/// </summary>
/// <value>The locked fields.</value>
public List < MetadataFields > LockedFields { get ; set ; }
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets the type of the media.
/// </summary>
/// <value>The type of the media.</value>
[IgnoreDataMember]
public virtual string MediaType
{
get
{
return null ;
}
}
[IgnoreDataMember]
2014-02-05 21:39:16 -07:00
public virtual IEnumerable < string > PhysicalLocations
2013-02-20 18:33:05 -07:00
{
get
{
2014-02-05 21:39:16 -07:00
var locationType = LocationType ;
2013-02-20 18:33:05 -07:00
2014-02-06 20:10:13 -07:00
if ( locationType = = LocationType . Remote | | locationType = = LocationType . Virtual )
2013-05-24 10:48:48 -07:00
{
2014-02-05 21:39:16 -07:00
return new string [ ] { } ;
2013-05-24 10:48:48 -07:00
}
2014-01-01 11:26:31 -07:00
2014-02-05 21:39:16 -07:00
return new [ ] { Path } ;
2013-02-20 18:33:05 -07:00
}
}
2014-01-28 22:17:58 -07:00
private string _forcedSortName ;
2013-03-11 18:46:46 -07:00
/// <summary>
/// Gets or sets the name of the forced sort.
/// </summary>
/// <value>The name of the forced sort.</value>
2014-01-28 22:17:58 -07:00
public string ForcedSortName
{
get { return _forcedSortName ; }
set { _forcedSortName = value ; _sortName = null ; }
}
2013-03-11 18:46:46 -07:00
private string _sortName ;
2013-02-20 18:33:05 -07:00
/// <summary>
2014-02-08 13:02:35 -07:00
/// Gets the name of the sort.
2013-02-20 18:33:05 -07:00
/// </summary>
/// <value>The name of the sort.</value>
2013-03-11 18:46:46 -07:00
[IgnoreDataMember]
public string SortName
{
get
{
2013-08-25 12:54:18 -07:00
if ( ! string . IsNullOrEmpty ( ForcedSortName ) )
{
return ForcedSortName ;
}
return _sortName ? ? ( _sortName = CreateSortName ( ) ) ;
2013-03-11 18:46:46 -07: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-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the parent.
/// </summary>
/// <value>The parent.</value>
[IgnoreDataMember]
public Folder Parent { get ; set ; }
2013-11-19 20:15:48 -07:00
[IgnoreDataMember]
public IEnumerable < Folder > Parents
{
get
{
var parent = Parent ;
while ( parent ! = null )
{
yield return parent ;
parent = parent . Parent ;
}
}
}
2013-02-20 18:33:05 -07:00
/// <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 07:13:47 -07:00
/// <summary>
/// Gets or sets the end date.
/// </summary>
/// <value>The end date.</value>
public DateTime ? EndDate { get ; set ; }
2013-04-13 16:43:41 -07:00
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the display type of the media.
/// </summary>
/// <value>The display type of the media.</value>
2013-06-14 05:18:40 -07:00
public string DisplayMediaType { get ; set ; }
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the official rating.
/// </summary>
/// <value>The official rating.</value>
2013-08-03 06:24:23 -07:00
public string OfficialRating { get ; set ; }
2013-02-20 18:33:05 -07:00
2013-06-23 10:48:30 -07:00
/// <summary>
/// Gets or sets the official rating description.
/// </summary>
/// <value>The official rating description.</value>
public string OfficialRatingDescription { get ; set ; }
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the custom rating.
/// </summary>
/// <value>The custom rating.</value>
2013-08-03 06:24:23 -07:00
public string CustomRating { get ; set ; }
2013-02-20 18:33:05 -07:00
/// <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 ; }
/// <summary>
/// Gets or sets the studios.
/// </summary>
/// <value>The studios.</value>
2013-08-07 08:59:13 -07:00
public List < string > Studios { get ; set ; }
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the genres.
/// </summary>
/// <value>The genres.</value>
2013-08-07 10:11:02 -07:00
public List < string > Genres { get ; set ; }
2013-02-20 18:33:05 -07:00
2013-04-12 07:13:47 -07:00
/// <summary>
/// Gets or sets the home page URL.
/// </summary>
/// <value>The home page URL.</value>
public string HomePageUrl { get ; set ; }
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the community rating.
/// </summary>
/// <value>The community rating.</value>
public float? CommunityRating { get ; set ; }
2013-07-05 10:40:51 -07:00
/// <summary>
/// Gets or sets the community rating vote count.
/// </summary>
/// <value>The community rating vote count.</value>
2013-07-05 17:19:44 -07:00
public int? VoteCount { get ; set ; }
2013-07-05 10:40:51 -07:00
2013-02-20 18:33:05 -07:00
/// <summary>
/// Gets or sets the run time ticks.
/// </summary>
/// <value>The run time ticks.</value>
public long? RunTimeTicks { get ; set ; }
/// <summary>
/// Gets or sets the production year.
/// </summary>
/// <value>The production year.</value>
2013-08-07 08:59:13 -07:00
public int? ProductionYear { get ; set ; }
2013-02-20 18:33:05 -07: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-08-03 06:24:23 -07:00
[IgnoreDataMember]
public virtual string OfficialRatingForComparison
{
get { return OfficialRating ; }
}
[IgnoreDataMember]
2014-02-06 08:58:49 -07:00
public string CustomRatingForComparison
2013-08-03 06:24:23 -07:00
{
2014-02-06 08:58:49 -07:00
get
{
if ( ! string . IsNullOrEmpty ( CustomRating ) )
{
return CustomRating ;
}
var parent = Parent ;
if ( parent ! = null )
{
return parent . CustomRatingForComparison ;
}
return null ;
}
2013-08-03 06:24:23 -07:00
}
2013-02-20 18:33:05 -07:00
/// <summary>
/// Loads local trailers from the file system
/// </summary>
/// <returns>List{Video}.</returns>
2014-02-12 22:11:54 -07:00
private IEnumerable < Trailer > LoadLocalTrailers ( List < FileSystemInfo > fileSystemChildren , IDirectoryService directoryService )
2014-02-05 21:39:16 -07:00
{
2014-02-06 15:22:03 -07:00
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 ) )
) ;
2014-02-12 22:11:54 -07:00
return LibraryManager . ResolvePaths < Trailer > ( files , directoryService , null ) . Select ( video = >
2014-02-06 15:22:03 -07:00
{
// 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 ( ) ;
2013-02-20 18:33:05 -07:00
}
2013-04-24 09:03:10 -07:00
/// <summary>
/// Loads the theme songs.
/// </summary>
/// <returns>List{Audio.Audio}.</returns>
2014-02-12 22:11:54 -07:00
private IEnumerable < Audio . Audio > LoadThemeSongs ( List < FileSystemInfo > fileSystemChildren , IDirectoryService directoryService )
2013-04-24 09:03:10 -07:00
{
2014-02-05 21:39:16 -07:00
var files = fileSystemChildren . OfType < DirectoryInfo > ( )
. Where ( i = > string . Equals ( i . Name , ThemeSongsFolderName , StringComparison . OrdinalIgnoreCase ) )
. SelectMany ( i = > i . EnumerateFiles ( "*" , SearchOption . TopDirectoryOnly ) )
. ToList ( ) ;
2013-04-24 09:03:10 -07:00
2013-05-30 11:19:30 -07:00
// Support plex/xbmc convention
2014-02-05 21:39:16 -07:00
files . AddRange ( fileSystemChildren . OfType < FileInfo > ( )
. Where ( i = > string . Equals ( System . IO . Path . GetFileNameWithoutExtension ( i . Name ) , ThemeSongFilename , StringComparison . OrdinalIgnoreCase ) )
2013-05-30 11:19:30 -07:00
) ;
2013-04-24 09:03:10 -07:00
2014-02-12 22:11:54 -07:00
return LibraryManager . ResolvePaths < Audio . Audio > ( files , directoryService , null ) . Select ( audio = >
2013-04-24 09:03:10 -07:00
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
2014-01-14 13:03:35 -07:00
var dbItem = LibraryManager . GetItemById ( audio . Id ) as Audio . Audio ;
2013-04-24 09:03:10 -07:00
if ( dbItem ! = null )
{
audio = dbItem ;
}
return audio ;
2014-02-06 09:46:26 -07:00
// Sort them so that the list can be easily compared for changes
} ) . OrderBy ( i = > i . Path ) . ToList ( ) ;
2013-04-24 09:03:10 -07:00
}
2013-04-28 09:25:14 -07:00
/// <summary>
/// Loads the video backdrops.
/// </summary>
/// <returns>List{Video}.</returns>
2014-02-12 22:11:54 -07:00
private IEnumerable < Video > LoadThemeVideos ( IEnumerable < FileSystemInfo > fileSystemChildren , IDirectoryService directoryService )
2013-04-28 09:25:14 -07:00
{
2014-02-05 21:39:16 -07:00
var files = fileSystemChildren . OfType < DirectoryInfo > ( )
. Where ( i = > string . Equals ( i . Name , ThemeVideosFolderName , StringComparison . OrdinalIgnoreCase ) )
. SelectMany ( i = > i . EnumerateFiles ( "*" , SearchOption . TopDirectoryOnly ) ) ;
2013-04-28 09:25:14 -07:00
2014-02-12 22:11:54 -07:00
return LibraryManager . ResolvePaths < Video > ( files , directoryService , null ) . Select ( item = >
2013-04-28 09:25:14 -07:00
{
// Try to retrieve it from the db. If we don't find it, use the resolved version
2014-01-14 13:03:35 -07:00
var dbItem = LibraryManager . GetItemById ( item . Id ) as Video ;
2013-04-28 09:25:14 -07:00
if ( dbItem ! = null )
{
item = dbItem ;
}
return item ;
2014-02-06 09:46:26 -07:00
// Sort them so that the list can be easily compared for changes
} ) . OrderBy ( i = > i . Path ) . ToList ( ) ;
2013-04-28 09:25:14 -07:00
}
2014-02-05 21:39:16 -07:00
public Task RefreshMetadata ( CancellationToken cancellationToken )
2014-01-28 11:37:01 -07:00
{
2014-02-05 21:39:16 -07:00
return RefreshMetadata ( new MetadataRefreshOptions ( ) , cancellationToken ) ;
2014-01-28 11:37:01 -07:00
}
2013-02-20 18:33:05 -07:00
/// <summary>
/// Overrides the base implementation to refresh metadata for local trailers
/// </summary>
2014-01-28 11:37:01 -07:00
/// <param name="options">The options.</param>
2013-02-20 18:33:05 -07:00
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>true if a provider reports we changed</returns>
2014-02-05 21:39:16 -07:00
public async Task RefreshMetadata ( MetadataRefreshOptions options , CancellationToken cancellationToken )
2013-02-20 18:33:05 -07:00
{
2014-02-05 21:39:16 -07:00
var locationType = LocationType ;
2014-02-10 11:39:41 -07:00
var requiresSave = false ;
2014-02-05 21:39:16 -07:00
if ( IsFolder | | Parent ! = null )
2013-06-20 12:07:58 -07:00
{
2014-02-08 15:38:02 -07:00
options . DirectoryService = options . DirectoryService ? ? new DirectoryService ( Logger ) ;
2014-02-12 22:11:54 -07:00
try
{
var files = locationType = = LocationType . FileSystem | | locationType = = LocationType . Offline ?
GetFileSystemChildren ( options . DirectoryService ) . ToList ( ) :
new List < FileSystemInfo > ( ) ;
2013-02-20 18:33:05 -07:00
2014-02-12 22:11:54 -07:00
var ownedItemsChanged = await RefreshedOwnedItems ( options , files , cancellationToken ) . ConfigureAwait ( false ) ;
2014-02-10 11:39:41 -07:00
2014-02-12 22:11:54 -07:00
if ( ownedItemsChanged )
{
requiresSave = true ;
}
}
catch ( Exception ex )
2014-02-10 11:39:41 -07:00
{
2014-02-12 22:11:54 -07:00
Logger . ErrorException ( "Error refreshing owned items for {0}" , ex , Path ? ? Name ) ;
2014-02-10 11:39:41 -07:00
}
2014-02-05 21:39:16 -07:00
}
2014-02-02 22:35:43 -07:00
2014-02-10 11:39:41 -07:00
var dateLastSaved = DateLastSaved ;
2014-01-28 11:37:01 -07:00
await ProviderManager . RefreshMetadata ( this , options , cancellationToken ) . ConfigureAwait ( false ) ;
2014-02-10 11:39:41 -07:00
// If it wasn't saved by the provider process, save now
if ( requiresSave & & dateLastSaved = = DateLastSaved )
{
await UpdateToRepository ( ItemUpdateType . MetadataImport , cancellationToken ) . ConfigureAwait ( false ) ;
}
2014-01-28 11:37:01 -07:00
}
2014-02-10 11:39:41 -07:00
/// <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 )
2014-02-02 22:35:43 -07:00
{
2013-06-17 13:35:43 -07:00
var themeSongsChanged = false ;
2013-02-20 18:33:05 -07:00
2013-06-17 13:35:43 -07:00
var themeVideosChanged = false ;
2013-04-28 09:25:14 -07:00
2013-06-17 13:35:43 -07:00
var localTrailersChanged = false ;
2013-02-20 18:33:05 -07:00
2013-06-17 13:35:43 -07:00
if ( LocationType = = LocationType . FileSystem & & Parent ! = null )
{
2013-12-05 09:50:21 -07:00
var hasThemeMedia = this as IHasThemeMedia ;
if ( hasThemeMedia ! = null )
{
2014-02-05 21:39:16 -07:00
if ( ! IsInMixedFolder )
{
themeSongsChanged = await RefreshThemeSongs ( hasThemeMedia , options , fileSystemChildren , cancellationToken ) . ConfigureAwait ( false ) ;
2013-06-17 13:35:43 -07:00
2014-02-05 21:39:16 -07:00
themeVideosChanged = await RefreshThemeVideos ( hasThemeMedia , options , fileSystemChildren , cancellationToken ) . ConfigureAwait ( false ) ;
}
2013-12-05 09:50:21 -07:00
}
2013-06-17 13:35:43 -07:00
2013-12-02 09:46:25 -07:00
var hasTrailers = this as IHasTrailers ;
if ( hasTrailers ! = null )
{
2014-02-05 21:39:16 -07:00
localTrailersChanged = await RefreshLocalTrailers ( hasTrailers , options , fileSystemChildren , cancellationToken ) . ConfigureAwait ( false ) ;
2013-12-02 09:46:25 -07:00
}
2013-06-17 13:35:43 -07:00
}
2014-02-06 20:10:13 -07:00
2014-02-10 11:39:41 -07:00
return themeSongsChanged | | themeVideosChanged | | localTrailersChanged ;
2014-02-05 21:39:16 -07:00
}
2013-02-20 18:33:05 -07:00
2014-02-10 11:39:41 -07:00
protected virtual IEnumerable < FileSystemInfo > GetFileSystemChildren ( IDirectoryService directoryService )
2014-02-05 21:39:16 -07:00
{
var path = ContainingFolderPath ;
2014-02-08 15:38:02 -07:00
return directoryService . GetFileSystemEntries ( path ) ;
2013-02-20 18:33:05 -07:00
}
2014-02-05 21:39:16 -07:00
private async Task < bool > RefreshLocalTrailers ( IHasTrailers item , MetadataRefreshOptions options , List < FileSystemInfo > fileSystemChildren , CancellationToken cancellationToken )
2013-05-08 13:58:52 -07:00
{
2014-02-12 22:11:54 -07:00
var newItems = LoadLocalTrailers ( fileSystemChildren , options . DirectoryService ) . ToList ( ) ;
2013-05-08 13:58:52 -07:00
var newItemIds = newItems . Select ( i = > i . Id ) . ToList ( ) ;
2013-12-02 09:46:25 -07:00
var itemsChanged = ! item . LocalTrailerIds . SequenceEqual ( newItemIds ) ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
var tasks = newItems . Select ( i = > i . RefreshMetadata ( options , cancellationToken ) ) ;
2014-01-28 11:37:01 -07:00
2014-02-05 21:39:16 -07:00
await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
2013-05-08 13:58:52 -07:00
2013-12-02 09:46:25 -07:00
item . LocalTrailerIds = newItemIds ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
return itemsChanged ;
2013-05-08 13:58:52 -07:00
}
2013-06-13 11:17:42 -07:00
2014-02-05 21:39:16 -07:00
private async Task < bool > RefreshThemeVideos ( IHasThemeMedia item , MetadataRefreshOptions options , IEnumerable < FileSystemInfo > fileSystemChildren , CancellationToken cancellationToken )
2013-05-08 13:58:52 -07:00
{
2014-02-12 22:11:54 -07:00
var newThemeVideos = LoadThemeVideos ( fileSystemChildren , options . DirectoryService ) . ToList ( ) ;
2014-02-06 09:46:26 -07:00
2013-05-08 13:58:52 -07:00
var newThemeVideoIds = newThemeVideos . Select ( i = > i . Id ) . ToList ( ) ;
2013-12-05 09:50:21 -07:00
var themeVideosChanged = ! item . ThemeVideoIds . SequenceEqual ( newThemeVideoIds ) ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
var tasks = newThemeVideos . Select ( i = > i . RefreshMetadata ( options , cancellationToken ) ) ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
2013-05-08 13:58:52 -07:00
2013-12-05 09:50:21 -07:00
item . ThemeVideoIds = newThemeVideoIds ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
return themeVideosChanged ;
2013-05-08 13:58:52 -07:00
}
2013-06-13 11:17:42 -07:00
2013-05-08 13:58:52 -07:00
/// <summary>
/// Refreshes the theme songs.
/// </summary>
2014-02-05 21:39:16 -07:00
private async Task < bool > RefreshThemeSongs ( IHasThemeMedia item , MetadataRefreshOptions options , List < FileSystemInfo > fileSystemChildren , CancellationToken cancellationToken )
2013-05-08 13:58:52 -07:00
{
2014-02-12 22:11:54 -07:00
var newThemeSongs = LoadThemeSongs ( fileSystemChildren , options . DirectoryService ) . ToList ( ) ;
2013-05-08 13:58:52 -07:00
var newThemeSongIds = newThemeSongs . Select ( i = > i . Id ) . ToList ( ) ;
2013-12-05 09:50:21 -07:00
var themeSongsChanged = ! item . ThemeSongIds . SequenceEqual ( newThemeSongIds ) ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
var tasks = newThemeSongs . Select ( i = > i . RefreshMetadata ( options , cancellationToken ) ) ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
await Task . WhenAll ( tasks ) . ConfigureAwait ( false ) ;
2013-05-08 13:58:52 -07:00
2013-12-05 09:50:21 -07:00
item . ThemeSongIds = newThemeSongIds ;
2013-05-08 13:58:52 -07:00
2014-02-05 21:39:16 -07:00
return themeSongsChanged ;
2013-05-08 13:58:52 -07:00
}
2013-02-20 18:33:05 -07: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 11:02:30 -07:00
/// Gets the user data key.
2013-02-20 18:33:05 -07:00
/// </summary>
2013-04-13 11:02:30 -07:00
/// <returns>System.String.</returns>
public virtual string GetUserDataKey ( )
2013-02-20 18:33:05 -07:00
{
2013-04-13 11:02:30 -07:00
return Id . ToString ( ) ;
2013-02-20 18:33:05 -07:00
}
2013-12-26 17:23:58 -07:00
/// <summary>
/// Gets the preferred metadata language.
/// </summary>
/// <returns>System.String.</returns>
2013-12-28 09:58:13 -07:00
public string GetPreferredMetadataLanguage ( )
2013-12-26 17:23:58 -07:00
{
string lang = null ;
var hasLang = this as IHasPreferredMetadataLanguage ;
if ( hasLang ! = null )
{
lang = hasLang . PreferredMetadataLanguage ;
}
2013-12-28 09:58:13 -07:00
if ( string . IsNullOrEmpty ( lang ) )
{
lang = Parents . OfType < IHasPreferredMetadataLanguage > ( )
. Select ( i = > i . PreferredMetadataLanguage )
. FirstOrDefault ( i = > ! string . IsNullOrEmpty ( i ) ) ;
}
2013-12-26 17:23:58 -07:00
if ( string . IsNullOrEmpty ( lang ) )
{
lang = ConfigurationManager . Configuration . PreferredMetadataLanguage ;
}
return lang ;
}
2013-12-28 09:58:13 -07:00
/// <summary>
/// Gets the preferred metadata language.
/// </summary>
/// <returns>System.String.</returns>
public string GetPreferredMetadataCountryCode ( )
{
string lang = null ;
var hasLang = this as IHasPreferredMetadataLanguage ;
if ( hasLang ! = null )
{
lang = hasLang . PreferredMetadataCountryCode ;
}
if ( string . IsNullOrEmpty ( lang ) )
{
lang = Parents . OfType < IHasPreferredMetadataLanguage > ( )
. Select ( i = > i . PreferredMetadataCountryCode )
. FirstOrDefault ( i = > ! string . IsNullOrEmpty ( i ) ) ;
}
if ( string . IsNullOrEmpty ( lang ) )
{
lang = ConfigurationManager . Configuration . MetadataCountryCode ;
}
return lang ;
}
2014-01-01 11:26:31 -07:00
public virtual bool IsSaveLocalMetadataEnabled ( )
{
return ConfigurationManager . Configuration . SaveLocalMeta ;
}
2013-02-20 18:33:05 -07:00
/// <summary>
/// Determines if a given user has access to this item
/// </summary>
/// <param name="user">The user.</param>
/// <returns><c>true</c> if [is parental allowed] [the specified user]; otherwise, <c>false</c>.</returns>
2013-06-10 10:46:11 -07:00
/// <exception cref="System.ArgumentNullException">user</exception>
2014-01-01 11:26:31 -07:00
public bool IsParentalAllowed ( User user )
2013-02-20 18:33:05 -07:00
{
if ( user = = null )
{
2013-04-22 08:38:38 -07:00
throw new ArgumentNullException ( "user" ) ;
}
2013-09-18 16:33:21 -07:00
var maxAllowedRating = user . Configuration . MaxParentalRating ;
if ( maxAllowedRating = = null )
2013-04-22 08:38:38 -07:00
{
return true ;
2013-02-20 18:33:05 -07:00
}
2013-08-03 06:24:23 -07:00
var rating = CustomRatingForComparison ;
2013-06-10 10:46:11 -07:00
2013-07-16 10:18:32 -07:00
if ( string . IsNullOrEmpty ( rating ) )
{
2013-08-03 06:24:23 -07:00
rating = OfficialRatingForComparison ;
2013-07-16 10:18:32 -07:00
}
2013-08-07 08:59:13 -07:00
2013-06-11 19:59:57 -07:00
if ( string . IsNullOrEmpty ( rating ) )
2013-05-23 08:07:25 -07:00
{
2013-12-26 09:53:23 -07:00
return ! GetBlockUnratedValue ( user . Configuration ) ;
2013-05-23 08:07:25 -07:00
}
2014-01-01 11:26:31 -07:00
var value = LocalizationManager . GetRatingLevel ( rating ) ;
2013-06-10 10:46:11 -07:00
// Could not determine the integer value
if ( ! value . HasValue )
{
2013-08-01 05:00:47 -07:00
return true ;
2013-06-10 10:46:11 -07:00
}
2013-09-18 16:33:21 -07:00
return value . Value < = maxAllowedRating . Value ;
2013-02-20 18:33:05 -07:00
}
2013-12-26 09:53:23 -07:00
/// <summary>
/// Gets the block unrated value.
/// </summary>
/// <param name="config">The configuration.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
protected virtual bool GetBlockUnratedValue ( UserConfiguration config )
{
return config . BlockNotRated ;
}
2013-02-20 18:33:05 -07: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" ) ;
}
2014-01-01 11:26:31 -07:00
return IsParentalAllowed ( user ) ;
2013-02-20 18:33:05 -07: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 ( ) ;
}
2013-12-29 19:41:22 -07:00
if ( IsInMixedFolder ! = copy . IsInMixedFolder )
{
Logger . Debug ( Name + " changed due to different value for IsInMixedFolder." ) ;
return true ;
}
2013-02-20 18:33:05 -07:00
var changed = copy . DateModified ! = DateModified ;
if ( changed )
{
2013-02-21 13:26:35 -07:00
Logger . Debug ( Name + " changed - original creation: " + DateCreated + " new creation: " + copy . DateCreated + " original modified: " + DateModified + " new modified: " + copy . DateModified ) ;
2013-02-20 18:33:05 -07:00
}
return changed ;
}
2013-11-21 13:48:26 -07:00
public virtual string GetClientTypeName ( )
{
return GetType ( ) . Name ;
}
2013-02-20 18:33:05 -07:00
/// <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-21 21:38:03 -07:00
public bool IsRecentlyAdded ( )
2013-02-20 18:33:05 -07:00
{
2013-03-03 22:43:06 -07:00
return ( DateTime . UtcNow - DateCreated ) . TotalDays < ConfigurationManager . Configuration . RecentItemDays ;
2013-02-20 18:33:05 -07: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 08:03:12 -07:00
throw new ArgumentNullException ( "person" ) ;
2013-02-20 18:33:05 -07:00
}
if ( string . IsNullOrWhiteSpace ( person . Name ) )
{
throw new ArgumentNullException ( ) ;
}
2013-07-12 12:56:40 -07: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 08:03:12 -07: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 ;
2013-11-19 20:47:29 -07:00
existing . SortOrder = person . SortOrder ? ? existing . SortOrder ;
2013-04-14 08:03:12 -07:00
return ;
}
}
2013-02-20 18:33:05 -07:00
2013-04-14 08:03:12 -07:00
if ( string . Equals ( person . Type , PersonType . Actor , StringComparison . OrdinalIgnoreCase ) )
2013-02-20 18:33:05 -07:00
{
2013-04-30 08:25:30 -07: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 08:03:12 -07:00
{
2013-04-30 08:25:30 -07:00
// Wasn't there - add it
2013-04-14 08:03:12 -07:00
People . Add ( person ) ;
}
2013-04-30 08:25:30 -07:00
else
{
// Was there, if no role and we have one - fill it in
2013-11-19 20:47:29 -07:00
if ( string . IsNullOrWhiteSpace ( existing . Role ) & & ! string . IsNullOrWhiteSpace ( person . Role ) )
{
existing . Role = person . Role ;
}
existing . SortOrder = person . SortOrder ? ? existing . SortOrder ;
2013-04-30 08:25:30 -07:00
}
2013-04-14 08:03:12 -07:00
}
else
{
2013-11-19 20:47:29 -07:00
var existing = People . FirstOrDefault ( p = >
string . Equals ( p . Name , person . Name , StringComparison . OrdinalIgnoreCase ) & &
string . Equals ( p . Type , person . Type , StringComparison . OrdinalIgnoreCase ) ) ;
2013-04-14 08:03:12 -07:00
// Check for dupes based on the combination of Name and Type
2013-11-19 20:47:29 -07:00
if ( existing = = null )
2013-04-14 08:03:12 -07:00
{
People . Add ( person ) ;
}
2013-11-19 20:47:29 -07:00
else
{
existing . SortOrder = person . SortOrder ? ? existing . SortOrder ;
}
2013-02-20 18:33:05 -07: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-04 21:12:05 -07:00
throw new ArgumentNullException ( "name" ) ;
2013-02-20 18:33:05 -07:00
}
if ( ! Studios . Contains ( name , StringComparer . OrdinalIgnoreCase ) )
{
Studios . Add ( name ) ;
}
}
/// <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 07:13:47 -07:00
throw new ArgumentNullException ( "name" ) ;
2013-02-20 18:33:05 -07:00
}
if ( ! Genres . Contains ( name , StringComparer . OrdinalIgnoreCase ) )
{
Genres . Add ( name ) ;
}
}
/// <summary>
2013-09-21 12:24:50 -07:00
/// Marks the played.
2013-02-20 18:33:05 -07:00
/// </summary>
/// <param name="user">The user.</param>
2013-09-21 12:24:50 -07:00
/// <param name="datePlayed">The date played.</param>
2013-04-02 12:25:16 -07:00
/// <param name="userManager">The user manager.</param>
2013-02-20 18:33:05 -07:00
/// <returns>Task.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
2013-10-02 09:08:58 -07:00
public virtual async Task MarkPlayed ( User user , DateTime ? datePlayed , IUserDataManager userManager )
2013-02-20 18:33:05 -07:00
{
if ( user = = null )
{
throw new ArgumentNullException ( ) ;
}
2013-04-13 11:02:30 -07:00
var key = GetUserDataKey ( ) ;
2013-06-17 13:35:43 -07:00
var data = userManager . GetUserData ( user . Id , key ) ;
2013-02-20 18:33:05 -07:00
2013-10-28 07:56:57 -07:00
if ( datePlayed . HasValue )
{
// Incremenet
data . PlayCount + + ;
}
// Ensure it's at least one
2013-09-21 12:24:50 -07:00
data . PlayCount = Math . Max ( data . PlayCount , 1 ) ;
data . LastPlayedDate = datePlayed ? ? data . LastPlayedDate ;
data . Played = true ;
2013-10-23 09:03:12 -07:00
await userManager . SaveUserData ( user . Id , this , data , UserDataSaveReason . TogglePlayed , CancellationToken . None ) . ConfigureAwait ( false ) ;
2013-09-21 12:24:50 -07: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 09:08:58 -07:00
public virtual async Task MarkUnplayed ( User user , IUserDataManager userManager )
2013-09-21 12:24:50 -07:00
{
if ( user = = null )
2013-02-20 18:33:05 -07:00
{
2013-09-21 12:24:50 -07:00
throw new ArgumentNullException ( ) ;
2013-02-20 18:33:05 -07:00
}
2013-09-21 12:24:50 -07: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-20 18:33:05 -07:00
2013-10-23 09:03:12 -07:00
await userManager . SaveUserData ( user . Id , this , data , UserDataSaveReason . TogglePlayed , CancellationToken . None ) . ConfigureAwait ( false ) ;
2013-02-20 18:33:05 -07: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>
2013-12-19 14:51:32 -07:00
/// <param name="imageIndex">Index of the image.</param>
2013-02-20 18:33:05 -07:00
/// <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>
2013-12-19 14:51:32 -07:00
public bool HasImage ( ImageType type , int imageIndex )
2013-02-20 18:33:05 -07:00
{
2014-02-07 13:30:41 -07:00
return GetImageInfo ( type , imageIndex ) ! = null ;
2013-02-20 18:33:05 -07:00
}
2014-02-07 13:30:41 -07:00
public void SetImagePath ( ImageType type , int index , FileInfo file )
2013-02-20 18:33:05 -07:00
{
2014-02-07 13:30:41 -07:00
if ( type = = ImageType . Chapter )
2013-02-20 18:33:05 -07:00
{
2014-02-07 13:30:41 -07:00
throw new ArgumentException ( "Cannot set chapter images using SetImagePath" ) ;
2013-02-20 18:33:05 -07:00
}
2014-02-07 13:30:41 -07:00
var image = GetImageInfo ( type , index ) ;
2013-02-20 18:33:05 -07:00
2014-02-07 13:30:41 -07:00
if ( image = = null )
2013-02-20 18:33:05 -07:00
{
2014-02-07 13:30:41 -07:00
ImageInfos . Add ( new ItemImageInfo
2013-02-20 18:33:05 -07:00
{
2014-02-07 13:30:41 -07:00
Path = file . FullName ,
Type = type ,
DateModified = FileSystem . GetLastWriteTimeUtc ( file )
} ) ;
2013-02-20 18:33:05 -07:00
}
else
{
2014-02-07 13:30:41 -07:00
image . Path = file . FullName ;
image . DateModified = FileSystem . GetLastWriteTimeUtc ( file ) ;
2013-02-20 18:33:05 -07:00
}
}
/// <summary>
/// Deletes the image.
/// </summary>
/// <param name="type">The type.</param>
2013-05-04 21:49:49 -07:00
/// <param name="index">The index.</param>
2013-02-20 18:33:05 -07:00
/// <returns>Task.</returns>
2014-02-07 13:30:41 -07:00
public Task DeleteImage ( ImageType type , int index )
2013-02-20 18:33:05 -07:00
{
2014-02-07 13:30:41 -07:00
var info = GetImageInfo ( type , index ) ;
2013-05-04 21:49:49 -07:00
2014-02-07 13:30:41 -07:00
if ( info = = null )
2013-05-04 21:49:49 -07:00
{
2014-02-07 13:30:41 -07:00
// Nothing to do
return Task . FromResult ( true ) ;
2013-05-04 21:49:49 -07:00
}
2013-02-20 18:33:05 -07:00
2014-02-07 13:30:41 -07:00
// Remove it from the item
ImageInfos . Remove ( info ) ;
2013-09-10 11:56:00 -07:00
2014-02-07 13:30:41 -07:00
// Delete the source file
var currentFile = new FileInfo ( info . Path ) ;
2013-10-22 12:03:21 -07:00
2014-02-07 13:30:41 -07:00
// Deletion will fail if the file is hidden so remove the attribute first
2013-10-22 12:03:21 -07:00
if ( currentFile . Exists )
{
if ( ( currentFile . Attributes & FileAttributes . Hidden ) = = FileAttributes . Hidden )
{
currentFile . Attributes & = ~ FileAttributes . Hidden ;
}
currentFile . Delete ( ) ;
}
2014-02-07 13:30:41 -07:00
2014-02-07 15:40:03 -07:00
return UpdateToRepository ( ItemUpdateType . ImageUpdate , CancellationToken . None ) ;
}
public virtual Task UpdateToRepository ( ItemUpdateType updateReason , CancellationToken cancellationToken )
{
return LibraryManager . UpdateItem ( this , ItemUpdateType . ImageUpdate , cancellationToken ) ;
2013-10-22 12:03:21 -07:00
}
2013-09-10 11:56:00 -07:00
/// <summary>
/// Validates that images within the item are still on the file system
/// </summary>
2014-02-10 11:39:41 -07:00
public bool ValidateImages ( IDirectoryService directoryService )
2013-09-10 11:56:00 -07:00
{
2014-02-08 15:38:02 -07:00
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 ( ) ;
2014-02-07 13:30:41 -07:00
var deletedImages = ImageInfos
2014-02-08 15:38:02 -07:00
. Where ( image = > ! allFiles . Contains ( image . Path , StringComparer . OrdinalIgnoreCase ) )
2013-09-10 11:56:00 -07:00
. ToList ( ) ;
2014-02-07 13:30:41 -07:00
if ( deletedImages . Count > 0 )
2013-09-10 11:56:00 -07:00
{
2014-02-07 13:30:41 -07:00
ImageInfos = ImageInfos . Except ( deletedImages ) . ToList ( ) ;
2013-09-10 11:56:00 -07:00
}
2014-01-28 11:37:01 -07:00
2014-02-07 13:30:41 -07:00
return deletedImages . Count > 0 ;
2013-09-10 11:56:00 -07:00
}
/// <summary>
2014-02-07 13:30:41 -07:00
/// Gets the image path.
2013-09-10 11:56:00 -07:00
/// </summary>
2014-02-07 13:30:41 -07:00
/// <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 )
2013-09-10 11:56:00 -07:00
{
2014-02-07 13:30:41 -07:00
var info = GetImageInfo ( imageType , imageIndex ) ;
2013-10-22 12:03:21 -07:00
2014-02-07 13:30:41 -07:00
return info = = null ? null : info . Path ;
2013-10-22 12:03:21 -07:00
}
2013-09-10 11:56:00 -07:00
/// <summary>
2014-02-07 13:30:41 -07:00
/// Gets the image information.
2013-09-10 11:56:00 -07:00
/// </summary>
2014-02-07 13:30:41 -07:00
/// <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 )
2013-09-10 11:56:00 -07:00
{
2014-02-07 13:30:41 -07:00
if ( imageType = = ImageType . Chapter )
{
var chapter = ItemRepository . GetChapter ( Id , imageIndex ) ;
2014-01-28 11:37:01 -07:00
2014-02-07 13:30:41 -07:00
if ( chapter = = null )
{
return null ;
}
2014-01-28 11:37:01 -07:00
2014-02-07 13:30:41 -07:00
var path = chapter . ImagePath ;
2013-12-05 09:50:21 -07:00
2014-02-07 13:30:41 -07:00
if ( string . IsNullOrWhiteSpace ( path ) )
{
return null ;
}
2013-09-10 11:56:00 -07:00
2014-02-07 13:30:41 -07:00
return new ItemImageInfo
{
Path = path ,
DateModified = FileSystem . GetLastWriteTimeUtc ( path ) ,
Type = imageType
} ;
2013-09-10 11:56:00 -07:00
}
2014-01-28 11:37:01 -07:00
2014-02-07 13:30:41 -07:00
return GetImages ( imageType )
. ElementAtOrDefault ( imageIndex ) ;
2013-09-10 11:56:00 -07:00
}
2013-09-18 11:49:06 -07:00
2014-02-07 13:30:41 -07:00
public IEnumerable < ItemImageInfo > GetImages ( ImageType imageType )
2013-09-18 11:49:06 -07:00
{
if ( imageType = = ImageType . Chapter )
{
2014-02-07 13:30:41 -07:00
throw new ArgumentException ( "No image info for chapter images" ) ;
2013-09-18 11:49:06 -07:00
}
2014-02-07 13:30:41 -07:00
return ImageInfos . Where ( i = > i . Type = = imageType ) ;
2013-09-18 11:49:06 -07:00
}
/// <summary>
2014-02-07 13:30:41 -07:00
/// Adds the images.
2013-09-18 11:49:06 -07:00
/// </summary>
2014-02-07 13:30:41 -07:00
/// <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 )
2013-09-18 11:49:06 -07:00
{
2014-02-07 13:30:41 -07:00
if ( imageType = = ImageType . Chapter )
2013-09-18 11:49:06 -07:00
{
2014-02-07 13:30:41 -07:00
throw new ArgumentException ( "Cannot call AddImages with chapter images" ) ;
2013-09-18 11:49:06 -07:00
}
2014-02-07 13:30:41 -07:00
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 ;
2013-09-18 11:49:06 -07:00
}
2013-12-01 12:31:58 -07:00
/// <summary>
/// Gets the file system path to delete when the item is to be deleted
/// </summary>
/// <returns></returns>
public virtual IEnumerable < string > GetDeletePaths ( )
{
return new [ ] { Path } ;
}
2013-12-19 14:51:32 -07:00
2014-02-07 13:30:41 -07:00
public bool AllowsMultipleImages ( ImageType type )
{
return type = = ImageType . Backdrop | | type = = ImageType . Screenshot | | type = = ImageType . Chapter ;
}
2013-12-19 14:51:32 -07:00
public Task SwapImages ( ImageType type , int index1 , int index2 )
{
2014-02-07 13:30:41 -07:00
if ( ! AllowsMultipleImages ( type ) )
2013-12-19 14:51:32 -07:00
{
throw new ArgumentException ( "The change index operation is only applicable to backdrops and screenshots" ) ;
}
2014-02-07 13:30:41 -07:00
var info1 = GetImageInfo ( type , index1 ) ;
var info2 = GetImageInfo ( type , index2 ) ;
2013-12-19 14:51:32 -07:00
2014-02-07 13:30:41 -07:00
if ( info1 = = null | | info2 = = null )
2014-01-28 11:37:01 -07:00
{
2014-02-07 13:30:41 -07:00
// Nothing to do
return Task . FromResult ( true ) ;
}
var path1 = info1 . Path ;
var path2 = info2 . Path ;
FileSystem . SwapFiles ( path1 , path2 ) ;
2014-02-09 14:23:55 -07:00
// Refresh these values
2014-02-07 13:30:41 -07:00
info1 . DateModified = FileSystem . GetLastWriteTimeUtc ( info1 . Path ) ;
info2 . DateModified = FileSystem . GetLastWriteTimeUtc ( info2 . Path ) ;
2014-01-28 11:37:01 -07:00
2014-02-07 15:40:03 -07:00
return UpdateToRepository ( ItemUpdateType . ImageUpdate , CancellationToken . None ) ;
2013-12-19 14:51:32 -07:00
}
2014-01-15 15:19:37 -07:00
public virtual bool IsPlayed ( User user )
{
var userdata = UserDataManager . GetUserData ( user . Id , GetUserDataKey ( ) ) ;
return userdata ! = null & & userdata . Played ;
}
2014-01-17 22:55:21 -07:00
public virtual bool IsUnplayed ( User user )
{
var userdata = UserDataManager . GetUserData ( user . Id , GetUserDataKey ( ) ) ;
return userdata = = null | | ! userdata . Played ;
}
2014-02-06 20:10:13 -07:00
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
} ;
}
2014-02-10 11:39:41 -07:00
/// <summary>
2014-02-12 22:11:54 -07:00
/// This is called before any metadata refresh and returns true or false indicating if changes were made
2014-02-10 11:39:41 -07:00
/// </summary>
2014-02-12 22:11:54 -07:00
public virtual bool BeforeMetadataRefresh ( )
2014-02-10 11:39:41 -07:00
{
2014-02-12 22:11:54 -07:00
var hasChanges = false ;
2014-02-10 11:39:41 -07:00
if ( string . IsNullOrEmpty ( Name ) & & ! string . IsNullOrEmpty ( Path ) )
{
Name = System . IO . Path . GetFileNameWithoutExtension ( Path ) ;
2014-02-12 22:11:54 -07:00
hasChanges = true ;
2014-02-10 11:39:41 -07:00
}
2014-02-12 22:11:54 -07:00
return hasChanges ;
2014-02-10 11:39:41 -07:00
}
2013-02-20 18:33:05 -07:00
}
}