mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-06-03 22:38:30 +01:00
converted movie providers to new system
This commit is contained in:
@@ -3,6 +3,10 @@ namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
public class AdultVideo : Video, IHasPreferredMetadataLanguage
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the preferred metadata language.
|
||||
/// </summary>
|
||||
/// <value>The preferred metadata language.</value>
|
||||
public string PreferredMetadataLanguage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
@@ -11,6 +15,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public class AggregateFolder : Folder
|
||||
{
|
||||
public AggregateFolder()
|
||||
{
|
||||
PhysicalLocationsList = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We don't support manual shortcuts
|
||||
/// </summary>
|
||||
@@ -36,6 +45,60 @@ namespace MediaBrowser.Controller.Entities
|
||||
get { return _virtualChildren; }
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return PhysicalLocationsList;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> PhysicalLocationsList { get; set; }
|
||||
|
||||
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
|
||||
{
|
||||
return CreateResolveArgs().FileSystemChildren;
|
||||
}
|
||||
|
||||
private ItemResolveArgs CreateResolveArgs()
|
||||
{
|
||||
var path = ContainingFolderPath;
|
||||
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
|
||||
{
|
||||
FileInfo = new DirectoryInfo(path),
|
||||
Path = path,
|
||||
Parent = Parent
|
||||
};
|
||||
|
||||
// Gather child folder and files
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var isPhysicalRoot = args.IsPhysicalRoot;
|
||||
|
||||
// When resolving the root, we need it's grandchildren (children of user views)
|
||||
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
|
||||
|
||||
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
|
||||
|
||||
// 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 = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
|
||||
|
||||
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
|
||||
}
|
||||
|
||||
args.FileSystemDictionary = fileSystemDictionary;
|
||||
}
|
||||
|
||||
PhysicalLocationsList = args.PhysicalLocations.ToList();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the virtual child.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Controller.Persistence;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Controller.Resolvers;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
@@ -102,6 +100,35 @@ namespace MediaBrowser.Controller.Entities
|
||||
[IgnoreDataMember]
|
||||
protected internal bool IsOffline { get; set; }
|
||||
|
||||
/// <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]
|
||||
public bool IsOwnedItem
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the type of the location.
|
||||
/// </summary>
|
||||
@@ -189,19 +216,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The locked fields.</value>
|
||||
public List<MetadataFields> LockedFields { get; set; }
|
||||
|
||||
/// <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>
|
||||
/// Gets the type of the media.
|
||||
/// </summary>
|
||||
@@ -215,160 +229,19 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
public virtual IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_resolveArgs == null)
|
||||
var locationType = LocationType;
|
||||
|
||||
if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
|
||||
{
|
||||
try
|
||||
{
|
||||
_resolveArgs = CreateResolveArgs();
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error creating resolve args for {0}", ex, Path);
|
||||
|
||||
IsOffline = true;
|
||||
|
||||
throw;
|
||||
}
|
||||
return new string[] { };
|
||||
}
|
||||
|
||||
return _resolveArgs;
|
||||
}
|
||||
set
|
||||
{
|
||||
_resolveArgs = value;
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return ResolveArgs.PhysicalLocations;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the resolve args.
|
||||
/// </summary>
|
||||
/// <param name="pathInfo">The path info.</param>
|
||||
public void ResetResolveArgs(FileSystemInfo pathInfo)
|
||||
{
|
||||
ResetResolveArgs(CreateResolveArgs(pathInfo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the resolve args.
|
||||
/// </summary>
|
||||
public void ResetResolveArgs()
|
||||
{
|
||||
_resolveArgs = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the resolve args.
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
public void ResetResolveArgs(ItemResolveArgs args)
|
||||
{
|
||||
_resolveArgs = args;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
protected internal virtual ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
|
||||
{
|
||||
var path = Path;
|
||||
|
||||
var locationType = LocationType;
|
||||
|
||||
if (locationType == LocationType.Remote ||
|
||||
locationType == LocationType.Virtual)
|
||||
{
|
||||
return new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager);
|
||||
}
|
||||
|
||||
var isDirectory = false;
|
||||
|
||||
if (UseParentPathToCreateResolveArgs)
|
||||
{
|
||||
path = System.IO.Path.GetDirectoryName(path);
|
||||
isDirectory = true;
|
||||
}
|
||||
|
||||
pathInfo = pathInfo ?? (isDirectory ? new DirectoryInfo(path) : FileSystem.GetFileSystemInfo(path));
|
||||
|
||||
if (pathInfo == null || !pathInfo.Exists)
|
||||
{
|
||||
throw new IOException("Unable to retrieve file system info for " + path);
|
||||
}
|
||||
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
|
||||
{
|
||||
FileInfo = pathInfo,
|
||||
Path = path,
|
||||
Parent = Parent
|
||||
};
|
||||
|
||||
// Gather child folder and files
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var isPhysicalRoot = args.IsPhysicalRoot;
|
||||
|
||||
// When resolving the root, we need it's grandchildren (children of user views)
|
||||
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
|
||||
|
||||
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
|
||||
|
||||
// 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 = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
|
||||
|
||||
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
|
||||
}
|
||||
|
||||
args.FileSystemDictionary = fileSystemDictionary;
|
||||
}
|
||||
|
||||
//update our dates
|
||||
EntityResolutionHelper.EnsureDates(FileSystem, this, args, false);
|
||||
|
||||
IsOffline = false;
|
||||
|
||||
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;
|
||||
return new[] { Path };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,122 +456,93 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// Loads local trailers from the file system
|
||||
/// </summary>
|
||||
/// <returns>List{Video}.</returns>
|
||||
private IEnumerable<Trailer> LoadLocalTrailers()
|
||||
private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
return new List<Trailer>();
|
||||
//ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
//try
|
||||
//{
|
||||
// resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return new List<Trailer>();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Trailer>();
|
||||
}
|
||||
// if (!resolveArgs.IsDirectory)
|
||||
// {
|
||||
// return new List<Trailer>();
|
||||
// }
|
||||
//}
|
||||
//catch (IOException ex)
|
||||
//{
|
||||
// Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
// return new List<Trailer>();
|
||||
//}
|
||||
|
||||
var files = new List<FileSystemInfo>();
|
||||
//var files = new List<FileSystemInfo>();
|
||||
|
||||
var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
|
||||
//var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
|
||||
|
||||
// Path doesn't exist. No biggie
|
||||
if (folder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading trailers for {0}", ex, Name);
|
||||
}
|
||||
}
|
||||
//// Path doesn't exist. No biggie
|
||||
//if (folder != null)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
// }
|
||||
// catch (IOException ex)
|
||||
// {
|
||||
// Logger.ErrorException("Error loading trailers for {0}", ex, Name);
|
||||
// }
|
||||
//}
|
||||
|
||||
// Support xbmc trailers (-trailer suffix on video file names)
|
||||
files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
|
||||
{
|
||||
if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
|
||||
}
|
||||
//// Support xbmc trailers (-trailer suffix on video file names)
|
||||
//files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
|
||||
// {
|
||||
// if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (IOException ex)
|
||||
// {
|
||||
// Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
|
||||
// }
|
||||
|
||||
return false;
|
||||
}));
|
||||
// return false;
|
||||
//}));
|
||||
|
||||
return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
|
||||
{
|
||||
// Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
|
||||
//return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
|
||||
//{
|
||||
// // Try to retrieve it from the db. If we don't find it, use the resolved version
|
||||
// var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(video.ResolveArgs);
|
||||
video = dbItem;
|
||||
}
|
||||
// if (dbItem != null)
|
||||
// {
|
||||
// video = dbItem;
|
||||
// }
|
||||
|
||||
return video;
|
||||
// return video;
|
||||
|
||||
}).ToList();
|
||||
//}).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the theme songs.
|
||||
/// </summary>
|
||||
/// <returns>List{Audio.Audio}.</returns>
|
||||
private IEnumerable<Audio.Audio> LoadThemeSongs()
|
||||
private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return new List<Audio.Audio>();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Audio.Audio>();
|
||||
}
|
||||
|
||||
var files = new List<FileSystemInfo>();
|
||||
|
||||
var folder = resolveArgs.GetFileSystemEntryByName(ThemeSongsFolderName);
|
||||
|
||||
// Path doesn't exist. No biggie
|
||||
if (folder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading theme songs for {0}", ex, Name);
|
||||
}
|
||||
}
|
||||
var files = fileSystemChildren.OfType<DirectoryInfo>()
|
||||
.Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
|
||||
.ToList();
|
||||
|
||||
// Support plex/xbmc convention
|
||||
files.AddRange(resolveArgs.FileSystemChildren
|
||||
.Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsAudioFile(i.Name))
|
||||
files.AddRange(fileSystemChildren.OfType<FileInfo>()
|
||||
.Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
|
||||
);
|
||||
|
||||
return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio =>
|
||||
@@ -708,7 +552,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(audio.ResolveArgs);
|
||||
audio = dbItem;
|
||||
}
|
||||
|
||||
@@ -720,44 +563,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// Loads the video backdrops.
|
||||
/// </summary>
|
||||
/// <returns>List{Video}.</returns>
|
||||
private IEnumerable<Video> LoadThemeVideos()
|
||||
private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return new List<Video>();
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Video>();
|
||||
}
|
||||
|
||||
var folder = resolveArgs.GetFileSystemEntryByName(ThemeVideosFolderName);
|
||||
|
||||
// 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>();
|
||||
}
|
||||
var files = fileSystemChildren.OfType<DirectoryInfo>()
|
||||
.Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
|
||||
|
||||
return LibraryManager.ResolvePaths<Video>(files, null).Select(item =>
|
||||
{
|
||||
@@ -766,7 +576,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(item.ResolveArgs);
|
||||
item = dbItem;
|
||||
}
|
||||
|
||||
@@ -774,9 +583,9 @@ namespace MediaBrowser.Controller.Entities
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
public Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool resetResolveArgs = true)
|
||||
public Task RefreshMetadata(CancellationToken cancellationToken)
|
||||
{
|
||||
return RefreshMetadata(new MetadataRefreshOptions { ResetResolveArgs = resetResolveArgs }, cancellationToken);
|
||||
return RefreshMetadata(new MetadataRefreshOptions(), cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -785,35 +594,24 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>true if a provider reports we changed</returns>
|
||||
public async Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
public async Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
{
|
||||
if (options.ResetResolveArgs)
|
||||
var locationType = LocationType;
|
||||
|
||||
if (IsFolder || Parent != null)
|
||||
{
|
||||
// Reload this
|
||||
ResetResolveArgs();
|
||||
var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ?
|
||||
GetFileSystemChildren().ToList() :
|
||||
new List<FileSystemInfo>();
|
||||
|
||||
await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await BeforeRefreshMetadata(options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private readonly Task _cachedTask = Task.FromResult(true);
|
||||
protected virtual Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
return _cachedTask;
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public virtual async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
{
|
||||
// Refresh for the item
|
||||
var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var themeSongsChanged = false;
|
||||
|
||||
var themeVideosChanged = false;
|
||||
@@ -825,102 +623,83 @@ namespace MediaBrowser.Controller.Entities
|
||||
var hasThemeMedia = this as IHasThemeMedia;
|
||||
if (hasThemeMedia != null)
|
||||
{
|
||||
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
if (!IsInMixedFolder)
|
||||
{
|
||||
themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
var hasTrailers = this as IHasTrailers;
|
||||
if (hasTrailers != null)
|
||||
{
|
||||
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Get the result from the item task
|
||||
var updateReason = await itemRefreshTask.ConfigureAwait(false);
|
||||
|
||||
var changed = updateReason.HasValue;
|
||||
|
||||
if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged)
|
||||
|
||||
if (themeSongsChanged || themeVideosChanged || localTrailersChanged)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await LibraryManager.UpdateItem(this, updateReason ?? ItemUpdateType.Unspecified, cancellationToken).ConfigureAwait(false);
|
||||
options.ForceSave = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren()
|
||||
{
|
||||
var newItems = LoadLocalTrailers().ToList();
|
||||
var path = ContainingFolderPath;
|
||||
|
||||
return new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly);
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LoadLocalTrailers(fileSystemChildren).ToList();
|
||||
var newItemIds = newItems.Select(i => i.Id).ToList();
|
||||
|
||||
var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
|
||||
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.LocalTrailerIds = newItemIds;
|
||||
|
||||
return itemsChanged || results.Contains(true);
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newThemeVideos = LoadThemeVideos().ToList();
|
||||
var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList();
|
||||
var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList();
|
||||
|
||||
var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
|
||||
|
||||
var tasks = newThemeVideos.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newThemeVideos.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.ThemeVideoIds = newThemeVideoIds;
|
||||
|
||||
return themeVideosChanged || results.Contains(true);
|
||||
return themeVideosChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the theme songs.
|
||||
/// </summary>
|
||||
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newThemeSongs = LoadThemeSongs().ToList();
|
||||
var newThemeSongs = LoadThemeSongs(fileSystemChildren).ToList();
|
||||
var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList();
|
||||
|
||||
var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
|
||||
|
||||
var tasks = newThemeSongs.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newThemeSongs.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
item.ThemeSongIds = newThemeSongIds;
|
||||
|
||||
return themeSongsChanged || results.Contains(true);
|
||||
return themeSongsChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1655,27 +1434,8 @@ namespace MediaBrowser.Controller.Entities
|
||||
throw new ArgumentNullException("imagePath");
|
||||
}
|
||||
|
||||
var locationType = LocationType;
|
||||
|
||||
if (locationType == LocationType.Remote ||
|
||||
locationType == LocationType.Virtual)
|
||||
{
|
||||
return FileSystem.GetLastWriteTimeUtc(imagePath);
|
||||
}
|
||||
|
||||
var metaFileEntry = ResolveArgs.GetMetaFileByPath(imagePath);
|
||||
|
||||
// If we didn't the metafile entry, check the Season
|
||||
if (metaFileEntry == null)
|
||||
{
|
||||
if (Parent != null)
|
||||
{
|
||||
metaFileEntry = Parent.ResolveArgs.GetMetaFileByPath(imagePath);
|
||||
}
|
||||
}
|
||||
|
||||
// See if we can avoid a file system lookup by looking for the file in ResolveArgs
|
||||
return metaFileEntry == null ? FileSystem.GetLastWriteTimeUtc(imagePath) : FileSystem.GetLastWriteTimeUtc(metaFileEntry);
|
||||
return FileSystem.GetLastWriteTimeUtc(imagePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -29,25 +29,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The preferred metadata country code.</value>
|
||||
public string PreferredMetadataCountryCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return !IsInMixedFolder;
|
||||
}
|
||||
}
|
||||
|
||||
public Book()
|
||||
{
|
||||
Tags = new List<string>();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -14,6 +16,11 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public class CollectionFolder : Folder, ICollectionFolder
|
||||
{
|
||||
public CollectionFolder()
|
||||
{
|
||||
PhysicalLocationsList = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is virtual folder.
|
||||
/// </summary>
|
||||
@@ -42,6 +49,60 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public override IEnumerable<string> PhysicalLocations
|
||||
{
|
||||
get
|
||||
{
|
||||
return PhysicalLocationsList;
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> PhysicalLocationsList { get; set; }
|
||||
|
||||
protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
|
||||
{
|
||||
return CreateResolveArgs().FileSystemChildren;
|
||||
}
|
||||
|
||||
private ItemResolveArgs CreateResolveArgs()
|
||||
{
|
||||
var path = ContainingFolderPath;
|
||||
|
||||
var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
|
||||
{
|
||||
FileInfo = new DirectoryInfo(path),
|
||||
Path = path,
|
||||
Parent = Parent
|
||||
};
|
||||
|
||||
// Gather child folder and files
|
||||
if (args.IsDirectory)
|
||||
{
|
||||
var isPhysicalRoot = args.IsPhysicalRoot;
|
||||
|
||||
// When resolving the root, we need it's grandchildren (children of user views)
|
||||
var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
|
||||
|
||||
var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
|
||||
|
||||
// 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 = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
|
||||
|
||||
fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
|
||||
}
|
||||
|
||||
args.FileSystemDictionary = fileSystemDictionary;
|
||||
}
|
||||
|
||||
PhysicalLocationsList = args.PhysicalLocations.ToList();
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
// Cache this since it will be used a lot
|
||||
/// <summary>
|
||||
/// The null task result
|
||||
@@ -59,13 +120,14 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <returns>Task.</returns>
|
||||
protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
|
||||
{
|
||||
CreateResolveArgs();
|
||||
ResetDynamicChildren();
|
||||
|
||||
return NullTaskResult;
|
||||
}
|
||||
|
||||
private List<LinkedChild> _linkedChildren;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Our children are actually just references to the ones in the physical root...
|
||||
/// </summary>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Localization;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
@@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
public List<Guid> ThemeSongIds { get; set; }
|
||||
public List<Guid> ThemeVideoIds { get; set; }
|
||||
|
||||
|
||||
public Folder()
|
||||
{
|
||||
LinkedChildren = new List<LinkedChild>();
|
||||
@@ -379,7 +380,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
nonCachedChildren = new BaseItem[] {};
|
||||
nonCachedChildren = new BaseItem[] { };
|
||||
|
||||
Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
|
||||
}
|
||||
@@ -402,8 +403,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (currentChildren.TryGetValue(child.Id, out currentChild))
|
||||
{
|
||||
currentChild.ResetResolveArgs(child.ResolveArgs);
|
||||
|
||||
//existing item - check if it has changed
|
||||
if (currentChild.HasChanged(child))
|
||||
{
|
||||
@@ -411,7 +410,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
if (currentChildLocationType != LocationType.Remote &&
|
||||
currentChildLocationType != LocationType.Virtual)
|
||||
{
|
||||
EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
|
||||
currentChild.DateModified = child.DateModified;
|
||||
}
|
||||
|
||||
currentChild.IsInMixedFolder = child.IsInMixedFolder;
|
||||
@@ -539,8 +538,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
await child.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = currentTuple.Item2,
|
||||
ReplaceAllMetadata = forceRefreshMetadata,
|
||||
ResetResolveArgs = false
|
||||
ReplaceAllMetadata = forceRefreshMetadata
|
||||
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
@@ -581,16 +579,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
});
|
||||
|
||||
await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
// Some folder providers are unable to refresh until children have been refreshed.
|
||||
await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -661,14 +649,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <returns>IEnumerable{BaseItem}.</returns>
|
||||
protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
|
||||
{
|
||||
var resolveArgs = ResolveArgs;
|
||||
|
||||
if (resolveArgs == null || resolveArgs.FileSystemDictionary == null)
|
||||
{
|
||||
Logger.Error("ResolveArgs null for {0}", Path);
|
||||
}
|
||||
|
||||
return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this);
|
||||
return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -914,43 +895,29 @@ namespace MediaBrowser.Controller.Entities
|
||||
return item;
|
||||
}
|
||||
|
||||
protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
|
||||
protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
|
||||
{
|
||||
RefreshLinkedChildren();
|
||||
if (RefreshLinkedChildren(fileSystemChildren))
|
||||
{
|
||||
options.ForceSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
return base.BeforeRefreshMetadata(options, cancellationToken);
|
||||
return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the linked children.
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
private bool RefreshLinkedChildren()
|
||||
private bool RefreshLinkedChildren(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
ItemResolveArgs resolveArgs;
|
||||
|
||||
try
|
||||
{
|
||||
resolveArgs = ResolveArgs;
|
||||
|
||||
if (!resolveArgs.IsDirectory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
|
||||
var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
|
||||
|
||||
var newShortcutLinks = resolveArgs.FileSystemChildren
|
||||
var newShortcutLinks = fileSystemChildren
|
||||
.Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
|
||||
.Select(i =>
|
||||
{
|
||||
@@ -1058,7 +1025,7 @@ namespace MediaBrowser.Controller.Entities
|
||||
return this;
|
||||
}
|
||||
|
||||
if (locationType != LocationType.Virtual && ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -79,17 +79,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The game system.</value>
|
||||
public string GameSystem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is multi part.
|
||||
/// </summary>
|
||||
@@ -101,17 +90,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
public List<string> MultiPartGameFiles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return !IsInMixedFolder;
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetUserDataKey()
|
||||
{
|
||||
var id = this.GetProviderId(MetadataProviders.Gamesdb);
|
||||
|
||||
@@ -99,6 +99,12 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// </summary>
|
||||
/// <value>The backdrop image paths.</value>
|
||||
List<string> BackdropImagePaths { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is owned item.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
|
||||
bool IsOwnedItem { get; }
|
||||
}
|
||||
|
||||
public static class HasImagesExtensions
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
public List<Guid> ThemeSongIds { get; set; }
|
||||
public List<Guid> ThemeVideoIds { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the preferred metadata country code.
|
||||
/// </summary>
|
||||
@@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
public string PreferredMetadataCountryCode { get; set; }
|
||||
|
||||
public string PreferredMetadataLanguage { get; set; }
|
||||
|
||||
|
||||
public Movie()
|
||||
{
|
||||
SpecialFeatureIds = new List<Guid>();
|
||||
@@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
public List<Guid> LocalTrailerIds { get; set; }
|
||||
public List<string> Keywords { get; set; }
|
||||
|
||||
|
||||
public List<MediaUrl> RemoteTrailers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -103,88 +103,48 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the base implementation to refresh metadata for special features
|
||||
/// </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>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
// Kick off a task to refresh the main item
|
||||
var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
|
||||
var specialFeaturesChanged = false;
|
||||
await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Must have a parent to have special features
|
||||
// In other words, it must be part of the Parent/Child tree
|
||||
if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder)
|
||||
{
|
||||
specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
}
|
||||
var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return specialFeaturesChanged || result;
|
||||
if (specialFeaturesChanged)
|
||||
{
|
||||
options.ForceSave = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> RefreshSpecialFeatures(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true)
|
||||
private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LoadSpecialFeatures().ToList();
|
||||
var newItems = LoadSpecialFeatures(fileSystemChildren).ToList();
|
||||
var newItemIds = newItems.Select(i => i.Id).ToList();
|
||||
|
||||
var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds);
|
||||
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh,
|
||||
ResetResolveArgs = false
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
SpecialFeatureIds = newItemIds;
|
||||
|
||||
return itemsChanged || results.Contains(true);
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the special features.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Video}.</returns>
|
||||
private IEnumerable<Video> LoadSpecialFeatures()
|
||||
private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
FileSystemInfo folder;
|
||||
|
||||
try
|
||||
{
|
||||
folder = ResolveArgs.GetFileSystemEntryByName("extras") ??
|
||||
ResolveArgs.GetFileSystemEntryByName("specials");
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
|
||||
return new List<Video>();
|
||||
}
|
||||
|
||||
// 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 special features for {0}", ex, Name);
|
||||
return new List<Video>();
|
||||
}
|
||||
var files = fileSystemChildren.OfType<DirectoryInfo>()
|
||||
.Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase))
|
||||
.SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
|
||||
|
||||
return LibraryManager.ResolvePaths<Video>(files, null).Select(video =>
|
||||
{
|
||||
@@ -193,7 +153,6 @@ namespace MediaBrowser.Controller.Entities.Movies
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(video.ResolveArgs);
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,28 +11,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
/// </summary>
|
||||
public class Episode : Video
|
||||
{
|
||||
/// <summary>
|
||||
/// Episodes have a special Metadata folder
|
||||
/// </summary>
|
||||
/// <value>The meta location.</value>
|
||||
[IgnoreDataMember]
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
return System.IO.Path.Combine(Parent.Path, "metadata");
|
||||
}
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the season in which it aired.
|
||||
/// </summary>
|
||||
|
||||
@@ -131,34 +131,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add files from the metadata folder to ResolveArgs
|
||||
/// </summary>
|
||||
/// <param name="args">The args.</param>
|
||||
public static void AddMetadataFiles(ItemResolveArgs args)
|
||||
{
|
||||
var folder = args.GetFileSystemEntryByName("metadata");
|
||||
|
||||
if (folder != null)
|
||||
{
|
||||
args.AddMetadataFiles(new DirectoryInfo(folder.FullName).EnumerateFiles());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates ResolveArgs on demand
|
||||
/// </summary>
|
||||
/// <param name="pathInfo">The path info.</param>
|
||||
/// <returns>ItemResolveArgs.</returns>
|
||||
protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
|
||||
{
|
||||
var args = base.CreateResolveArgs(pathInfo);
|
||||
|
||||
AddMetadataFiles(args);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the name of the sort.
|
||||
/// </summary>
|
||||
|
||||
@@ -111,20 +111,6 @@ namespace MediaBrowser.Controller.Entities.TV
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates ResolveArgs on demand
|
||||
/// </summary>
|
||||
/// <param name="pathInfo">The path info.</param>
|
||||
/// <returns>ItemResolveArgs.</returns>
|
||||
protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
|
||||
{
|
||||
var args = base.CreateResolveArgs(pathInfo);
|
||||
|
||||
Season.AddMetadataFiles(args);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
[IgnoreDataMember]
|
||||
public bool ContainsEpisodesWithoutSeasonFolders
|
||||
{
|
||||
|
||||
@@ -89,33 +89,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should be overridden to return the proper folder where metadata lives
|
||||
/// </summary>
|
||||
/// <value>The meta location.</value>
|
||||
[IgnoreDataMember]
|
||||
public override string MetaLocation
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!IsLocalTrailer)
|
||||
{
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
|
||||
return base.MetaLocation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needed because the resolver stops at the trailer folder and we find the video inside.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get { return !IsLocalTrailer; }
|
||||
}
|
||||
|
||||
public override string GetUserDataKey()
|
||||
{
|
||||
var key = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tvcom);
|
||||
|
||||
@@ -84,33 +84,23 @@ namespace MediaBrowser.Controller.Entities
|
||||
/// <value>The aspect ratio.</value>
|
||||
public string AspectRatio { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should be overridden to return the proper folder where metadata lives
|
||||
/// </summary>
|
||||
/// <value>The meta location.</value>
|
||||
[IgnoreDataMember]
|
||||
public override string MetaLocation
|
||||
public override string ContainingFolderPath
|
||||
{
|
||||
get
|
||||
{
|
||||
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Needed because the resolver stops at the movie folder and we find the video inside.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
|
||||
protected override bool UseParentPathToCreateResolveArgs
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsInMixedFolder)
|
||||
if (IsMultiPart)
|
||||
{
|
||||
return false;
|
||||
return System.IO.Path.GetDirectoryName(Path);
|
||||
}
|
||||
|
||||
return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart;
|
||||
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd ||
|
||||
VideoType == VideoType.HdDvd)
|
||||
{
|
||||
return Path;
|
||||
}
|
||||
|
||||
return base.ContainingFolderPath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,106 +149,73 @@ namespace MediaBrowser.Controller.Entities
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// <returns>true if a provider reports we changed</returns>
|
||||
public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
|
||||
protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
// Kick off a task to refresh the main item
|
||||
var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
|
||||
var additionalPartsChanged = false;
|
||||
await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Must have a parent to have additional parts
|
||||
// In other words, it must be part of the Parent/Child tree
|
||||
// The additional parts won't have additional parts themselves
|
||||
if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null)
|
||||
{
|
||||
try
|
||||
var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (additionalPartsChanged)
|
||||
{
|
||||
additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error loading additional parts for {0}.", ex, Name);
|
||||
options.ForceSave = true;
|
||||
}
|
||||
}
|
||||
|
||||
return additionalPartsChanged || result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the additional parts.
|
||||
/// </summary>
|
||||
/// <param name="options">The options.</param>
|
||||
/// <param name="fileSystemChildren">The file system children.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="forceSave">if set to <c>true</c> [force save].</param>
|
||||
/// <param name="forceRefresh">if set to <c>true</c> [force refresh].</param>
|
||||
/// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
private async Task<bool> RefreshAdditionalParts(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true)
|
||||
private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
|
||||
{
|
||||
var newItems = LoadAdditionalParts().ToList();
|
||||
var newItems = LoadAdditionalParts(fileSystemChildren).ToList();
|
||||
|
||||
var newItemIds = newItems.Select(i => i.Id).ToList();
|
||||
|
||||
var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds);
|
||||
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
|
||||
{
|
||||
ForceSave = forceSave,
|
||||
ReplaceAllMetadata = forceRefresh
|
||||
var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
|
||||
|
||||
}, cancellationToken));
|
||||
|
||||
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
await Task.WhenAll(tasks).ConfigureAwait(false);
|
||||
|
||||
AdditionalPartIds = newItemIds;
|
||||
|
||||
return itemsChanged || results.Contains(true);
|
||||
return itemsChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads the additional parts.
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{Video}.</returns>
|
||||
private IEnumerable<Video> LoadAdditionalParts()
|
||||
private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren)
|
||||
{
|
||||
IEnumerable<FileSystemInfo> files;
|
||||
|
||||
var path = Path;
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
throw new ApplicationException(string.Format("Item {0} has a null path.", Name ?? Id.ToString()));
|
||||
}
|
||||
|
||||
if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
|
||||
{
|
||||
var parentPath = System.IO.Path.GetDirectoryName(path);
|
||||
|
||||
if (string.IsNullOrEmpty(parentPath))
|
||||
files = fileSystemChildren.Where(i =>
|
||||
{
|
||||
throw new IOException("Unable to get parent path info from " + path);
|
||||
}
|
||||
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
|
||||
}
|
||||
|
||||
files = new DirectoryInfo(parentPath)
|
||||
.EnumerateDirectories()
|
||||
.Where(i => !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFile(i.Name));
|
||||
return false;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var resolveArgs = ResolveArgs;
|
||||
|
||||
if (resolveArgs == null)
|
||||
{
|
||||
throw new IOException("ResolveArgs are null for " + path);
|
||||
}
|
||||
|
||||
files = resolveArgs.FileSystemChildren.Where(i =>
|
||||
files = fileSystemChildren.Where(i =>
|
||||
{
|
||||
if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
@@ -276,7 +233,6 @@ namespace MediaBrowser.Controller.Entities
|
||||
|
||||
if (dbItem != null)
|
||||
{
|
||||
dbItem.ResetResolveArgs(video.ResolveArgs);
|
||||
video = dbItem;
|
||||
}
|
||||
|
||||
|
||||
@@ -195,77 +195,6 @@ namespace MediaBrowser.Controller.Library
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Store these to reduce disk access in Resolvers
|
||||
/// </summary>
|
||||
/// <value>The metadata file dictionary.</value>
|
||||
private Dictionary<string, FileSystemInfo> MetadataFileDictionary { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the metadata files.
|
||||
/// </summary>
|
||||
/// <value>The metadata files.</value>
|
||||
public IEnumerable<FileSystemInfo> MetadataFiles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (MetadataFileDictionary != null)
|
||||
{
|
||||
return MetadataFileDictionary.Values;
|
||||
}
|
||||
|
||||
return new FileSystemInfo[] { };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the metadata file.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <exception cref="System.IO.FileNotFoundException"></exception>
|
||||
public void AddMetadataFile(string path)
|
||||
{
|
||||
var file = new FileInfo(path);
|
||||
|
||||
if (!file.Exists)
|
||||
{
|
||||
throw new FileNotFoundException(path);
|
||||
}
|
||||
|
||||
AddMetadataFile(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the metadata file.
|
||||
/// </summary>
|
||||
/// <param name="fileInfo">The file info.</param>
|
||||
public void AddMetadataFile(FileSystemInfo fileInfo)
|
||||
{
|
||||
AddMetadataFiles(new[] { fileInfo });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the metadata files.
|
||||
/// </summary>
|
||||
/// <param name="files">The files.</param>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
public void AddMetadataFiles(IEnumerable<FileSystemInfo> files)
|
||||
{
|
||||
if (files == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (MetadataFileDictionary == null)
|
||||
{
|
||||
MetadataFileDictionary = new Dictionary<string, FileSystemInfo>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
foreach (var file in files)
|
||||
{
|
||||
MetadataFileDictionary[file.Name] = file;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the file system entry by.
|
||||
/// </summary>
|
||||
@@ -320,16 +249,6 @@ namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (MetadataFileDictionary != null)
|
||||
{
|
||||
FileSystemInfo entry;
|
||||
|
||||
if (MetadataFileDictionary.TryGetValue(System.IO.Path.GetFileName(path), out entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return GetFileSystemEntryByPath(path);
|
||||
}
|
||||
@@ -346,16 +265,6 @@ namespace MediaBrowser.Controller.Library
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (MetadataFileDictionary != null)
|
||||
{
|
||||
FileSystemInfo entry;
|
||||
|
||||
if (MetadataFileDictionary.TryGetValue(name, out entry))
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return GetFileSystemEntryByName(name);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,6 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
|
||||
bool IsParentalAllowed(User user);
|
||||
|
||||
Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
|
||||
Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class BaseItemXmlParser<T>
|
||||
where T : BaseItem, new()
|
||||
where T : BaseItem
|
||||
{
|
||||
/// <summary>
|
||||
/// The logger
|
||||
@@ -422,11 +422,7 @@ namespace MediaBrowser.Controller.Providers
|
||||
int runtime;
|
||||
if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out runtime))
|
||||
{
|
||||
// For audio and video don't replace ffmpeg data
|
||||
if (!(item is Video || item is Audio))
|
||||
{
|
||||
item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
|
||||
}
|
||||
item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2,13 +2,8 @@
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -146,19 +141,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
providerInfo.LastRefreshed = value;
|
||||
providerInfo.LastRefreshStatus = status;
|
||||
providerInfo.ProviderVersion = providerVersion;
|
||||
|
||||
// Save the file system stamp for future comparisons
|
||||
if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
try
|
||||
{
|
||||
providerInfo.FileStamp = GetCurrentFileSystemStamp(item);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error getting file stamp for {0}", ex, item.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -233,12 +215,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem &&
|
||||
HasFileSystemStampChanged(item, providerInfo))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion))
|
||||
{
|
||||
return true;
|
||||
@@ -263,17 +239,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
return CompareDate(item) > providerInfo.LastRefreshed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the item's file system stamp has changed from the last time the provider refreshed
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="providerInfo">The provider info.</param>
|
||||
/// <returns><c>true</c> if [has file system stamp changed] [the specified item]; otherwise, <c>false</c>.</returns>
|
||||
protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo)
|
||||
{
|
||||
return GetCurrentFileSystemStamp(item) != providerInfo.FileStamp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this to return the date that should be compared to the last refresh date
|
||||
/// to determine if this provider should be re-fetched.
|
||||
@@ -301,159 +266,5 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
/// <value>The priority.</value>
|
||||
public abstract MetadataProviderPriority Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
|
||||
protected virtual bool RefreshOnFileSystemStampChange
|
||||
{
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual string[] FilestampExtensions
|
||||
{
|
||||
get { return new string[] { }; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the parent's file system stamp should be used for comparison
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
|
||||
protected virtual bool UseParentFileSystemStamp(BaseItem item)
|
||||
{
|
||||
// True when the current item is just a file
|
||||
return !item.ResolveArgs.IsDirectory;
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
|
||||
{
|
||||
if (UseParentFileSystemStamp(item) && item.Parent != null)
|
||||
{
|
||||
return new[] { item.Parent };
|
||||
}
|
||||
|
||||
return new[] { item };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item's current file system stamp
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns>Guid.</returns>
|
||||
private Guid GetCurrentFileSystemStamp(BaseItem item)
|
||||
{
|
||||
return GetFileSystemStamp(GetItemsForFileStampComparison(item));
|
||||
}
|
||||
|
||||
private Dictionary<string, string> _fileStampExtensionsDictionary;
|
||||
|
||||
private Dictionary<string, string> FileStampExtensionsDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
return _fileStampExtensionsDictionary ??
|
||||
(_fileStampExtensionsDictionary =
|
||||
FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file system stamp.
|
||||
/// </summary>
|
||||
/// <param name="items">The items.</param>
|
||||
/// <returns>Guid.</returns>
|
||||
protected virtual Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var extensions = FileStampExtensionsDictionary;
|
||||
var numExtensions = FilestampExtensions.Length;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
// If there's no path or the item is a file, there's nothing to do
|
||||
if (item.LocationType == LocationType.FileSystem)
|
||||
{
|
||||
var resolveArgs = item.ResolveArgs;
|
||||
|
||||
if (resolveArgs.IsDirectory)
|
||||
{
|
||||
// Record the name of each file
|
||||
// Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
|
||||
AddFiles(sb, resolveArgs.FileSystemChildren, extensions, numExtensions);
|
||||
AddFiles(sb, resolveArgs.MetadataFiles, extensions, numExtensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var stamp = sb.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(stamp))
|
||||
{
|
||||
return Guid.Empty;
|
||||
}
|
||||
|
||||
return stamp.GetMD5();
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> FoldersToMonitor = new[] { "extrafanart", "extrathumbs" }
|
||||
.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
protected Guid GetFileSystemStamp(IEnumerable<FileSystemInfo> files)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var extensions = FileStampExtensionsDictionary;
|
||||
var numExtensions = FilestampExtensions.Length;
|
||||
|
||||
// Record the name of each file
|
||||
// Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
|
||||
AddFiles(sb, files, extensions, numExtensions);
|
||||
|
||||
return sb.ToString().GetMD5();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the files.
|
||||
/// </summary>
|
||||
/// <param name="sb">The sb.</param>
|
||||
/// <param name="files">The files.</param>
|
||||
/// <param name="extensions">The extensions.</param>
|
||||
/// <param name="numExtensions">The num extensions.</param>
|
||||
private void AddFiles(StringBuilder sb, IEnumerable<FileSystemInfo> files, Dictionary<string, string> extensions, int numExtensions)
|
||||
{
|
||||
foreach (var file in files
|
||||
.OrderBy(f => f.Name))
|
||||
{
|
||||
try
|
||||
{
|
||||
if ((file.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
if (FoldersToMonitor.ContainsKey(file.Name))
|
||||
{
|
||||
sb.Append(file.Name);
|
||||
|
||||
var children = ((DirectoryInfo)file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList();
|
||||
AddFiles(sb, children, extensions, numExtensions);
|
||||
}
|
||||
}
|
||||
|
||||
// It's a file
|
||||
else if (numExtensions == 0 || extensions.ContainsKey(file.Extension))
|
||||
{
|
||||
sb.Append(file.Name);
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Logger.ErrorException("Error accessing file attributes for {0}", ex, file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,5 +39,11 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
/// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns>
|
||||
bool IsSaveLocalMetadataEnabled();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this instance is in mixed folder.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
|
||||
bool IsInMixedFolder { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
{
|
||||
public interface ILocalMetadataProvider : IMetadataProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether [has local metadata] [the specified item].
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <returns><c>true</c> if [has local metadata] [the specified item]; otherwise, <c>false</c>.</returns>
|
||||
bool HasLocalMetadata(IHasMetadata item);
|
||||
}
|
||||
|
||||
public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider
|
||||
@@ -19,9 +13,16 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// <summary>
|
||||
/// Gets the metadata.
|
||||
/// </summary>
|
||||
/// <param name="path">The path.</param>
|
||||
/// <param name="info">The information.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{MetadataResult{`0}}.</returns>
|
||||
Task<MetadataResult<TItemType>> GetMetadata(string path, CancellationToken cancellationToken);
|
||||
Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
public class ItemInfo
|
||||
{
|
||||
public string Path { get; set; }
|
||||
|
||||
public bool IsInMixedFolder { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,15 +24,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// <returns>Task.</returns>
|
||||
Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Executes the metadata providers.
|
||||
/// </summary>
|
||||
/// <param name="item">The item.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <param name="force">if set to <c>true</c> [force].</param>
|
||||
/// <returns>Task{System.Boolean}.</returns>
|
||||
Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the image.
|
||||
/// </summary>
|
||||
@@ -60,12 +51,11 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// <summary>
|
||||
/// Adds the metadata providers.
|
||||
/// </summary>
|
||||
/// <param name="providers">The providers.</param>
|
||||
/// <param name="imageProviders">The image providers.</param>
|
||||
/// <param name="metadataServices">The metadata services.</param>
|
||||
/// <param name="metadataProviders">The metadata providers.</param>
|
||||
/// <param name="savers">The savers.</param>
|
||||
void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
|
||||
void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
|
||||
IEnumerable<IMetadataSaver> savers);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -16,17 +16,6 @@ namespace MediaBrowser.Controller.Providers
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public bool ForceSave { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// TODO: deprecate. Keeping this for now, for api compatibility
|
||||
/// </summary>
|
||||
[Obsolete]
|
||||
public bool ResetResolveArgs { get; set; }
|
||||
|
||||
public MetadataRefreshOptions()
|
||||
{
|
||||
ResetResolveArgs = true;
|
||||
}
|
||||
}
|
||||
|
||||
public class ImageRefreshOptions
|
||||
|
||||
Reference in New Issue
Block a user