move additional classes to new server lib

This commit is contained in:
Luke Pulverenti
2016-11-03 02:37:52 -04:00
parent 41bef184d1
commit 3eb4091808
88 changed files with 354 additions and 278 deletions

View File

@@ -1,56 +0,0 @@
using MediaBrowser.Common.Events;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Activity
{
public class ActivityManager : IActivityManager
{
public event EventHandler<GenericEventArgs<ActivityLogEntry>> EntryCreated;
private readonly IActivityRepository _repo;
private readonly ILogger _logger;
private readonly IUserManager _userManager;
public ActivityManager(ILogger logger, IActivityRepository repo, IUserManager userManager)
{
_logger = logger;
_repo = repo;
_userManager = userManager;
}
public async Task Create(ActivityLogEntry entry)
{
entry.Id = Guid.NewGuid().ToString("N");
entry.Date = DateTime.UtcNow;
await _repo.Create(entry).ConfigureAwait(false);
EventHelper.FireEventIfNotNull(EntryCreated, this, new GenericEventArgs<ActivityLogEntry>(entry), _logger);
}
public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, startIndex, limit);
foreach (var item in result.Items.Where(i => !string.IsNullOrWhiteSpace(i.UserId)))
{
var user = _userManager.GetUserById(item.UserId);
if (user != null)
{
var dto = _userManager.GetUserDto(user);
item.UserPrimaryImageTag = dto.PrimaryImageTag;
}
}
return result;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,831 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.FileOrganization;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Logging;
using MediaBrowser.Server.Implementations.Library;
using MediaBrowser.Server.Implementations.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
public class EpisodeFileOrganizer
{
private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IFileOrganizationService _organizationService;
private readonly IServerConfigurationManager _config;
private readonly IProviderManager _providerManager;
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
public EpisodeFileOrganizer(IFileOrganizationService organizationService, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager)
{
_organizationService = organizationService;
_config = config;
_fileSystem = fileSystem;
_logger = logger;
_libraryManager = libraryManager;
_libraryMonitor = libraryMonitor;
_providerManager = providerManager;
}
public async Task<FileOrganizationResult> OrganizeEpisodeFile(string path, AutoOrganizeOptions options, bool overwriteExisting, CancellationToken cancellationToken)
{
_logger.Info("Sorting file {0}", path);
var result = new FileOrganizationResult
{
Date = DateTime.UtcNow,
OriginalPath = path,
OriginalFileName = Path.GetFileName(path),
Type = FileOrganizerType.Episode,
FileSize = new FileInfo(path).Length
};
try
{
if (_libraryMonitor.IsPathLocked(path))
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = "Path is locked by other processes. Please try again later.";
return result;
}
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var resolver = new Naming.TV.EpisodeResolver(namingOptions, new PatternsLogger());
var episodeInfo = resolver.Resolve(path, false) ??
new Naming.TV.EpisodeInfo();
var seriesName = episodeInfo.SeriesName;
if (!string.IsNullOrEmpty(seriesName))
{
var seasonNumber = episodeInfo.SeasonNumber;
result.ExtractedSeasonNumber = seasonNumber;
// Passing in true will include a few extra regex's
var episodeNumber = episodeInfo.EpisodeNumber;
result.ExtractedEpisodeNumber = episodeNumber;
var premiereDate = episodeInfo.IsByDate ?
new DateTime(episodeInfo.Year.Value, episodeInfo.Month.Value, episodeInfo.Day.Value) :
(DateTime?)null;
if (episodeInfo.IsByDate || (seasonNumber.HasValue && episodeNumber.HasValue))
{
if (episodeInfo.IsByDate)
{
_logger.Debug("Extracted information from {0}. Series name {1}, Date {2}", path, seriesName, premiereDate.Value);
}
else
{
_logger.Debug("Extracted information from {0}. Series name {1}, Season {2}, Episode {3}", path, seriesName, seasonNumber, episodeNumber);
}
var endingEpisodeNumber = episodeInfo.EndingEpsiodeNumber;
result.ExtractedEndingEpisodeNumber = endingEpisodeNumber;
await OrganizeEpisode(path,
seriesName,
seasonNumber,
episodeNumber,
endingEpisodeNumber,
premiereDate,
options,
overwriteExisting,
false,
result,
cancellationToken).ConfigureAwait(false);
}
else
{
var msg = string.Format("Unable to determine episode number from {0}", path);
result.Status = FileSortingStatus.Failure;
result.StatusMessage = msg;
_logger.Warn(msg);
}
}
else
{
var msg = string.Format("Unable to determine series name from {0}", path);
result.Status = FileSortingStatus.Failure;
result.StatusMessage = msg;
_logger.Warn(msg);
}
var previousResult = _organizationService.GetResultBySourcePath(path);
if (previousResult != null)
{
// Don't keep saving the same result over and over if nothing has changed
if (previousResult.Status == result.Status && previousResult.StatusMessage == result.StatusMessage && result.Status != FileSortingStatus.Success)
{
return previousResult;
}
}
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
}
return result;
}
public async Task<FileOrganizationResult> OrganizeWithCorrection(EpisodeFileOrganizationRequest request, AutoOrganizeOptions options, CancellationToken cancellationToken)
{
var result = _organizationService.GetResult(request.ResultId);
try
{
Series series = null;
if (request.NewSeriesProviderIds.Count > 0)
{
// We're having a new series here
SeriesInfo seriesRequest = new SeriesInfo();
seriesRequest.ProviderIds = request.NewSeriesProviderIds;
var refreshOptions = new MetadataRefreshOptions(_fileSystem);
series = new Series();
series.Id = Guid.NewGuid();
series.Name = request.NewSeriesName;
int year;
if (int.TryParse(request.NewSeriesYear, out year))
{
series.ProductionYear = year;
}
var seriesFolderName = series.Name;
if (series.ProductionYear.HasValue)
{
seriesFolderName = string.Format("{0} ({1})", seriesFolderName, series.ProductionYear);
}
series.Path = Path.Combine(request.TargetFolder, seriesFolderName);
series.ProviderIds = request.NewSeriesProviderIds;
await series.RefreshMetadata(refreshOptions, cancellationToken);
}
if (series == null)
{
// Existing Series
series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId));
}
await OrganizeEpisode(result.OriginalPath,
series,
request.SeasonNumber,
request.EpisodeNumber,
request.EndingEpisodeNumber,
null,
options,
true,
request.RememberCorrection,
result,
cancellationToken).ConfigureAwait(false);
await _organizationService.SaveResult(result, CancellationToken.None).ConfigureAwait(false);
}
catch (Exception ex)
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
}
return result;
}
private Task OrganizeEpisode(string sourcePath,
string seriesName,
int? seasonNumber,
int? episodeNumber,
int? endingEpiosdeNumber,
DateTime? premiereDate,
AutoOrganizeOptions options,
bool overwriteExisting,
bool rememberCorrection,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
var series = GetMatchingSeries(seriesName, result, options);
if (series == null)
{
var msg = string.Format("Unable to find series in library matching name {0}", seriesName);
result.Status = FileSortingStatus.Failure;
result.StatusMessage = msg;
_logger.Warn(msg);
return Task.FromResult(true);
}
return OrganizeEpisode(sourcePath,
series,
seasonNumber,
episodeNumber,
endingEpiosdeNumber,
premiereDate,
options,
overwriteExisting,
rememberCorrection,
result,
cancellationToken);
}
private async Task OrganizeEpisode(string sourcePath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpiosdeNumber,
DateTime? premiereDate,
AutoOrganizeOptions options,
bool overwriteExisting,
bool rememberCorrection,
FileOrganizationResult result,
CancellationToken cancellationToken)
{
_logger.Info("Sorting file {0} into series {1}", sourcePath, series.Path);
var originalExtractedSeriesString = result.ExtractedName;
bool isNew = string.IsNullOrWhiteSpace(result.Id);
if (isNew)
{
await _organizationService.SaveResult(result, cancellationToken);
}
if (!_organizationService.AddToInProgressList(result, isNew))
{
throw new Exception("File is currently processed otherwise. Please try again later.");
}
try
{
// Proceed to sort the file
var newPath = await GetNewPath(sourcePath, series, seasonNumber, episodeNumber, endingEpiosdeNumber, premiereDate, options.TvOptions, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrEmpty(newPath))
{
var msg = string.Format("Unable to sort {0} because target path could not be determined.", sourcePath);
throw new Exception(msg);
}
_logger.Info("Sorting file {0} to new path {1}", sourcePath, newPath);
result.TargetPath = newPath;
var fileExists = _fileSystem.FileExists(result.TargetPath);
var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber);
if (!overwriteExisting)
{
if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
{
var msg = string.Format("File '{0}' already copied to new path '{1}', stopping organization", sourcePath, newPath);
_logger.Info(msg);
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
return;
}
if (fileExists)
{
var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, newPath);
_logger.Info(msg);
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
result.TargetPath = newPath;
return;
}
if (otherDuplicatePaths.Count > 0)
{
var msg = string.Format("File '{0}' already exists as these:'{1}'. Stopping organization", sourcePath, string.Join("', '", otherDuplicatePaths));
_logger.Info(msg);
result.Status = FileSortingStatus.SkippedExisting;
result.StatusMessage = msg;
result.DuplicatePaths = otherDuplicatePaths;
return;
}
}
PerformFileSorting(options.TvOptions, result);
if (overwriteExisting)
{
var hasRenamedFiles = false;
foreach (var path in otherDuplicatePaths)
{
_logger.Debug("Removing duplicate episode {0}", path);
_libraryMonitor.ReportFileSystemChangeBeginning(path);
var renameRelatedFiles = !hasRenamedFiles &&
string.Equals(Path.GetDirectoryName(path), Path.GetDirectoryName(result.TargetPath), StringComparison.OrdinalIgnoreCase);
if (renameRelatedFiles)
{
hasRenamedFiles = true;
}
try
{
DeleteLibraryFile(path, renameRelatedFiles, result.TargetPath);
}
catch (IOException ex)
{
_logger.ErrorException("Error removing duplicate episode", ex, path);
}
finally
{
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
}
}
}
}
catch (Exception ex)
{
result.Status = FileSortingStatus.Failure;
result.StatusMessage = ex.Message;
_logger.Warn(ex.Message);
return;
}
finally
{
_organizationService.RemoveFromInprogressList(result);
}
if (rememberCorrection)
{
SaveSmartMatchString(originalExtractedSeriesString, series, options);
}
}
private void SaveSmartMatchString(string matchString, Series series, AutoOrganizeOptions options)
{
if (string.IsNullOrEmpty(matchString) || matchString.Length < 3)
{
return;
}
SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, series.Name, StringComparison.OrdinalIgnoreCase));
if (info == null)
{
info = new SmartMatchInfo();
info.ItemName = series.Name;
info.OrganizerType = FileOrganizerType.Episode;
info.DisplayName = series.Name;
var list = options.SmartMatchInfos.ToList();
list.Add(info);
options.SmartMatchInfos = list.ToArray();
}
if (!info.MatchStrings.Contains(matchString, StringComparer.OrdinalIgnoreCase))
{
var list = info.MatchStrings.ToList();
list.Add(matchString);
info.MatchStrings = list.ToArray();
_config.SaveAutoOrganizeOptions(options);
}
}
private void DeleteLibraryFile(string path, bool renameRelatedFiles, string targetPath)
{
_fileSystem.DeleteFile(path);
if (!renameRelatedFiles)
{
return;
}
// Now find other files
var originalFilenameWithoutExtension = Path.GetFileNameWithoutExtension(path);
var directory = Path.GetDirectoryName(path);
if (!string.IsNullOrWhiteSpace(originalFilenameWithoutExtension) && !string.IsNullOrWhiteSpace(directory))
{
// Get all related files, e.g. metadata, images, etc
var files = _fileSystem.GetFilePaths(directory)
.Where(i => (Path.GetFileNameWithoutExtension(i) ?? string.Empty).StartsWith(originalFilenameWithoutExtension, StringComparison.OrdinalIgnoreCase))
.ToList();
var targetFilenameWithoutExtension = Path.GetFileNameWithoutExtension(targetPath);
foreach (var file in files)
{
directory = Path.GetDirectoryName(file);
var filename = Path.GetFileName(file);
filename = filename.Replace(originalFilenameWithoutExtension, targetFilenameWithoutExtension,
StringComparison.OrdinalIgnoreCase);
var destination = Path.Combine(directory, filename);
_fileSystem.MoveFile(file, destination);
}
}
}
private List<string> GetOtherDuplicatePaths(string targetPath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpisodeNumber)
{
// TODO: Support date-naming?
if (!seasonNumber.HasValue || !episodeNumber.HasValue)
{
return new List<string>();
}
var episodePaths = series.GetRecursiveChildren()
.OfType<Episode>()
.Where(i =>
{
var locationType = i.LocationType;
// Must be file system based and match exactly
if (locationType != LocationType.Remote &&
locationType != LocationType.Virtual &&
i.ParentIndexNumber.HasValue &&
i.ParentIndexNumber.Value == seasonNumber &&
i.IndexNumber.HasValue &&
i.IndexNumber.Value == episodeNumber)
{
if (endingEpisodeNumber.HasValue || i.IndexNumberEnd.HasValue)
{
return endingEpisodeNumber.HasValue && i.IndexNumberEnd.HasValue &&
endingEpisodeNumber.Value == i.IndexNumberEnd.Value;
}
return true;
}
return false;
})
.Select(i => i.Path)
.ToList();
var folder = Path.GetDirectoryName(targetPath);
var targetFileNameWithoutExtension = _fileSystem.GetFileNameWithoutExtension(targetPath);
try
{
var filesOfOtherExtensions = _fileSystem.GetFilePaths(folder)
.Where(i => _libraryManager.IsVideoFile(i) && string.Equals(_fileSystem.GetFileNameWithoutExtension(i), targetFileNameWithoutExtension, StringComparison.OrdinalIgnoreCase));
episodePaths.AddRange(filesOfOtherExtensions);
}
catch (DirectoryNotFoundException)
{
// No big deal. Maybe the season folder doesn't already exist.
}
return episodePaths.Where(i => !string.Equals(i, targetPath, StringComparison.OrdinalIgnoreCase))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
private void PerformFileSorting(TvFileOrganizationOptions options, FileOrganizationResult result)
{
_libraryMonitor.ReportFileSystemChangeBeginning(result.TargetPath);
_fileSystem.CreateDirectory(Path.GetDirectoryName(result.TargetPath));
var targetAlreadyExists = _fileSystem.FileExists(result.TargetPath);
try
{
if (targetAlreadyExists || options.CopyOriginalFile)
{
_fileSystem.CopyFile(result.OriginalPath, result.TargetPath, true);
}
else
{
_fileSystem.MoveFile(result.OriginalPath, result.TargetPath);
}
result.Status = FileSortingStatus.Success;
result.StatusMessage = string.Empty;
}
catch (Exception ex)
{
var errorMsg = string.Format("Failed to move file from {0} to {1}: {2}", result.OriginalPath, result.TargetPath, ex.Message);
result.Status = FileSortingStatus.Failure;
result.StatusMessage = errorMsg;
_logger.ErrorException(errorMsg, ex);
return;
}
finally
{
_libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true);
}
if (targetAlreadyExists && !options.CopyOriginalFile)
{
try
{
_fileSystem.DeleteFile(result.OriginalPath);
}
catch (Exception ex)
{
_logger.ErrorException("Error deleting {0}", ex, result.OriginalPath);
}
}
}
private Series GetMatchingSeries(string seriesName, FileOrganizationResult result, AutoOrganizeOptions options)
{
var parsedName = _libraryManager.ParseName(seriesName);
var yearInName = parsedName.Year;
var nameWithoutYear = parsedName.Name;
result.ExtractedName = nameWithoutYear;
result.ExtractedYear = yearInName;
var series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Series).Name },
Recursive = true
})
.Cast<Series>()
.Select(i => NameUtils.GetMatchScore(nameWithoutYear, yearInName, i))
.Where(i => i.Item2 > 0)
.OrderByDescending(i => i.Item2)
.Select(i => i.Item1)
.FirstOrDefault();
if (series == null)
{
SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(e => e.MatchStrings.Contains(nameWithoutYear, StringComparer.OrdinalIgnoreCase));
if (info != null)
{
series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Series).Name },
Recursive = true,
Name = info.ItemName
}).Cast<Series>().FirstOrDefault();
}
}
return series;
}
/// <summary>
/// Gets the new path.
/// </summary>
/// <param name="sourcePath">The source path.</param>
/// <param name="series">The series.</param>
/// <param name="seasonNumber">The season number.</param>
/// <param name="episodeNumber">The episode number.</param>
/// <param name="endingEpisodeNumber">The ending episode number.</param>
/// <param name="premiereDate">The premiere date.</param>
/// <param name="options">The options.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>System.String.</returns>
private async Task<string> GetNewPath(string sourcePath,
Series series,
int? seasonNumber,
int? episodeNumber,
int? endingEpisodeNumber,
DateTime? premiereDate,
TvFileOrganizationOptions options,
CancellationToken cancellationToken)
{
var episodeInfo = new EpisodeInfo
{
IndexNumber = episodeNumber,
IndexNumberEnd = endingEpisodeNumber,
MetadataCountryCode = series.GetPreferredMetadataCountryCode(),
MetadataLanguage = series.GetPreferredMetadataLanguage(),
ParentIndexNumber = seasonNumber,
SeriesProviderIds = series.ProviderIds,
PremiereDate = premiereDate
};
var searchResults = await _providerManager.GetRemoteSearchResults<Episode, EpisodeInfo>(new RemoteSearchQuery<EpisodeInfo>
{
SearchInfo = episodeInfo
}, cancellationToken).ConfigureAwait(false);
var episode = searchResults.FirstOrDefault();
if (episode == null)
{
var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
_logger.Warn(msg);
throw new Exception(msg);
}
var episodeName = episode.Name;
//if (string.IsNullOrWhiteSpace(episodeName))
//{
// var msg = string.Format("No provider metadata found for {0} season {1} episode {2}", series.Name, seasonNumber, episodeNumber);
// _logger.Warn(msg);
// return null;
//}
seasonNumber = seasonNumber ?? episode.ParentIndexNumber;
episodeNumber = episodeNumber ?? episode.IndexNumber;
var newPath = GetSeasonFolderPath(series, seasonNumber.Value, options);
// MAX_PATH - trailing <NULL> charachter - drive component: 260 - 1 - 3 = 256
// Usually newPath would include the drive component, but use 256 to be sure
var maxFilenameLength = 256 - newPath.Length;
if (!newPath.EndsWith(@"\"))
{
// Remove 1 for missing backslash combining path and filename
maxFilenameLength--;
}
// Remove additional 4 chars to prevent PathTooLongException for downloaded subtitles (eg. filename.ext.eng.srt)
maxFilenameLength -= 4;
var episodeFileName = GetEpisodeFileName(sourcePath, series.Name, seasonNumber.Value, episodeNumber.Value, endingEpisodeNumber, episodeName, options, maxFilenameLength);
if (string.IsNullOrEmpty(episodeFileName))
{
// cause failure
return string.Empty;
}
newPath = Path.Combine(newPath, episodeFileName);
return newPath;
}
/// <summary>
/// Gets the season folder path.
/// </summary>
/// <param name="series">The series.</param>
/// <param name="seasonNumber">The season number.</param>
/// <param name="options">The options.</param>
/// <returns>System.String.</returns>
private string GetSeasonFolderPath(Series series, int seasonNumber, TvFileOrganizationOptions options)
{
// If there's already a season folder, use that
var season = series
.GetRecursiveChildren(i => i is Season && i.LocationType == LocationType.FileSystem && i.IndexNumber.HasValue && i.IndexNumber.Value == seasonNumber)
.FirstOrDefault();
if (season != null)
{
return season.Path;
}
var path = series.Path;
if (series.ContainsEpisodesWithoutSeasonFolders)
{
return path;
}
if (seasonNumber == 0)
{
return Path.Combine(path, _fileSystem.GetValidFilename(options.SeasonZeroFolderName));
}
var seasonFolderName = options.SeasonFolderPattern
.Replace("%s", seasonNumber.ToString(_usCulture))
.Replace("%0s", seasonNumber.ToString("00", _usCulture))
.Replace("%00s", seasonNumber.ToString("000", _usCulture));
return Path.Combine(path, _fileSystem.GetValidFilename(seasonFolderName));
}
private string GetEpisodeFileName(string sourcePath, string seriesName, int seasonNumber, int episodeNumber, int? endingEpisodeNumber, string episodeTitle, TvFileOrganizationOptions options, int? maxLength)
{
seriesName = _fileSystem.GetValidFilename(seriesName).Trim();
if (string.IsNullOrWhiteSpace(episodeTitle))
{
episodeTitle = string.Empty;
}
else
{
episodeTitle = _fileSystem.GetValidFilename(episodeTitle).Trim();
}
var sourceExtension = (Path.GetExtension(sourcePath) ?? string.Empty).TrimStart('.');
var pattern = endingEpisodeNumber.HasValue ? options.MultiEpisodeNamePattern : options.EpisodeNamePattern;
if (string.IsNullOrWhiteSpace(pattern))
{
throw new Exception("GetEpisodeFileName: Configured episode name pattern is empty!");
}
var result = pattern.Replace("%sn", seriesName)
.Replace("%s.n", seriesName.Replace(" ", "."))
.Replace("%s_n", seriesName.Replace(" ", "_"))
.Replace("%s", seasonNumber.ToString(_usCulture))
.Replace("%0s", seasonNumber.ToString("00", _usCulture))
.Replace("%00s", seasonNumber.ToString("000", _usCulture))
.Replace("%ext", sourceExtension)
.Replace("%en", "%#1")
.Replace("%e.n", "%#2")
.Replace("%e_n", "%#3");
if (endingEpisodeNumber.HasValue)
{
result = result.Replace("%ed", endingEpisodeNumber.Value.ToString(_usCulture))
.Replace("%0ed", endingEpisodeNumber.Value.ToString("00", _usCulture))
.Replace("%00ed", endingEpisodeNumber.Value.ToString("000", _usCulture));
}
result = result.Replace("%e", episodeNumber.ToString(_usCulture))
.Replace("%0e", episodeNumber.ToString("00", _usCulture))
.Replace("%00e", episodeNumber.ToString("000", _usCulture));
if (maxLength.HasValue && result.Contains("%#"))
{
// Substract 3 for the temp token length (%#1, %#2 or %#3)
int maxRemainingTitleLength = maxLength.Value - result.Length + 3;
string shortenedEpisodeTitle = string.Empty;
if (maxRemainingTitleLength > 5)
{
// A title with fewer than 5 letters wouldn't be of much value
shortenedEpisodeTitle = episodeTitle.Substring(0, Math.Min(maxRemainingTitleLength, episodeTitle.Length));
}
result = result.Replace("%#1", shortenedEpisodeTitle)
.Replace("%#2", shortenedEpisodeTitle.Replace(" ", "."))
.Replace("%#3", shortenedEpisodeTitle.Replace(" ", "_"));
}
if (maxLength.HasValue && result.Length > maxLength.Value)
{
// There may be cases where reducing the title length may still not be sufficient to
// stay below maxLength
var msg = string.Format("Unable to generate an episode file name shorter than {0} characters to constrain to the max path limit", maxLength);
throw new Exception(msg);
}
return result;
}
private bool IsSameEpisode(string sourcePath, string newPath)
{
try
{
var sourceFileInfo = new FileInfo(sourcePath);
var destinationFileInfo = new FileInfo(newPath);
if (sourceFileInfo.Length == destinationFileInfo.Length)
{
return true;
}
}
catch (FileNotFoundException)
{
return false;
}
catch (DirectoryNotFoundException)
{
return false;
}
return false;
}
}
}

View File

@@ -1,33 +0,0 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.FileOrganization;
using System.Collections.Generic;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
public static class ConfigurationExtension
{
public static AutoOrganizeOptions GetAutoOrganizeOptions(this IConfigurationManager manager)
{
return manager.GetConfiguration<AutoOrganizeOptions>("autoorganize");
}
public static void SaveAutoOrganizeOptions(this IConfigurationManager manager, AutoOrganizeOptions options)
{
manager.SaveConfiguration("autoorganize", options);
}
}
public class AutoOrganizeOptionsFactory : IConfigurationFactory
{
public IEnumerable<ConfigurationStore> GetConfigurations()
{
return new List<ConfigurationStore>
{
new ConfigurationStore
{
Key = "autoorganize",
ConfigurationType = typeof (AutoOrganizeOptions)
}
};
}
}
}

View File

@@ -1,80 +0,0 @@
using MediaBrowser.Controller.FileOrganization;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
/// <summary>
/// Class SessionInfoWebSocketListener
/// </summary>
class FileOrganizationNotifier : IServerEntryPoint
{
private readonly IFileOrganizationService _organizationService;
private readonly ISessionManager _sessionManager;
private readonly ITaskManager _taskManager;
public FileOrganizationNotifier(ILogger logger, IFileOrganizationService organizationService, ISessionManager sessionManager, ITaskManager taskManager)
{
_organizationService = organizationService;
_sessionManager = sessionManager;
_taskManager = taskManager;
}
public void Run()
{
_organizationService.ItemAdded += _organizationService_ItemAdded;
_organizationService.ItemRemoved += _organizationService_ItemRemoved;
_organizationService.ItemUpdated += _organizationService_ItemUpdated;
_organizationService.LogReset += _organizationService_LogReset;
//_taskManager.TaskCompleted += _taskManager_TaskCompleted;
}
private void _organizationService_LogReset(object sender, EventArgs e)
{
_sessionManager.SendMessageToAdminSessions("AutoOrganize_LogReset", (FileOrganizationResult)null, CancellationToken.None);
}
private void _organizationService_ItemUpdated(object sender, GenericEventArgs<FileOrganizationResult> e)
{
_sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemUpdated", e.Argument, CancellationToken.None);
}
private void _organizationService_ItemRemoved(object sender, GenericEventArgs<FileOrganizationResult> e)
{
_sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemRemoved", e.Argument, CancellationToken.None);
}
private void _organizationService_ItemAdded(object sender, GenericEventArgs<FileOrganizationResult> e)
{
_sessionManager.SendMessageToAdminSessions("AutoOrganize_ItemAdded", e.Argument, CancellationToken.None);
}
//private void _taskManager_TaskCompleted(object sender, TaskCompletionEventArgs e)
//{
// var taskWithKey = e.Task.ScheduledTask as IHasKey;
// if (taskWithKey != null && taskWithKey.Key == "AutoOrganize")
// {
// _sessionManager.SendMessageToAdminSessions("AutoOrganize_TaskCompleted", (FileOrganizationResult)null, CancellationToken.None);
// }
//}
public void Dispose()
{
_organizationService.ItemAdded -= _organizationService_ItemAdded;
_organizationService.ItemRemoved -= _organizationService_ItemRemoved;
_organizationService.ItemUpdated -= _organizationService_ItemUpdated;
_organizationService.LogReset -= _organizationService_LogReset;
//_taskManager.TaskCompleted -= _taskManager_TaskCompleted;
}
}
}

View File

@@ -1,283 +0,0 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.FileOrganization;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Events;
using MediaBrowser.Common.Events;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
public class FileOrganizationService : IFileOrganizationService
{
private readonly ITaskManager _taskManager;
private readonly IFileOrganizationRepository _repo;
private readonly ILogger _logger;
private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IProviderManager _providerManager;
private readonly ConcurrentDictionary<string, bool> _inProgressItemIds = new ConcurrentDictionary<string, bool>();
public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemAdded;
public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemUpdated;
public event EventHandler<GenericEventArgs<FileOrganizationResult>> ItemRemoved;
public event EventHandler LogReset;
public FileOrganizationService(ITaskManager taskManager, IFileOrganizationRepository repo, ILogger logger, ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, IServerConfigurationManager config, IFileSystem fileSystem, IProviderManager providerManager)
{
_taskManager = taskManager;
_repo = repo;
_logger = logger;
_libraryMonitor = libraryMonitor;
_libraryManager = libraryManager;
_config = config;
_fileSystem = fileSystem;
_providerManager = providerManager;
}
public void BeginProcessNewFiles()
{
_taskManager.CancelIfRunningAndQueue<OrganizerScheduledTask>();
}
public Task SaveResult(FileOrganizationResult result, CancellationToken cancellationToken)
{
if (result == null || string.IsNullOrEmpty(result.OriginalPath))
{
throw new ArgumentNullException("result");
}
result.Id = result.OriginalPath.GetMD5().ToString("N");
return _repo.SaveResult(result, cancellationToken);
}
public QueryResult<FileOrganizationResult> GetResults(FileOrganizationResultQuery query)
{
var results = _repo.GetResults(query);
foreach (var result in results.Items)
{
result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id);
}
return results;
}
public FileOrganizationResult GetResult(string id)
{
var result = _repo.GetResult(id);
if (result != null)
{
result.IsInProgress = _inProgressItemIds.ContainsKey(result.Id);
}
return result;
}
public FileOrganizationResult GetResultBySourcePath(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
var id = path.GetMD5().ToString("N");
return GetResult(id);
}
public async Task DeleteOriginalFile(string resultId)
{
var result = _repo.GetResult(resultId);
_logger.Info("Requested to delete {0}", result.OriginalPath);
if (!AddToInProgressList(result, false))
{
throw new Exception("Path is currently processed otherwise. Please try again later.");
}
try
{
_fileSystem.DeleteFile(result.OriginalPath);
}
catch (Exception ex)
{
_logger.ErrorException("Error deleting {0}", ex, result.OriginalPath);
}
finally
{
RemoveFromInprogressList(result);
}
await _repo.Delete(resultId);
EventHelper.FireEventIfNotNull(ItemRemoved, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
}
private AutoOrganizeOptions GetAutoOrganizeOptions()
{
return _config.GetAutoOrganizeOptions();
}
public async Task PerformOrganization(string resultId)
{
var result = _repo.GetResult(resultId);
if (string.IsNullOrEmpty(result.TargetPath))
{
throw new ArgumentException("No target path available.");
}
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_libraryMonitor, _providerManager);
var organizeResult = await organizer.OrganizeEpisodeFile(result.OriginalPath, GetAutoOrganizeOptions(), true, CancellationToken.None)
.ConfigureAwait(false);
if (organizeResult.Status != FileSortingStatus.Success)
{
throw new Exception(result.StatusMessage);
}
}
public async Task ClearLog()
{
await _repo.DeleteAll();
EventHelper.FireEventIfNotNull(LogReset, this, EventArgs.Empty, _logger);
}
public async Task PerformEpisodeOrganization(EpisodeFileOrganizationRequest request)
{
var organizer = new EpisodeFileOrganizer(this, _config, _fileSystem, _logger, _libraryManager,
_libraryMonitor, _providerManager);
var result = await organizer.OrganizeWithCorrection(request, GetAutoOrganizeOptions(), CancellationToken.None).ConfigureAwait(false);
if (result.Status != FileSortingStatus.Success)
{
throw new Exception(result.StatusMessage);
}
}
public QueryResult<SmartMatchInfo> GetSmartMatchInfos(FileOrganizationResultQuery query)
{
if (query == null)
{
throw new ArgumentNullException("query");
}
var options = GetAutoOrganizeOptions();
var items = options.SmartMatchInfos.Skip(query.StartIndex ?? 0).Take(query.Limit ?? Int32.MaxValue).ToArray();
return new QueryResult<SmartMatchInfo>()
{
Items = items,
TotalRecordCount = options.SmartMatchInfos.Length
};
}
public void DeleteSmartMatchEntry(string itemName, string matchString)
{
if (string.IsNullOrEmpty(itemName))
{
throw new ArgumentNullException("itemName");
}
if (string.IsNullOrEmpty(matchString))
{
throw new ArgumentNullException("matchString");
}
var options = GetAutoOrganizeOptions();
SmartMatchInfo info = options.SmartMatchInfos.FirstOrDefault(i => string.Equals(i.ItemName, itemName));
if (info != null && info.MatchStrings.Contains(matchString))
{
var list = info.MatchStrings.ToList();
list.Remove(matchString);
info.MatchStrings = list.ToArray();
if (info.MatchStrings.Length == 0)
{
var infos = options.SmartMatchInfos.ToList();
infos.Remove(info);
options.SmartMatchInfos = infos.ToArray();
}
_config.SaveAutoOrganizeOptions(options);
}
}
/// <summary>
/// Attempts to add a an item to the list of currently processed items.
/// </summary>
/// <param name="result">The result item.</param>
/// <param name="isNewItem">Passing true will notify the client to reload all items, otherwise only a single item will be refreshed.</param>
/// <returns>True if the item was added, False if the item is already contained in the list.</returns>
public bool AddToInProgressList(FileOrganizationResult result, bool isNewItem)
{
if (string.IsNullOrWhiteSpace(result.Id))
{
result.Id = result.OriginalPath.GetMD5().ToString("N");
}
if (!_inProgressItemIds.TryAdd(result.Id, false))
{
return false;
}
result.IsInProgress = true;
if (isNewItem)
{
EventHelper.FireEventIfNotNull(ItemAdded, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
}
else
{
EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
}
return true;
}
/// <summary>
/// Removes an item from the list of currently processed items.
/// </summary>
/// <param name="result">The result item.</param>
/// <returns>True if the item was removed, False if the item was not contained in the list.</returns>
public bool RemoveFromInprogressList(FileOrganizationResult result)
{
bool itemValue;
var retval = _inProgressItemIds.TryRemove(result.Id, out itemValue);
result.IsInProgress = false;
EventHelper.FireEventIfNotNull(ItemUpdated, this, new GenericEventArgs<FileOrganizationResult>(result), _logger);
return retval;
}
}
}

View File

@@ -1,96 +0,0 @@
using MediaBrowser.Model.Extensions;
using MediaBrowser.Controller.Entities;
using System;
using System.Globalization;
using System.Linq;
using System.Text;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
public static class NameUtils
{
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
internal static Tuple<T, int> GetMatchScore<T>(string sortedName, int? year, T series)
where T : BaseItem
{
var score = 0;
var seriesNameWithoutYear = series.Name;
if (series.ProductionYear.HasValue)
{
seriesNameWithoutYear = seriesNameWithoutYear.Replace(series.ProductionYear.Value.ToString(UsCulture), String.Empty);
}
if (IsNameMatch(sortedName, seriesNameWithoutYear))
{
score++;
if (year.HasValue && series.ProductionYear.HasValue)
{
if (year.Value == series.ProductionYear.Value)
{
score++;
}
else
{
// Regardless of name, return a 0 score if the years don't match
return new Tuple<T, int>(series, 0);
}
}
}
return new Tuple<T, int>(series, score);
}
private static bool IsNameMatch(string name1, string name2)
{
name1 = GetComparableName(name1);
name2 = GetComparableName(name2);
return String.Equals(name1, name2, StringComparison.OrdinalIgnoreCase);
}
private static string GetComparableName(string name)
{
name = RemoveDiacritics(name);
name = " " + name + " ";
name = name.Replace(".", " ")
.Replace("_", " ")
.Replace(" and ", " ")
.Replace(".and.", " ")
.Replace("&", " ")
.Replace("!", " ")
.Replace("(", " ")
.Replace(")", " ")
.Replace(":", " ")
.Replace(",", " ")
.Replace("-", " ")
.Replace("'", " ")
.Replace("[", " ")
.Replace("]", " ")
.Replace(" a ", String.Empty, StringComparison.OrdinalIgnoreCase)
.Replace(" the ", String.Empty, StringComparison.OrdinalIgnoreCase)
.Replace(" ", String.Empty);
return name.Trim();
}
/// <summary>
/// Removes the diacritics.
/// </summary>
/// <param name="text">The text.</param>
/// <returns>System.String.</returns>
private static string RemoveDiacritics(string text)
{
return String.Concat(
text.Normalize(NormalizationForm.FormD)
.Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) !=
UnicodeCategory.NonSpacingMark)
).Normalize(NormalizationForm.FormC);
}
}
}

View File

@@ -1,101 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.FileOrganization;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
public class OrganizerScheduledTask : IScheduledTask, IConfigurableScheduledTask
{
private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IServerConfigurationManager _config;
private readonly IFileOrganizationService _organizationService;
private readonly IProviderManager _providerManager;
public OrganizerScheduledTask(ILibraryMonitor libraryMonitor, ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, IServerConfigurationManager config, IFileOrganizationService organizationService, IProviderManager providerManager)
{
_libraryMonitor = libraryMonitor;
_libraryManager = libraryManager;
_logger = logger;
_fileSystem = fileSystem;
_config = config;
_organizationService = organizationService;
_providerManager = providerManager;
}
public string Name
{
get { return "Organize new media files"; }
}
public string Description
{
get { return "Processes new files available in the configured watch folder."; }
}
public string Category
{
get { return "Library"; }
}
private AutoOrganizeOptions GetAutoOrganizeOptions()
{
return _config.GetAutoOrganizeOptions();
}
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
if (GetAutoOrganizeOptions().TvOptions.IsEnabled)
{
await new TvFolderOrganizer(_libraryManager, _logger, _fileSystem, _libraryMonitor, _organizationService, _config, _providerManager)
.Organize(GetAutoOrganizeOptions(), cancellationToken, progress).ConfigureAwait(false);
}
}
/// <summary>
/// Creates the triggers that define when the task will run
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[] {
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromMinutes(5).Ticks}
};
}
public bool IsHidden
{
get { return !GetAutoOrganizeOptions().TvOptions.IsEnabled; }
}
public bool IsEnabled
{
get { return GetAutoOrganizeOptions().TvOptions.IsEnabled; }
}
public bool IsLogged
{
get { return false; }
}
public string Key
{
get { return "AutoOrganize"; }
}
}
}

View File

@@ -1,210 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.FileOrganization;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.FileOrganization;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Server.Implementations.FileOrganization
{
public class TvFolderOrganizer
{
private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly IFileOrganizationService _organizationService;
private readonly IServerConfigurationManager _config;
private readonly IProviderManager _providerManager;
public TvFolderOrganizer(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IFileOrganizationService organizationService, IServerConfigurationManager config, IProviderManager providerManager)
{
_libraryManager = libraryManager;
_logger = logger;
_fileSystem = fileSystem;
_libraryMonitor = libraryMonitor;
_organizationService = organizationService;
_config = config;
_providerManager = providerManager;
}
private bool EnableOrganization(FileSystemMetadata fileInfo, TvFileOrganizationOptions options)
{
var minFileBytes = options.MinFileSizeMb * 1024 * 1024;
try
{
return _libraryManager.IsVideoFile(fileInfo.FullName) && fileInfo.Length >= minFileBytes;
}
catch (Exception ex)
{
_logger.ErrorException("Error organizing file {0}", ex, fileInfo.Name);
}
return false;
}
public async Task Organize(AutoOrganizeOptions options, CancellationToken cancellationToken, IProgress<double> progress)
{
var watchLocations = options.TvOptions.WatchLocations.ToList();
var eligibleFiles = watchLocations.SelectMany(GetFilesToOrganize)
.OrderBy(_fileSystem.GetCreationTimeUtc)
.Where(i => EnableOrganization(i, options.TvOptions))
.ToList();
var processedFolders = new HashSet<string>();
progress.Report(10);
if (eligibleFiles.Count > 0)
{
var numComplete = 0;
foreach (var file in eligibleFiles)
{
var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager,
_libraryMonitor, _providerManager);
try
{
var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.TvOptions.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false);
if (result.Status == FileSortingStatus.Success && !processedFolders.Contains(file.DirectoryName, StringComparer.OrdinalIgnoreCase))
{
processedFolders.Add(file.DirectoryName);
}
}
catch (Exception ex)
{
_logger.ErrorException("Error organizing episode {0}", ex, file.FullName);
}
numComplete++;
double percent = numComplete;
percent /= eligibleFiles.Count;
progress.Report(10 + 89 * percent);
}
}
cancellationToken.ThrowIfCancellationRequested();
progress.Report(99);
foreach (var path in processedFolders)
{
var deleteExtensions = options.TvOptions.LeftOverFileExtensionsToDelete
.Select(i => i.Trim().TrimStart('.'))
.Where(i => !string.IsNullOrEmpty(i))
.Select(i => "." + i)
.ToList();
if (deleteExtensions.Count > 0)
{
DeleteLeftOverFiles(path, deleteExtensions);
}
if (options.TvOptions.DeleteEmptyFolders)
{
if (!IsWatchFolder(path, watchLocations))
{
DeleteEmptyFolders(path);
}
}
}
progress.Report(100);
}
/// <summary>
/// Gets the files to organize.
/// </summary>
/// <param name="path">The path.</param>
/// <returns>IEnumerable{FileInfo}.</returns>
private List<FileSystemMetadata> GetFilesToOrganize(string path)
{
try
{
return _fileSystem.GetFiles(path, true)
.ToList();
}
catch (IOException ex)
{
_logger.ErrorException("Error getting files from {0}", ex, path);
return new List<FileSystemMetadata>();
}
}
/// <summary>
/// Deletes the left over files.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="extensions">The extensions.</param>
private void DeleteLeftOverFiles(string path, IEnumerable<string> extensions)
{
var eligibleFiles = _fileSystem.GetFiles(path, true)
.Where(i => extensions.Contains(i.Extension, StringComparer.OrdinalIgnoreCase))
.ToList();
foreach (var file in eligibleFiles)
{
try
{
_fileSystem.DeleteFile(file.FullName);
}
catch (Exception ex)
{
_logger.ErrorException("Error deleting file {0}", ex, file.FullName);
}
}
}
/// <summary>
/// Deletes the empty folders.
/// </summary>
/// <param name="path">The path.</param>
private void DeleteEmptyFolders(string path)
{
try
{
foreach (var d in _fileSystem.GetDirectoryPaths(path))
{
DeleteEmptyFolders(d);
}
var entries = _fileSystem.GetFileSystemEntryPaths(path);
if (!entries.Any())
{
try
{
_logger.Debug("Deleting empty directory {0}", path);
_fileSystem.DeleteDirectory(path, false);
}
catch (UnauthorizedAccessException) { }
catch (DirectoryNotFoundException) { }
}
}
catch (UnauthorizedAccessException) { }
}
/// <summary>
/// Determines if a given folder path is contained in a folder list
/// </summary>
/// <param name="path">The folder path to check.</param>
/// <param name="watchLocations">A list of folders.</param>
private bool IsWatchFolder(string path, IEnumerable<string> watchLocations)
{
return watchLocations.Contains(path, StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@@ -14,7 +14,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Server.Implementations.ScheduledTasks;
using MediaBrowser.Model.Threading;
namespace MediaBrowser.Server.Implementations.IO
{
@@ -26,13 +26,14 @@ namespace MediaBrowser.Server.Implementations.IO
private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem;
private readonly List<string> _affectedPaths = new List<string>();
private Timer _timer;
private ITimer _timer;
private readonly ITimerFactory _timerFactory;
private readonly object _timerLock = new object();
public string Path { get; private set; }
public event EventHandler<EventArgs> Completed;
public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger)
public FileRefresher(string path, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILibraryManager libraryManager, ITaskManager taskManager, ILogger logger, ITimerFactory timerFactory)
{
logger.Debug("New file refresher created for {0}", path);
Path = path;
@@ -42,6 +43,7 @@ namespace MediaBrowser.Server.Implementations.IO
LibraryManager = libraryManager;
TaskManager = taskManager;
Logger = logger;
_timerFactory = timerFactory;
AddPath(path);
}
@@ -88,7 +90,7 @@ namespace MediaBrowser.Server.Implementations.IO
if (_timer == null)
{
_timer = new Timer(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
_timer = _timerFactory.Create(OnTimerCallback, null, TimeSpan.FromSeconds(ConfigurationManager.Configuration.LibraryMonitorDelay), TimeSpan.FromMilliseconds(-1));
}
else
{
@@ -163,7 +165,7 @@ namespace MediaBrowser.Server.Implementations.IO
// If the root folder changed, run the library task so the user can see it
if (itemsToRefresh.Any(i => i is AggregateFolder))
{
TaskManager.CancelIfRunningAndQueue<RefreshMediaLibraryTask>();
LibraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None);
return;
}
@@ -268,11 +270,11 @@ namespace MediaBrowser.Server.Implementations.IO
return false;
}
}
catch (DirectoryNotFoundException)
{
// File may have been deleted
return false;
}
//catch (DirectoryNotFoundException)
//{
// // File may have been deleted
// return false;
//}
catch (FileNotFoundException)
{
// File may have been deleted

View File

@@ -16,6 +16,7 @@ using MediaBrowser.Model.IO;
using MediaBrowser.Controller;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Model.Threading;
namespace MediaBrowser.Server.Implementations.IO
{
@@ -136,12 +137,12 @@ namespace MediaBrowser.Server.Implementations.IO
private IServerConfigurationManager ConfigurationManager { get; set; }
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationHost _appHost;
private readonly ITimerFactory _timerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="LibraryMonitor" /> class.
/// </summary>
public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, IServerApplicationHost appHost)
public LibraryMonitor(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem, ITimerFactory timerFactory)
{
if (taskManager == null)
{
@@ -153,7 +154,7 @@ namespace MediaBrowser.Server.Implementations.IO
Logger = logManager.GetLogger(GetType().Name);
ConfigurationManager = configurationManager;
_fileSystem = fileSystem;
_appHost = appHost;
_timerFactory = timerFactory;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
@@ -528,7 +529,7 @@ namespace MediaBrowser.Server.Implementations.IO
}
}
var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger);
var newRefresher = new FileRefresher(path, _fileSystem, ConfigurationManager, LibraryManager, TaskManager, Logger, _timerFactory);
newRefresher.Completed += NewRefresher_Completed;
_activeRefreshers.Add(newRefresher);
}

View File

@@ -1,148 +0,0 @@
using MediaBrowser.Model.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Server.Implementations.Library
{
/// <summary>
/// Provides the core resolver ignore rules
/// </summary>
public class CoreResolutionIgnoreRule : IResolverIgnoreRule
{
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Any folder named in this list will be ignored - can be added to at runtime for extensibility
/// </summary>
public static readonly List<string> IgnoreFolders = new List<string>
{
"metadata",
"ps3_update",
"ps3_vprm",
"extrafanart",
"extrathumbs",
".actors",
".wd_tv",
// Synology
"@eaDir",
"eaDir",
"#recycle"
};
public CoreResolutionIgnoreRule(IFileSystem fileSystem, ILibraryManager libraryManager)
{
_fileSystem = fileSystem;
_libraryManager = libraryManager;
}
/// <summary>
/// Shoulds the ignore.
/// </summary>
/// <param name="fileInfo">The file information.</param>
/// <param name="parent">The parent.</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
{
var filename = fileInfo.Name;
var isHidden = fileInfo.IsHidden;
var path = fileInfo.FullName;
// Handle mac .DS_Store
// https://github.com/MediaBrowser/MediaBrowser/issues/427
if (filename.IndexOf("._", StringComparison.OrdinalIgnoreCase) == 0)
{
return true;
}
// Ignore hidden files and folders
if (isHidden)
{
if (parent == null)
{
var parentFolderName = Path.GetFileName(Path.GetDirectoryName(path));
if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
// Sometimes these are marked hidden
if (_fileSystem.IsRootPath(path))
{
return false;
}
return true;
}
if (fileInfo.IsDirectory)
{
// Ignore any folders in our list
if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return true;
}
if (parent != null)
{
// Ignore trailer folders but allow it at the collection level
if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) &&
!(parent is AggregateFolder) && !(parent is UserRootFolder))
{
return true;
}
if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
else
{
if (parent != null)
{
// Don't resolve these into audio files
if (string.Equals(_fileSystem.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
{
return true;
}
}
// Ignore samples
var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase)
.Replace("-", " ", StringComparison.OrdinalIgnoreCase)
.Replace("_", " ", StringComparison.OrdinalIgnoreCase)
.Replace("!", " ", StringComparison.OrdinalIgnoreCase);
if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
}
return false;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,102 +0,0 @@
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
namespace MediaBrowser.Server.Implementations.Library
{
public class LocalTrailerPostScanTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
private readonly IChannelManager _channelManager;
public LocalTrailerPostScanTask(ILibraryManager libraryManager, IChannelManager channelManager)
{
_libraryManager = libraryManager;
_channelManager = channelManager;
}
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var items = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(BoxSet).Name, typeof(Game).Name, typeof(Movie).Name, typeof(Series).Name },
Recursive = true
}).OfType<IHasTrailers>().ToList();
var trailerTypes = Enum.GetNames(typeof(TrailerType))
.Select(i => (TrailerType)Enum.Parse(typeof(TrailerType), i, true))
.Except(new[] { TrailerType.LocalTrailer })
.ToArray();
var trailers = _libraryManager.GetItemList(new InternalItemsQuery
{
IncludeItemTypes = new[] { typeof(Trailer).Name },
TrailerTypes = trailerTypes,
Recursive = true
}).ToArray();
var numComplete = 0;
foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
await AssignTrailers(item, trailers).ConfigureAwait(false);
numComplete++;
double percent = numComplete;
percent /= items.Count;
progress.Report(percent * 100);
}
progress.Report(100);
}
private async Task AssignTrailers(IHasTrailers item, BaseItem[] channelTrailers)
{
if (item is Game)
{
return;
}
var imdbId = item.GetProviderId(MetadataProviders.Imdb);
var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
var trailers = channelTrailers.Where(i =>
{
if (!string.IsNullOrWhiteSpace(imdbId) &&
string.Equals(imdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (!string.IsNullOrWhiteSpace(tmdbId) &&
string.Equals(tmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
});
var trailerIds = trailers.Select(i => i.Id)
.ToList();
if (!trailerIds.SequenceEqual(item.RemoteTrailerIds))
{
item.RemoteTrailerIds = trailerIds;
var baseItem = (BaseItem)item;
await baseItem.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None)
.ConfigureAwait(false);
}
}
}
}

View File

@@ -1,648 +0,0 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Server.Implementations.Library
{
public class MediaSourceManager : IMediaSourceManager, IDisposable
{
private readonly IItemRepository _itemRepo;
private readonly IUserManager _userManager;
private readonly ILibraryManager _libraryManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IFileSystem _fileSystem;
private IMediaSourceProvider[] _providers;
private readonly ILogger _logger;
private readonly IUserDataManager _userDataManager;
public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager)
{
_itemRepo = itemRepo;
_userManager = userManager;
_libraryManager = libraryManager;
_logger = logger;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
_userDataManager = userDataManager;
}
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
{
_providers = providers.ToArray();
}
public IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query)
{
var list = _itemRepo.GetMediaStreams(query)
.ToList();
foreach (var stream in list)
{
stream.SupportsExternalStream = StreamSupportsExternalStream(stream);
}
return list;
}
private bool StreamSupportsExternalStream(MediaStream stream)
{
if (stream.IsExternal)
{
return true;
}
if (stream.IsTextSubtitleStream)
{
return true;
}
return false;
}
public IEnumerable<MediaStream> GetMediaStreams(string mediaSourceId)
{
var list = GetMediaStreams(new MediaStreamQuery
{
ItemId = new Guid(mediaSourceId)
});
return GetMediaStreamsForItem(list);
}
public IEnumerable<MediaStream> GetMediaStreams(Guid itemId)
{
var list = GetMediaStreams(new MediaStreamQuery
{
ItemId = itemId
});
return GetMediaStreamsForItem(list);
}
private IEnumerable<MediaStream> GetMediaStreamsForItem(IEnumerable<MediaStream> streams)
{
var list = streams.ToList();
var subtitleStreams = list
.Where(i => i.Type == MediaStreamType.Subtitle)
.ToList();
if (subtitleStreams.Count > 0)
{
foreach (var subStream in subtitleStreams)
{
subStream.SupportsExternalStream = StreamSupportsExternalStream(subStream);
}
}
return list;
}
public async Task<IEnumerable<MediaSourceInfo>> GetPlayackMediaSources(string id, string userId, bool enablePathSubstitution, string[] supportedLiveMediaTypes, CancellationToken cancellationToken)
{
var item = _libraryManager.GetItemById(id);
var hasMediaSources = (IHasMediaSources)item;
User user = null;
if (!string.IsNullOrWhiteSpace(userId))
{
user = _userManager.GetUserById(userId);
}
var mediaSources = GetStaticMediaSources(hasMediaSources, enablePathSubstitution, user);
var dynamicMediaSources = await GetDynamicMediaSources(hasMediaSources, cancellationToken).ConfigureAwait(false);
var list = new List<MediaSourceInfo>();
list.AddRange(mediaSources);
foreach (var source in dynamicMediaSources)
{
if (user != null)
{
SetUserProperties(hasMediaSources, source, user);
}
if (source.Protocol == MediaProtocol.File)
{
// TODO: Path substitution
if (!_fileSystem.FileExists(source.Path))
{
source.SupportsDirectStream = false;
}
}
else if (source.Protocol == MediaProtocol.Http)
{
// TODO: Allow this when the source is plain http, e.g. not HLS or Mpeg Dash
source.SupportsDirectStream = false;
}
else
{
source.SupportsDirectStream = false;
}
list.Add(source);
}
foreach (var source in list)
{
if (user != null)
{
if (string.Equals(item.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
{
if (!user.Policy.EnableAudioPlaybackTranscoding)
{
source.SupportsTranscoding = false;
}
}
}
}
return SortMediaSources(list).Where(i => i.Type != MediaSourceType.Placeholder);
}
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
{
var tasks = _providers.Select(i => GetDynamicMediaSources(item, i, cancellationToken));
var results = await Task.WhenAll(tasks).ConfigureAwait(false);
return results.SelectMany(i => i.ToList());
}
private async Task<IEnumerable<MediaSourceInfo>> GetDynamicMediaSources(IHasMediaSources item, IMediaSourceProvider provider, CancellationToken cancellationToken)
{
try
{
var sources = await provider.GetMediaSources(item, cancellationToken).ConfigureAwait(false);
var list = sources.ToList();
foreach (var mediaSource in list)
{
SetKeyProperties(provider, mediaSource);
}
return list;
}
catch (Exception ex)
{
_logger.ErrorException("Error getting media sources", ex);
return new List<MediaSourceInfo>();
}
}
private void SetKeyProperties(IMediaSourceProvider provider, MediaSourceInfo mediaSource)
{
var prefix = provider.GetType().FullName.GetMD5().ToString("N") + LiveStreamIdDelimeter;
if (!string.IsNullOrWhiteSpace(mediaSource.OpenToken) && !mediaSource.OpenToken.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
mediaSource.OpenToken = prefix + mediaSource.OpenToken;
}
if (!string.IsNullOrWhiteSpace(mediaSource.LiveStreamId) && !mediaSource.LiveStreamId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
mediaSource.LiveStreamId = prefix + mediaSource.LiveStreamId;
}
}
public async Task<MediaSourceInfo> GetMediaSource(IHasMediaSources item, string mediaSourceId, string liveStreamId, bool enablePathSubstitution, CancellationToken cancellationToken)
{
if (!string.IsNullOrWhiteSpace(liveStreamId))
{
return await GetLiveStream(liveStreamId, cancellationToken).ConfigureAwait(false);
}
//await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
//try
//{
// var stream = _openStreams.Values.FirstOrDefault(i => string.Equals(i.MediaSource.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
// if (stream != null)
// {
// return stream.MediaSource;
// }
//}
//finally
//{
// _liveStreamSemaphore.Release();
//}
var sources = await GetPlayackMediaSources(item.Id.ToString("N"), null, enablePathSubstitution, new[] { MediaType.Audio, MediaType.Video },
CancellationToken.None).ConfigureAwait(false);
return sources.FirstOrDefault(i => string.Equals(i.Id, mediaSourceId, StringComparison.OrdinalIgnoreCase));
}
public IEnumerable<MediaSourceInfo> GetStaticMediaSources(IHasMediaSources item, bool enablePathSubstitution, User user = null)
{
if (item == null)
{
throw new ArgumentNullException("item");
}
if (!(item is Video))
{
return item.GetMediaSources(enablePathSubstitution);
}
var sources = item.GetMediaSources(enablePathSubstitution).ToList();
if (user != null)
{
foreach (var source in sources)
{
SetUserProperties(item, source, user);
}
}
return sources;
}
private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user)
{
var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user, item);
var allowRememberingSelection = item == null || item.EnableRememberingTrackSelections;
SetDefaultAudioStreamIndex(source, userData, user, allowRememberingSelection);
SetDefaultSubtitleStreamIndex(source, userData, user, allowRememberingSelection);
}
private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections && user.Configuration.SubtitleMode != SubtitlePlaybackMode.None && allowRememberingSelection)
{
var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid
if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))
{
source.DefaultSubtitleStreamIndex = index;
return;
}
}
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
? new List<string>() : new List<string> { user.Configuration.SubtitleLanguagePreference };
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
? null
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
source.DefaultSubtitleStreamIndex = MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams,
preferredSubs,
user.Configuration.SubtitleMode,
audioLangage);
MediaStreamSelector.SetSubtitleStreamScores(source.MediaStreams, preferredSubs,
user.Configuration.SubtitleMode, audioLangage);
}
private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user, bool allowRememberingSelection)
{
if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections && allowRememberingSelection)
{
var index = userData.AudioStreamIndex.Value;
// Make sure the saved index is still valid
if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))
{
source.DefaultAudioStreamIndex = index;
return;
}
}
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
? new string[] { }
: new[] { user.Configuration.AudioLanguagePreference };
source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
}
private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
{
if (i.VideoType.HasValue && i.VideoType.Value == VideoType.VideoFile)
{
return 0;
}
return 1;
}).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0)
.ThenByDescending(i =>
{
var stream = i.VideoStream;
return stream == null || stream.Width == null ? 0 : stream.Width.Value;
})
.ToList();
}
private readonly Dictionary<string, LiveStreamInfo> _openStreams = new Dictionary<string, LiveStreamInfo>(StringComparer.OrdinalIgnoreCase);
private readonly SemaphoreSlim _liveStreamSemaphore = new SemaphoreSlim(1, 1);
public async Task<LiveStreamResponse> OpenLiveStream(LiveStreamRequest request, bool enableAutoClose, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
var tuple = GetProvider(request.OpenToken);
var provider = tuple.Item1;
var mediaSourceTuple = await provider.OpenMediaSource(tuple.Item2, cancellationToken).ConfigureAwait(false);
var mediaSource = mediaSourceTuple.Item1;
if (string.IsNullOrWhiteSpace(mediaSource.LiveStreamId))
{
throw new InvalidOperationException(string.Format("{0} returned null LiveStreamId", provider.GetType().Name));
}
SetKeyProperties(provider, mediaSource);
var info = new LiveStreamInfo
{
Date = DateTime.UtcNow,
EnableCloseTimer = enableAutoClose,
Id = mediaSource.LiveStreamId,
MediaSource = mediaSource,
DirectStreamProvider = mediaSourceTuple.Item2
};
_openStreams[mediaSource.LiveStreamId] = info;
if (enableAutoClose)
{
StartCloseTimer();
}
var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.Debug("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
if (!string.IsNullOrWhiteSpace(request.UserId))
{
var user = _userManager.GetUserById(request.UserId);
var item = string.IsNullOrWhiteSpace(request.ItemId)
? null
: _libraryManager.GetItemById(request.ItemId);
SetUserProperties(item, clone, user);
}
return new LiveStreamResponse
{
MediaSource = clone
};
}
finally
{
_liveStreamSemaphore.Release();
}
}
public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> GetLiveStreamWithDirectStreamProvider(string id, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
_logger.Debug("Getting already opened live stream {0}", id);
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
LiveStreamInfo info;
if (_openStreams.TryGetValue(id, out info))
{
return new Tuple<MediaSourceInfo, IDirectStreamProvider>(info.MediaSource, info.DirectStreamProvider);
}
else
{
throw new ResourceNotFoundException();
}
}
finally
{
_liveStreamSemaphore.Release();
}
}
public async Task<MediaSourceInfo> GetLiveStream(string id, CancellationToken cancellationToken)
{
var result = await GetLiveStreamWithDirectStreamProvider(id, cancellationToken).ConfigureAwait(false);
return result.Item1;
}
public async Task PingLiveStream(string id, CancellationToken cancellationToken)
{
await _liveStreamSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
LiveStreamInfo info;
if (_openStreams.TryGetValue(id, out info))
{
info.Date = DateTime.UtcNow;
}
else
{
_logger.Error("Failed to ping live stream {0}", id);
}
}
finally
{
_liveStreamSemaphore.Release();
}
}
private async Task CloseLiveStreamWithProvider(IMediaSourceProvider provider, string streamId)
{
_logger.Info("Closing live stream {0} with provider {1}", streamId, provider.GetType().Name);
try
{
await provider.CloseMediaSource(streamId).ConfigureAwait(false);
}
catch (NotImplementedException)
{
}
catch (Exception ex)
{
_logger.ErrorException("Error closing live stream {0}", ex, streamId);
}
}
public async Task CloseLiveStream(string id)
{
if (string.IsNullOrWhiteSpace(id))
{
throw new ArgumentNullException("id");
}
await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
try
{
LiveStreamInfo current;
if (_openStreams.TryGetValue(id, out current))
{
_openStreams.Remove(id);
current.Closed = true;
if (current.MediaSource.RequiresClosing)
{
var tuple = GetProvider(id);
await CloseLiveStreamWithProvider(tuple.Item1, tuple.Item2).ConfigureAwait(false);
}
if (_openStreams.Count == 0)
{
StopCloseTimer();
}
}
}
finally
{
_liveStreamSemaphore.Release();
}
}
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
private const char LiveStreamIdDelimeter = '_';
private Tuple<IMediaSourceProvider, string> GetProvider(string key)
{
if (string.IsNullOrWhiteSpace(key))
{
throw new ArgumentException("key");
}
var keys = key.Split(new[] { LiveStreamIdDelimeter }, 2);
var provider = _providers.FirstOrDefault(i => string.Equals(i.GetType().FullName.GetMD5().ToString("N"), keys[0], StringComparison.OrdinalIgnoreCase));
var splitIndex = key.IndexOf(LiveStreamIdDelimeter);
var keyId = key.Substring(splitIndex + 1);
return new Tuple<IMediaSourceProvider, string>(provider, keyId);
}
private Timer _closeTimer;
private readonly TimeSpan _openStreamMaxAge = TimeSpan.FromSeconds(180);
private void StartCloseTimer()
{
StopCloseTimer();
_closeTimer = new Timer(CloseTimerCallback, null, _openStreamMaxAge, _openStreamMaxAge);
}
private void StopCloseTimer()
{
var timer = _closeTimer;
if (timer != null)
{
_closeTimer = null;
timer.Dispose();
}
}
private async void CloseTimerCallback(object state)
{
List<LiveStreamInfo> infos;
await _liveStreamSemaphore.WaitAsync().ConfigureAwait(false);
try
{
infos = _openStreams
.Values
.Where(i => i.EnableCloseTimer && DateTime.UtcNow - i.Date > _openStreamMaxAge)
.ToList();
}
finally
{
_liveStreamSemaphore.Release();
}
foreach (var info in infos)
{
if (!info.Closed)
{
try
{
await CloseLiveStream(info.Id).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error closing media source", ex);
}
}
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
StopCloseTimer();
Dispose(true);
}
private readonly object _disposeLock = new object();
/// <summary>
/// Releases unmanaged and - optionally - managed resources.
/// </summary>
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
protected virtual void Dispose(bool dispose)
{
if (dispose)
{
lock (_disposeLock)
{
foreach (var key in _openStreams.Keys.ToList())
{
var task = CloseLiveStream(key);
Task.WaitAll(task);
}
}
}
}
private class LiveStreamInfo
{
public DateTime Date;
public bool EnableCloseTimer;
public string Id;
public bool Closed;
public MediaSourceInfo MediaSource;
public IDirectStreamProvider DirectStreamProvider;
}
}
}

View File

@@ -1,157 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Server.Implementations.Library
{
public class MusicManager : IMusicManager
{
private readonly ILibraryManager _libraryManager;
public MusicManager(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
public IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user)
{
var list = new List<Audio>
{
item
};
return list.Concat(GetInstantMixFromGenres(item.Genres, user));
}
public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist artist, User user)
{
var genres = user.RootFolder
.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Audio).Name }
})
.Cast<Audio>()
.Where(i => i.HasAnyArtist(artist.Name))
.SelectMany(i => i.Genres)
.Concat(artist.Genres)
.Distinct(StringComparer.OrdinalIgnoreCase);
return GetInstantMixFromGenres(genres, user);
}
public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
{
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Audio).Name }
})
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
.DistinctNames();
return GetInstantMixFromGenres(genres, user);
}
public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
{
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
IncludeItemTypes = new[] {typeof(Audio).Name}
})
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
.DistinctNames();
return GetInstantMixFromGenres(genres, user);
}
public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
{
var genres = item
.GetRecursiveChildren(user, new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Audio).Name }
})
.Cast<Audio>()
.SelectMany(i => i.Genres)
.Concat(item.Genres)
.DistinctNames();
return GetInstantMixFromGenres(genres, user);
}
public IEnumerable<Audio> GetInstantMixFromGenres(IEnumerable<string> genres, User user)
{
var genreList = genres.ToList();
var inputItems = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { typeof(Audio).Name },
Genres = genreList.ToArray()
});
var genresDictionary = genreList.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
return inputItems
.Cast<Audio>()
.Select(i => new Tuple<Audio, int>(i, i.Genres.Count(genresDictionary.ContainsKey)))
.Where(i => i.Item2 > 0)
.OrderByDescending(i => i.Item2)
.ThenBy(i => Guid.NewGuid())
.Select(i => i.Item1)
.Take(100)
.OrderBy(i => Guid.NewGuid());
}
public IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user)
{
var genre = item as MusicGenre;
if (genre != null)
{
return GetInstantMixFromGenres(new[] { item.Name }, user);
}
var playlist = item as Playlist;
if (playlist != null)
{
return GetInstantMixFromPlaylist(playlist, user);
}
var album = item as MusicAlbum;
if (album != null)
{
return GetInstantMixFromAlbum(album, user);
}
var artist = item as MusicArtist;
if (artist != null)
{
return GetInstantMixFromArtist(artist, user);
}
var song = item as Audio;
if (song != null)
{
return GetInstantMixFromSong(song, user);
}
var folder = item as Folder;
if (folder != null)
{
return GetInstantMixFromFolder(folder, user);
}
return new Audio[] { };
}
}
}

View File

@@ -1,45 +0,0 @@
using System;
using System.Text.RegularExpressions;
namespace MediaBrowser.Server.Implementations.Library
{
public static class PathExtensions
{
/// <summary>
/// Gets the attribute value.
/// </summary>
/// <param name="str">The STR.</param>
/// <param name="attrib">The attrib.</param>
/// <returns>System.String.</returns>
/// <exception cref="System.ArgumentNullException">attrib</exception>
public static string GetAttributeValue(this string str, string attrib)
{
if (string.IsNullOrEmpty(str))
{
throw new ArgumentNullException("str");
}
if (string.IsNullOrEmpty(attrib))
{
throw new ArgumentNullException("attrib");
}
string srch = "[" + attrib + "=";
int start = str.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
if (start > -1)
{
start += srch.Length;
int end = str.IndexOf(']', start);
return str.Substring(start, end - start);
}
// for imdbid we also accept pattern matching
if (string.Equals(attrib, "imdbid", StringComparison.OrdinalIgnoreCase))
{
var m = Regex.Match(str, "tt\\d{7}", RegexOptions.IgnoreCase);
return m.Success ? m.Value : null;
}
return null;
}
}
}

View File

@@ -1,183 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Server.Implementations.Library
{
/// <summary>
/// Class ResolverHelper
/// </summary>
public static class ResolverHelper
{
/// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="parent">The parent.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
/// <param name="directoryService">The directory service.</param>
/// <exception cref="System.ArgumentException">Item must have a path</exception>
public static void SetInitialItemValues(BaseItem item, Folder parent, IFileSystem fileSystem, ILibraryManager libraryManager, IDirectoryService directoryService)
{
// This version of the below method has no ItemResolveArgs, so we have to require the path already being set
if (string.IsNullOrWhiteSpace(item.Path))
{
throw new ArgumentException("Item must have a Path");
}
// If the resolver didn't specify this
if (parent != null)
{
item.SetParent(parent);
}
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values
var fileInfo = directoryService.GetFile(item.Path);
SetDateCreated(item, fileSystem, fileInfo);
EnsureName(item, fileInfo);
}
/// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
/// <param name="fileSystem">The file system.</param>
/// <param name="libraryManager">The library manager.</param>
public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem, ILibraryManager libraryManager)
{
// If the resolver didn't specify this
if (string.IsNullOrEmpty(item.Path))
{
item.Path = args.Path;
}
// If the resolver didn't specify this
if (args.Parent != null)
{
item.SetParent(args.Parent);
}
item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
// Make sure the item has a name
EnsureName(item, args.FileInfo);
item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
item.GetParents().Any(i => i.IsLocked);
// Make sure DateCreated and DateModified have values
EnsureDates(fileSystem, item, args);
}
/// <summary>
/// Ensures the name.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="fileInfo">The file information.</param>
private static void EnsureName(BaseItem item, FileSystemMetadata fileInfo)
{
// If the subclass didn't supply a name, add it here
if (string.IsNullOrEmpty(item.Name) && !string.IsNullOrEmpty(item.Path))
{
item.Name = GetDisplayName(fileInfo.Name, fileInfo.IsDirectory);
}
}
/// <summary>
/// Gets the display name.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
/// <returns>System.String.</returns>
private static string GetDisplayName(string path, bool isDirectory)
{
return isDirectory ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path);
}
/// <summary>
/// The MB name regex
/// </summary>
private static readonly Regex MbNameRegex = new Regex(@"(\[.*?\])", RegexOptions.Compiled);
internal static string StripBrackets(string inputString)
{
var output = MbNameRegex.Replace(inputString, string.Empty).Trim();
return Regex.Replace(output, @"\s+", " ");
}
/// <summary>
/// Ensures DateCreated and DateModified have values
/// </summary>
/// <param name="fileSystem">The file system.</param>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
private static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args)
{
if (fileSystem == null)
{
throw new ArgumentNullException("fileSystem");
}
if (item == null)
{
throw new ArgumentNullException("item");
}
if (args == null)
{
throw new ArgumentNullException("args");
}
// See if a different path came out of the resolver than what went in
if (!string.Equals(args.Path, item.Path, StringComparison.OrdinalIgnoreCase))
{
var childData = args.IsDirectory ? args.GetFileSystemEntryByPath(item.Path) : null;
if (childData != null)
{
SetDateCreated(item, fileSystem, childData);
}
else
{
var fileData = fileSystem.GetFileSystemInfo(item.Path);
if (fileData.Exists)
{
SetDateCreated(item, fileSystem, fileData);
}
}
}
else
{
SetDateCreated(item, fileSystem, args.FileInfo);
}
}
private static void SetDateCreated(BaseItem item, IFileSystem fileSystem, FileSystemMetadata info)
{
var config = BaseItem.ConfigurationManager.GetMetadataConfiguration();
if (config.UseFileCreationTimeForDateAdded)
{
item.DateCreated = fileSystem.GetCreationTimeUtc(info);
}
else
{
item.DateCreated = DateTime.UtcNow;
}
}
}
}

View File

@@ -1,68 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using System;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
/// Class AudioResolver
/// </summary>
public class AudioResolver : ItemResolver<Controller.Entities.Audio.Audio>
{
private readonly ILibraryManager _libraryManager;
public AudioResolver(ILibraryManager libraryManager)
{
_libraryManager = libraryManager;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get { return ResolverPriority.Last; }
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Entities.Audio.Audio.</returns>
protected override Controller.Entities.Audio.Audio Resolve(ItemResolveArgs args)
{
// Return audio if the path is a file and has a matching extension
if (!args.IsDirectory)
{
var libraryOptions = args.GetLibraryOptions();
if (_libraryManager.IsAudioFile(args.Path, libraryOptions))
{
var collectionType = args.GetCollectionType();
var isMixed = string.IsNullOrWhiteSpace(collectionType);
// For conflicting extensions, give priority to videos
if (isMixed && _libraryManager.IsVideoFile(args.Path, libraryOptions))
{
return null;
}
var isStandalone = args.Parent == null;
if (isStandalone ||
string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) ||
isMixed)
{
return new Controller.Entities.Audio.Audio();
}
}
}
return null;
}
}
}

View File

@@ -1,173 +0,0 @@
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Naming.Audio;
using MediaBrowser.Server.Implementations.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
/// Class MusicAlbumResolver
/// </summary>
public class MusicAlbumResolver : ItemResolver<MusicAlbum>
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
public MusicAlbumResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager)
{
_logger = logger;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get
{
// Behind special folder resolver
return ResolverPriority.Second;
}
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>MusicAlbum.</returns>
protected override MusicAlbum Resolve(ItemResolveArgs args)
{
if (!args.IsDirectory) return null;
// Avoid mis-identifying top folders
if (args.HasParent<MusicAlbum>()) return null;
if (args.Parent.IsRoot) return null;
var collectionType = args.GetCollectionType();
var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
// If there's a collection type and it's not music, don't allow it.
if (!isMusicMediaFolder)
{
return null;
}
return IsMusicAlbum(args) ? new MusicAlbum() : null;
}
/// <summary>
/// Determine if the supplied file data points to a music album
/// </summary>
public bool IsMusicAlbum(string path, IDirectoryService directoryService, LibraryOptions libraryOptions)
{
return ContainsMusic(directoryService.GetFileSystemEntries(path), true, directoryService, _logger, _fileSystem, libraryOptions, _libraryManager);
}
/// <summary>
/// Determine if the supplied resolve args should be considered a music album
/// </summary>
/// <param name="args">The args.</param>
/// <returns><c>true</c> if [is music album] [the specified args]; otherwise, <c>false</c>.</returns>
private bool IsMusicAlbum(ItemResolveArgs args)
{
// Args points to an album if parent is an Artist folder or it directly contains music
if (args.IsDirectory)
{
//if (args.Parent is MusicArtist) return true; //saves us from testing children twice
if (ContainsMusic(args.FileSystemChildren, true, args.DirectoryService, _logger, _fileSystem, args.GetLibraryOptions(), _libraryManager)) return true;
}
return false;
}
/// <summary>
/// Determine if the supplied list contains what we should consider music
/// </summary>
private bool ContainsMusic(IEnumerable<FileSystemMetadata> list,
bool allowSubfolders,
IDirectoryService directoryService,
ILogger logger,
IFileSystem fileSystem,
LibraryOptions libraryOptions,
ILibraryManager libraryManager)
{
var discSubfolderCount = 0;
var notMultiDisc = false;
foreach (var fileSystemInfo in list)
{
if (fileSystemInfo.IsDirectory)
{
if (allowSubfolders)
{
var path = fileSystemInfo.FullName;
var isMultiDisc = IsMultiDiscFolder(path, libraryOptions);
if (isMultiDisc)
{
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager);
if (hasMusic)
{
logger.Debug("Found multi-disc folder: " + path);
discSubfolderCount++;
}
}
else
{
var hasMusic = ContainsMusic(directoryService.GetFileSystemEntries(path), false, directoryService, logger, fileSystem, libraryOptions, libraryManager);
if (hasMusic)
{
// If there are folders underneath with music that are not multidisc, then this can't be a multi-disc album
notMultiDisc = true;
}
}
}
}
var fullName = fileSystemInfo.FullName;
if (libraryManager.IsAudioFile(fullName, libraryOptions))
{
return true;
}
}
if (notMultiDisc)
{
return false;
}
return discSubfolderCount > 0;
}
private bool IsMultiDiscFolder(string path, LibraryOptions libraryOptions)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions(libraryOptions);
var parser = new AlbumParser(namingOptions, new PatternsLogger());
var result = parser.ParseMultiPart(path);
return result.IsMultiPart;
}
}
}

View File

@@ -1,94 +0,0 @@
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
{
/// <summary>
/// Class MusicArtistResolver
/// </summary>
public class MusicArtistResolver : ItemResolver<MusicArtist>
{
private readonly ILogger _logger;
private readonly IFileSystem _fileSystem;
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
public MusicArtistResolver(ILogger logger, IFileSystem fileSystem, ILibraryManager libraryManager, IServerConfigurationManager config)
{
_logger = logger;
_fileSystem = fileSystem;
_libraryManager = libraryManager;
_config = config;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get
{
// Behind special folder resolver
return ResolverPriority.Second;
}
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>MusicArtist.</returns>
protected override MusicArtist Resolve(ItemResolveArgs args)
{
if (!args.IsDirectory) return null;
// Don't allow nested artists
if (args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>())
{
return null;
}
var collectionType = args.GetCollectionType();
var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
// If there's a collection type and it's not music, it can't be a series
if (!isMusicMediaFolder)
{
return null;
}
if (args.ContainsFileSystemEntryByName("artist.nfo"))
{
return new MusicArtist();
}
if (_config.Configuration.EnableSimpleArtistDetection)
{
return null;
}
// Avoid mis-identifying top folders
if (args.Parent.IsRoot) return null;
var directoryService = args.DirectoryService;
var albumResolver = new MusicAlbumResolver(_logger, _fileSystem, _libraryManager);
// If we contain an album assume we are an artist folder
return args.FileSystemChildren.Where(i => i.IsDirectory).Any(i => albumResolver.IsMusicAlbum(i.FullName, directoryService, args.GetLibraryOptions())) ? new MusicArtist() : null;
}
}
}

View File

@@ -1,297 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using MediaBrowser.Naming.Video;
using MediaBrowser.Server.Implementations.Logging;
using System;
using System.IO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
/// <summary>
/// Resolves a Path into a Video or Video subclass
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class BaseVideoResolver<T> : Controller.Resolvers.ItemResolver<T>
where T : Video, new()
{
protected readonly ILibraryManager LibraryManager;
protected BaseVideoResolver(ILibraryManager libraryManager)
{
LibraryManager = libraryManager;
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>`0.</returns>
protected override T Resolve(ItemResolveArgs args)
{
return ResolveVideo<T>(args, false);
}
/// <summary>
/// Resolves the video.
/// </summary>
/// <typeparam name="TVideoType">The type of the T video type.</typeparam>
/// <param name="args">The args.</param>
/// <param name="parseName">if set to <c>true</c> [parse name].</param>
/// <returns>``0.</returns>
protected TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName)
where TVideoType : Video, new()
{
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
// If the path is a file check for a matching extensions
var parser = new Naming.Video.VideoResolver(namingOptions, new PatternsLogger());
if (args.IsDirectory)
{
TVideoType video = null;
VideoFileInfo videoInfo = null;
// Loop through each child file/folder and see if we find a video
foreach (var child in args.FileSystemChildren)
{
var filename = child.Name;
if (child.IsDirectory)
{
if (IsDvdDirectory(filename))
{
videoInfo = parser.ResolveDirectory(args.Path);
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.Dvd,
ProductionYear = videoInfo.Year
};
break;
}
if (IsBluRayDirectory(filename))
{
videoInfo = parser.ResolveDirectory(args.Path);
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.BluRay,
ProductionYear = videoInfo.Year
};
break;
}
}
else if (IsDvdFile(filename))
{
videoInfo = parser.ResolveDirectory(args.Path);
if (videoInfo == null)
{
return null;
}
video = new TVideoType
{
Path = args.Path,
VideoType = VideoType.Dvd,
ProductionYear = videoInfo.Year
};
break;
}
}
if (video != null)
{
video.Name = parseName ?
videoInfo.Name :
Path.GetFileName(args.Path);
Set3DFormat(video, videoInfo);
}
return video;
}
else
{
var videoInfo = parser.Resolve(args.Path, false, false);
if (videoInfo == null)
{
return null;
}
if (LibraryManager.IsVideoFile(args.Path, args.GetLibraryOptions()) || videoInfo.IsStub)
{
var path = args.Path;
var video = new TVideoType
{
Path = path,
IsInMixedFolder = true,
ProductionYear = videoInfo.Year
};
SetVideoType(video, videoInfo);
video.Name = parseName ?
videoInfo.Name :
Path.GetFileNameWithoutExtension(args.Path);
Set3DFormat(video, videoInfo);
return video;
}
}
return null;
}
protected void SetVideoType(Video video, VideoFileInfo videoInfo)
{
var extension = Path.GetExtension(video.Path);
video.VideoType = string.Equals(extension, ".iso", StringComparison.OrdinalIgnoreCase) ||
string.Equals(extension, ".img", StringComparison.OrdinalIgnoreCase) ?
VideoType.Iso :
VideoType.VideoFile;
video.IsShortcut = string.Equals(extension, ".strm", StringComparison.OrdinalIgnoreCase);
video.IsPlaceHolder = videoInfo.IsStub;
if (videoInfo.IsStub)
{
if (string.Equals(videoInfo.StubType, "dvd", StringComparison.OrdinalIgnoreCase))
{
video.VideoType = VideoType.Dvd;
}
else if (string.Equals(videoInfo.StubType, "hddvd", StringComparison.OrdinalIgnoreCase))
{
video.VideoType = VideoType.HdDvd;
video.IsHD = true;
}
else if (string.Equals(videoInfo.StubType, "bluray", StringComparison.OrdinalIgnoreCase))
{
video.VideoType = VideoType.BluRay;
video.IsHD = true;
}
else if (string.Equals(videoInfo.StubType, "hdtv", StringComparison.OrdinalIgnoreCase))
{
video.IsHD = true;
}
}
SetIsoType(video);
}
protected void SetIsoType(Video video)
{
if (video.VideoType == VideoType.Iso)
{
if (video.Path.IndexOf("dvd", StringComparison.OrdinalIgnoreCase) != -1)
{
video.IsoType = IsoType.Dvd;
}
else if (video.Path.IndexOf("bluray", StringComparison.OrdinalIgnoreCase) != -1)
{
video.IsoType = IsoType.BluRay;
}
}
}
protected void Set3DFormat(Video video, bool is3D, string format3D)
{
if (is3D)
{
if (string.Equals(format3D, "fsbs", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.FullSideBySide;
}
else if (string.Equals(format3D, "ftab", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.FullTopAndBottom;
}
else if (string.Equals(format3D, "hsbs", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.HalfSideBySide;
}
else if (string.Equals(format3D, "htab", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
}
else if (string.Equals(format3D, "sbs", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.HalfSideBySide;
}
else if (string.Equals(format3D, "sbs3d", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.HalfSideBySide;
}
else if (string.Equals(format3D, "tab", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.HalfTopAndBottom;
}
else if (string.Equals(format3D, "mvc", StringComparison.OrdinalIgnoreCase))
{
video.Video3DFormat = Video3DFormat.MVC;
}
}
}
protected void Set3DFormat(Video video, VideoFileInfo videoInfo)
{
Set3DFormat(video, videoInfo.Is3D, videoInfo.Format3D);
}
protected void Set3DFormat(Video video)
{
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var resolver = new Format3DParser(namingOptions, new PatternsLogger());
var result = resolver.Parse(video.Path);
Set3DFormat(video, result.Is3D, result.Format3D);
}
/// <summary>
/// Determines whether [is DVD directory] [the specified directory name].
/// </summary>
/// <param name="directoryName">Name of the directory.</param>
/// <returns><c>true</c> if [is DVD directory] [the specified directory name]; otherwise, <c>false</c>.</returns>
protected bool IsDvdDirectory(string directoryName)
{
return string.Equals(directoryName, "video_ts", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether [is DVD file] [the specified name].
/// </summary>
/// <param name="name">The name.</param>
/// <returns><c>true</c> if [is DVD file] [the specified name]; otherwise, <c>false</c>.</returns>
protected bool IsDvdFile(string name)
{
return string.Equals(name, "video_ts.ifo", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether [is blu ray directory] [the specified directory name].
/// </summary>
/// <param name="directoryName">Name of the directory.</param>
/// <returns><c>true</c> if [is blu ray directory] [the specified directory name]; otherwise, <c>false</c>.</returns>
protected bool IsBluRayDirectory(string directoryName)
{
return string.Equals(directoryName, "bdmv", StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,56 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
/// <summary>
/// Class FolderResolver
/// </summary>
public class FolderResolver : FolderResolver<Folder>
{
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get { return ResolverPriority.Last; }
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Folder.</returns>
protected override Folder Resolve(ItemResolveArgs args)
{
if (args.IsDirectory)
{
return new Folder();
}
return null;
}
}
/// <summary>
/// Class FolderResolver
/// </summary>
/// <typeparam name="TItemType">The type of the T item type.</typeparam>
public abstract class FolderResolver<TItemType> : ItemResolver<TItemType>
where TItemType : Folder, new()
{
/// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
protected override void SetInitialItemValues(TItemType item, ItemResolveArgs args)
{
base.SetInitialItemValues(item, args);
item.IsRoot = args.Parent == null;
}
}
}

View File

@@ -1,62 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
/// <summary>
/// Class ItemResolver
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class ItemResolver<T> : IItemResolver
where T : BaseItem, new()
{
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>`0.</returns>
protected virtual T Resolve(ItemResolveArgs args)
{
return null;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public virtual ResolverPriority Priority
{
get
{
return ResolverPriority.First;
}
}
/// <summary>
/// Sets initial values on the newly resolved item
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
protected virtual void SetInitialItemValues(T item, ItemResolveArgs args)
{
}
/// <summary>
/// Resolves the path.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>BaseItem.</returns>
BaseItem IItemResolver.ResolvePath(ItemResolveArgs args)
{
var item = Resolve(args);
if (item != null)
{
SetInitialItemValues(item, args);
}
return item;
}
}
}

View File

@@ -1,77 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System;
using System.IO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
/// <summary>
/// Class BoxSetResolver
/// </summary>
public class BoxSetResolver : FolderResolver<BoxSet>
{
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>BoxSet.</returns>
protected override BoxSet Resolve(ItemResolveArgs args)
{
// It's a boxset if all of the following conditions are met:
// Is a Directory
// Contains [boxset] in the path
if (args.IsDirectory)
{
var filename = Path.GetFileName(args.Path);
if (string.IsNullOrEmpty(filename))
{
return null;
}
if (filename.IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1 ||
args.ContainsFileSystemEntryByName("collection.xml"))
{
return new BoxSet
{
Path = args.Path,
Name = ResolverHelper.StripBrackets(Path.GetFileName(args.Path))
};
}
}
return null;
}
/// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
protected override void SetInitialItemValues(BoxSet item, ItemResolveArgs args)
{
base.SetInitialItemValues(item, args);
SetProviderIdFromPath(item);
}
/// <summary>
/// Sets the provider id from path.
/// </summary>
/// <param name="item">The item.</param>
private void SetProviderIdFromPath(BaseItem item)
{
//we need to only look at the name of this actual item (not parents)
var justName = Path.GetFileName(item.Path);
var id = justName.GetAttributeValue("tmdbid");
if (!string.IsNullOrEmpty(id))
{
item.SetProviderId(MetadataProviders.Tmdb, id);
}
}
}
}

View File

@@ -1,541 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Naming.Video;
using MediaBrowser.Server.Implementations.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
{
/// <summary>
/// Class MovieResolver
/// </summary>
public class MovieResolver : BaseVideoResolver<Video>, IMultiItemResolver
{
public MovieResolver(ILibraryManager libraryManager)
: base(libraryManager)
{
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get
{
// Give plugins a chance to catch iso's first
// Also since we have to loop through child files looking for videos,
// see if we can avoid some of that by letting other resolvers claim folders first
// Also run after series resolver
return ResolverPriority.Third;
}
}
public MultiItemResolverResult ResolveMultiple(Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
{
var result = ResolveMultipleInternal(parent, files, collectionType, directoryService);
if (result != null)
{
foreach (var item in result.Items)
{
SetInitialItemValues((Video)item, null);
}
}
return result;
}
private MultiItemResolverResult ResolveMultipleInternal(Folder parent,
List<FileSystemMetadata> files,
string collectionType,
IDirectoryService directoryService)
{
if (IsInvalid(parent, collectionType))
{
return null;
}
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<MusicVideo>(parent, files, directoryService, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<Video>(parent, files, directoryService, false);
}
if (string.IsNullOrEmpty(collectionType))
{
// Owned items should just use the plain video type
if (parent == null)
{
return ResolveVideos<Video>(parent, files, directoryService, false);
}
if (parent is Series || parent.GetParents().OfType<Series>().Any())
{
return null;
}
return ResolveVideos<Movie>(parent, files, directoryService, false);
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<Movie>(parent, files, directoryService, true);
}
return null;
}
private MultiItemResolverResult ResolveVideos<T>(Folder parent, IEnumerable<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, bool suppportMultiEditions)
where T : Video, new()
{
var files = new List<FileSystemMetadata>();
var videos = new List<BaseItem>();
var leftOver = new List<FileSystemMetadata>();
// Loop through each child file/folder and see if we find a video
foreach (var child in fileSystemEntries)
{
if (child.IsDirectory)
{
leftOver.Add(child);
}
else if (IsIgnored(child.Name))
{
}
else
{
files.Add(child);
}
}
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var resolver = new VideoListResolver(namingOptions, new PatternsLogger());
var resolverResult = resolver.Resolve(files, suppportMultiEditions).ToList();
var result = new MultiItemResolverResult
{
ExtraFiles = leftOver,
Items = videos
};
var isInMixedFolder = resolverResult.Count > 1;
foreach (var video in resolverResult)
{
var firstVideo = video.Files.First();
var videoItem = new T
{
Path = video.Files[0].Path,
IsInMixedFolder = isInMixedFolder,
ProductionYear = video.Year,
Name = video.Name,
AdditionalParts = video.Files.Skip(1).Select(i => i.Path).ToList(),
LocalAlternateVersions = video.AlternateVersions.Select(i => i.Path).ToList()
};
SetVideoType(videoItem, firstVideo);
Set3DFormat(videoItem, firstVideo);
result.Items.Add(videoItem);
}
result.ExtraFiles.AddRange(files.Where(i => !ContainsFile(resolverResult, i)));
return result;
}
private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
{
return result.Any(i => ContainsFile(i, file));
}
private bool ContainsFile(VideoInfo result, FileSystemMetadata file)
{
return result.Files.Any(i => ContainsFile(i, file)) ||
result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
result.Extras.Any(i => ContainsFile(i, file));
}
private bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
{
return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Video.</returns>
protected override Video Resolve(ItemResolveArgs args)
{
var collectionType = args.GetCollectionType();
if (IsInvalid(args.Parent, collectionType))
{
return null;
}
// Find movies with their own folders
if (args.IsDirectory)
{
var files = args.FileSystemChildren
.Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
.ToList();
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<MusicVideo>(args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType, false);
}
if (string.IsNullOrEmpty(collectionType))
{
// Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
//return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
if (args.HasParent<Series>())
{
return null;
}
{
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
}
if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType, true);
}
return null;
}
// Owned items will be caught by the plain video resolver
if (args.Parent == null)
{
return null;
}
Video item = null;
if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<MusicVideo>(args, false);
}
// To find a movie file, the collection type must be movies or boxsets
else if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<Movie>(args, true);
}
else if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
item = ResolveVideo<Video>(args, false);
}
else if (string.IsNullOrEmpty(collectionType))
{
if (args.HasParent<Series>())
{
return null;
}
item = ResolveVideo<Video>(args, false);
}
if (item != null)
{
item.IsInMixedFolder = true;
}
return item;
}
private bool IsIgnored(string filename)
{
// Ignore samples
var sampleFilename = " " + filename.Replace(".", " ", StringComparison.OrdinalIgnoreCase)
.Replace("-", " ", StringComparison.OrdinalIgnoreCase)
.Replace("_", " ", StringComparison.OrdinalIgnoreCase)
.Replace("!", " ", StringComparison.OrdinalIgnoreCase);
if (sampleFilename.IndexOf(" sample ", StringComparison.OrdinalIgnoreCase) != -1)
{
return true;
}
return false;
}
/// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
protected override void SetInitialItemValues(Video item, ItemResolveArgs args)
{
base.SetInitialItemValues(item, args);
SetProviderIdsFromPath(item);
}
/// <summary>
/// Sets the provider id from path.
/// </summary>
/// <param name="item">The item.</param>
private void SetProviderIdsFromPath(Video item)
{
if (item is Movie || item is MusicVideo)
{
//we need to only look at the name of this actual item (not parents)
var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
if (!string.IsNullOrWhiteSpace(justName))
{
// check for tmdb id
var tmdbid = justName.GetAttributeValue("tmdbid");
if (!string.IsNullOrWhiteSpace(tmdbid))
{
item.SetProviderId(MetadataProviders.Tmdb, tmdbid);
}
}
if (!string.IsNullOrWhiteSpace(item.Path))
{
// check for imdb id - we use full media path, as we can assume, that this will match in any use case (wither id in parent dir or in file name)
var imdbid = item.Path.GetAttributeValue("imdbid");
if (!string.IsNullOrWhiteSpace(imdbid))
{
item.SetProviderId(MetadataProviders.Imdb, imdbid);
}
}
}
}
/// <summary>
/// Finds a movie based on a child file system entries
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns>Movie.</returns>
private T FindMovie<T>(string path, Folder parent, List<FileSystemMetadata> fileSystemEntries, IDirectoryService directoryService, string collectionType, bool allowFilesAsFolders)
where T : Video, new()
{
var multiDiscFolders = new List<FileSystemMetadata>();
// Search for a folder rip
foreach (var child in fileSystemEntries)
{
var filename = child.Name;
if (child.IsDirectory)
{
if (IsDvdDirectory(filename))
{
var movie = new T
{
Path = path,
VideoType = VideoType.Dvd
};
Set3DFormat(movie);
return movie;
}
if (IsBluRayDirectory(filename))
{
var movie = new T
{
Path = path,
VideoType = VideoType.BluRay
};
Set3DFormat(movie);
return movie;
}
multiDiscFolders.Add(child);
}
else if (IsDvdFile(filename))
{
var movie = new T
{
Path = path,
VideoType = VideoType.Dvd
};
Set3DFormat(movie);
return movie;
}
}
if (allowFilesAsFolders)
{
// TODO: Allow GetMultiDiscMovie in here
var supportsMultiVersion = !string.Equals(collectionType, CollectionType.HomeVideos) &&
!string.Equals(collectionType, CollectionType.Photos) &&
!string.Equals(collectionType, CollectionType.MusicVideos);
var result = ResolveVideos<T>(parent, fileSystemEntries, directoryService, supportsMultiVersion);
if (result.Items.Count == 1)
{
var movie = (T)result.Items[0];
movie.IsInMixedFolder = false;
movie.Name = Path.GetFileName(movie.ContainingFolderPath);
return movie;
}
if (result.Items.Count == 0 && multiDiscFolders.Count > 0)
{
return GetMultiDiscMovie<T>(multiDiscFolders, directoryService);
}
}
return null;
}
/// <summary>
/// Gets the multi disc movie.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="multiDiscFolders">The folders.</param>
/// <param name="directoryService">The directory service.</param>
/// <returns>``0.</returns>
private T GetMultiDiscMovie<T>(List<FileSystemMetadata> multiDiscFolders, IDirectoryService directoryService)
where T : Video, new()
{
var videoTypes = new List<VideoType>();
var folderPaths = multiDiscFolders.Select(i => i.FullName).Where(i =>
{
var subFileEntries = directoryService.GetFileSystemEntries(i)
.ToList();
var subfolders = subFileEntries
.Where(e => e.IsDirectory)
.Select(d => d.Name)
.ToList();
if (subfolders.Any(IsDvdDirectory))
{
videoTypes.Add(VideoType.Dvd);
return true;
}
if (subfolders.Any(IsBluRayDirectory))
{
videoTypes.Add(VideoType.BluRay);
return true;
}
var subFiles = subFileEntries
.Where(e => !e.IsDirectory)
.Select(d => d.Name);
if (subFiles.Any(IsDvdFile))
{
videoTypes.Add(VideoType.Dvd);
return true;
}
return false;
}).OrderBy(i => i).ToList();
// If different video types were found, don't allow this
if (videoTypes.Distinct().Count() > 1)
{
return null;
}
if (folderPaths.Count == 0)
{
return null;
}
var namingOptions = ((LibraryManager)LibraryManager).GetNamingOptions();
var resolver = new StackResolver(namingOptions, new PatternsLogger());
var result = resolver.ResolveDirectories(folderPaths);
if (result.Stacks.Count != 1)
{
return null;
}
var returnVideo = new T
{
Path = folderPaths[0],
AdditionalParts = folderPaths.Skip(1).ToList(),
VideoType = videoTypes[0],
Name = result.Stacks[0].Name
};
SetIsoType(returnVideo);
return returnVideo;
}
private bool IsInvalid(Folder parent, string collectionType)
{
if (parent != null)
{
if (parent.IsRoot)
{
return true;
}
}
var validCollectionTypes = new[]
{
CollectionType.Movies,
CollectionType.HomeVideos,
CollectionType.MusicVideos,
CollectionType.Movies,
CollectionType.Photos
};
if (string.IsNullOrWhiteSpace(collectionType))
{
return false;
}
return !validCollectionTypes.Contains(collectionType, StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,56 +0,0 @@
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using System;
using System.IO;
using System.Linq;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
public class PhotoAlbumResolver : FolderResolver<PhotoAlbum>
{
private readonly IImageProcessor _imageProcessor;
public PhotoAlbumResolver(IImageProcessor imageProcessor)
{
_imageProcessor = imageProcessor;
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Trailer.</returns>
protected override PhotoAlbum Resolve(ItemResolveArgs args)
{
// Must be an image file within a photo collection
if (args.IsDirectory && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
if (HasPhotos(args))
{
return new PhotoAlbum
{
Path = args.Path
};
}
}
return null;
}
private bool HasPhotos(ItemResolveArgs args)
{
return args.FileSystemChildren.Any(i => (!i.IsDirectory) && PhotoResolver.IsImageFile(i.FullName, _imageProcessor));
}
public override ResolverPriority Priority
{
get
{
// Behind special folder resolver
return ResolverPriority.Second;
}
}
}
}

View File

@@ -1,103 +0,0 @@
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Entities;
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
public class PhotoResolver : ItemResolver<Photo>
{
private readonly IImageProcessor _imageProcessor;
private readonly ILibraryManager _libraryManager;
public PhotoResolver(IImageProcessor imageProcessor, ILibraryManager libraryManager)
{
_imageProcessor = imageProcessor;
_libraryManager = libraryManager;
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Trailer.</returns>
protected override Photo Resolve(ItemResolveArgs args)
{
if (!args.IsDirectory)
{
// Must be an image file within a photo collection
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase) ||
(string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) && args.GetLibraryOptions().EnablePhotos))
{
if (IsImageFile(args.Path, _imageProcessor))
{
var filename = Path.GetFileNameWithoutExtension(args.Path);
// Make sure the image doesn't belong to a video file
if (args.DirectoryService.GetFiles(Path.GetDirectoryName(args.Path)).Any(i => IsOwnedByMedia(args.GetLibraryOptions(), i, filename)))
{
return null;
}
return new Photo
{
Path = args.Path
};
}
}
}
return null;
}
private bool IsOwnedByMedia(LibraryOptions libraryOptions, FileSystemMetadata file, string imageFilename)
{
if (_libraryManager.IsVideoFile(file.FullName, libraryOptions) && imageFilename.StartsWith(Path.GetFileNameWithoutExtension(file.Name), StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
private static readonly string[] IgnoreFiles =
{
"folder",
"thumb",
"landscape",
"fanart",
"backdrop",
"poster",
"cover"
};
internal static bool IsImageFile(string path, IImageProcessor imageProcessor)
{
var filename = Path.GetFileNameWithoutExtension(path) ?? string.Empty;
if (IgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase))
{
return false;
}
if (IgnoreFiles.Any(i => filename.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1))
{
return false;
}
return imageProcessor.SupportedInputFormats.Contains((Path.GetExtension(path) ?? string.Empty).TrimStart('.'), StringComparer.OrdinalIgnoreCase);
}
}
}

View File

@@ -1,42 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Playlists;
using System;
using System.IO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
public class PlaylistResolver : FolderResolver<Playlist>
{
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>BoxSet.</returns>
protected override Playlist Resolve(ItemResolveArgs args)
{
// It's a boxset if all of the following conditions are met:
// Is a Directory
// Contains [playlist] in the path
if (args.IsDirectory)
{
var filename = Path.GetFileName(args.Path);
if (string.IsNullOrEmpty(filename))
{
return null;
}
if (filename.IndexOf("[playlist]", StringComparison.OrdinalIgnoreCase) != -1)
{
return new Playlist
{
Path = args.Path,
Name = ResolverHelper.StripBrackets(Path.GetFileName(args.Path))
};
}
}
return null;
}
}
}

View File

@@ -1,85 +0,0 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
using System;
using System.IO;
using System.Linq;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
class SpecialFolderResolver : FolderResolver<Folder>
{
private readonly IFileSystem _fileSystem;
private readonly IServerApplicationPaths _appPaths;
public SpecialFolderResolver(IFileSystem fileSystem, IServerApplicationPaths appPaths)
{
_fileSystem = fileSystem;
_appPaths = appPaths;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get { return ResolverPriority.First; }
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Folder.</returns>
protected override Folder Resolve(ItemResolveArgs args)
{
if (args.IsDirectory)
{
if (args.IsPhysicalRoot)
{
return new AggregateFolder();
}
if (string.Equals(args.Path, _appPaths.DefaultUserViewsPath, StringComparison.OrdinalIgnoreCase))
{
return new UserRootFolder(); //if we got here and still a root - must be user root
}
if (args.IsVf)
{
return new CollectionFolder
{
CollectionType = GetCollectionType(args),
PhysicalLocationsList = args.PhysicalLocations.ToList()
};
}
}
return null;
}
private string GetCollectionType(ItemResolveArgs args)
{
return args.FileSystemChildren
.Where(i =>
{
try
{
return !i.IsDirectory &&
string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase);
}
catch (IOException)
{
return false;
}
})
.Select(i => _fileSystem.GetFileNameWithoutExtension(i))
.FirstOrDefault();
}
}
}

View File

@@ -1,75 +0,0 @@
using System;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using System.Linq;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
/// Class EpisodeResolver
/// </summary>
public class EpisodeResolver : BaseVideoResolver<Episode>
{
public EpisodeResolver(ILibraryManager libraryManager) : base(libraryManager)
{
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Episode.</returns>
protected override Episode Resolve(ItemResolveArgs args)
{
var parent = args.Parent;
if (parent == null)
{
return null;
}
var season = parent as Season;
// Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it.
if (season == null)
{
season = parent.GetParents().OfType<Season>().FirstOrDefault();
}
// If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
// Also handle flat tv folders
if (season != null ||
string.Equals(args.GetCollectionType(), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) ||
args.HasParent<Series>())
{
var episode = ResolveVideo<Episode>(args, false);
if (episode != null)
{
var series = parent as Series;
if (series == null)
{
series = parent.GetParents().OfType<Series>().FirstOrDefault();
}
if (series != null)
{
episode.SeriesId = series.Id;
episode.SeriesName = series.Name;
episode.SeriesSortName = series.SortName;
}
if (season != null)
{
episode.SeasonId = season.Id;
episode.SeasonName = season.Name;
}
}
return episode;
}
return null;
}
}
}

View File

@@ -1,62 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Naming.Common;
using MediaBrowser.Naming.TV;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
/// Class SeasonResolver
/// </summary>
public class SeasonResolver : FolderResolver<Season>
{
/// <summary>
/// The _config
/// </summary>
private readonly IServerConfigurationManager _config;
private readonly ILibraryManager _libraryManager;
/// <summary>
/// Initializes a new instance of the <see cref="SeasonResolver"/> class.
/// </summary>
/// <param name="config">The config.</param>
public SeasonResolver(IServerConfigurationManager config, ILibraryManager libraryManager)
{
_config = config;
_libraryManager = libraryManager;
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Season.</returns>
protected override Season Resolve(ItemResolveArgs args)
{
if (args.Parent is Series && args.IsDirectory)
{
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var series = ((Series)args.Parent);
var season = new Season
{
IndexNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(args.Path, true, true).SeasonNumber,
SeriesId = series.Id,
SeriesSortName = series.SortName,
SeriesName = series.Name
};
if (season.IndexNumber.HasValue && season.IndexNumber.Value == 0)
{
season.Name = _config.Configuration.SeasonZeroDisplayName;
}
return season;
}
return null;
}
}
}

View File

@@ -1,251 +0,0 @@
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Naming.Common;
using MediaBrowser.Naming.TV;
using MediaBrowser.Server.Implementations.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
/// <summary>
/// Class SeriesResolver
/// </summary>
public class SeriesResolver : FolderResolver<Series>
{
private readonly IFileSystem _fileSystem;
private readonly ILogger _logger;
private readonly ILibraryManager _libraryManager;
public SeriesResolver(IFileSystem fileSystem, ILogger logger, ILibraryManager libraryManager)
{
_fileSystem = fileSystem;
_logger = logger;
_libraryManager = libraryManager;
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get
{
return ResolverPriority.Second;
}
}
/// <summary>
/// Resolves the specified args.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>Series.</returns>
protected override Series Resolve(ItemResolveArgs args)
{
if (args.IsDirectory)
{
if (args.HasParent<Series>() || args.HasParent<Season>())
{
return null;
}
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
//if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
//{
// return new Series
// {
// Path = args.Path,
// Name = Path.GetFileName(args.Path)
// };
//}
var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path);
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
return new Series
{
Path = args.Path,
Name = Path.GetFileName(args.Path)
};
}
}
else if (string.IsNullOrWhiteSpace(collectionType))
{
if (args.ContainsFileSystemEntryByName("tvshow.nfo"))
{
if (args.Parent.IsRoot)
{
// For now, return null, but if we want to allow this in the future then add some additional checks to guard against a misplaced tvshow.nfo
return null;
}
return new Series
{
Path = args.Path,
Name = Path.GetFileName(args.Path)
};
}
if (args.Parent.IsRoot)
{
return null;
}
if (IsSeriesFolder(args.Path, args.FileSystemChildren, args.DirectoryService, _fileSystem, _logger, _libraryManager, args.GetLibraryOptions(), false))
{
return new Series
{
Path = args.Path,
Name = Path.GetFileName(args.Path)
};
}
}
}
return null;
}
public static bool IsSeriesFolder(string path,
IEnumerable<FileSystemMetadata> fileSystemChildren,
IDirectoryService directoryService,
IFileSystem fileSystem,
ILogger logger,
ILibraryManager libraryManager,
LibraryOptions libraryOptions,
bool isTvContentType)
{
foreach (var child in fileSystemChildren)
{
//if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
//{
// //logger.Debug("Igoring series file or folder marked hidden: {0}", child.FullName);
// continue;
//}
// Can't enforce this because files saved by Bitcasa are always marked System
//if ((attributes & FileAttributes.System) == FileAttributes.System)
//{
// logger.Debug("Igoring series subfolder marked system: {0}", child.FullName);
// continue;
//}
if (child.IsDirectory)
{
if (IsSeasonFolder(child.FullName, isTvContentType, libraryManager))
{
//logger.Debug("{0} is a series because of season folder {1}.", path, child.FullName);
return true;
}
}
else
{
string fullName = child.FullName;
if (libraryManager.IsVideoFile(fullName, libraryOptions))
{
if (isTvContentType)
{
return true;
}
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
// In mixed folders we need to be conservative and avoid expressions that may result in false positives (e.g. movies with numbers in the title)
if (!isTvContentType)
{
namingOptions.EpisodeExpressions = namingOptions.EpisodeExpressions
.Where(i => i.IsNamed && !i.IsOptimistic)
.ToList();
}
var episodeResolver = new Naming.TV.EpisodeResolver(namingOptions, new PatternsLogger());
var episodeInfo = episodeResolver.Resolve(fullName, false, false);
if (episodeInfo != null && episodeInfo.EpisodeNumber.HasValue)
{
return true;
}
}
}
}
logger.Debug("{0} is not a series folder.", path);
return false;
}
/// <summary>
/// Determines whether [is place holder] [the specified path].
/// </summary>
/// <param name="path">The path.</param>
/// <returns><c>true</c> if [is place holder] [the specified path]; otherwise, <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException">path</exception>
private static bool IsVideoPlaceHolder(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentNullException("path");
}
var extension = Path.GetExtension(path);
return string.Equals(extension, ".disc", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether [is season folder] [the specified path].
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isTvContentType">if set to <c>true</c> [is tv content type].</param>
/// <param name="libraryManager">The library manager.</param>
/// <returns><c>true</c> if [is season folder] [the specified path]; otherwise, <c>false</c>.</returns>
private static bool IsSeasonFolder(string path, bool isTvContentType, ILibraryManager libraryManager)
{
var namingOptions = ((LibraryManager)libraryManager).GetNamingOptions();
var seasonNumber = new SeasonPathParser(namingOptions, new RegexProvider()).Parse(path, isTvContentType, isTvContentType).SeasonNumber;
return seasonNumber.HasValue;
}
/// <summary>
/// Sets the initial item values.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="args">The args.</param>
protected override void SetInitialItemValues(Series item, ItemResolveArgs args)
{
base.SetInitialItemValues(item, args);
SetProviderIdFromPath(item, args.Path);
}
/// <summary>
/// Sets the provider id from path.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="path">The path.</param>
private void SetProviderIdFromPath(Series item, string path)
{
var justName = Path.GetFileName(path);
var id = justName.GetAttributeValue("tvdbid");
if (!string.IsNullOrEmpty(id))
{
item.SetProviderId(MetadataProviders.Tvdb, id);
}
}
}
}

View File

@@ -1,45 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Resolvers;
namespace MediaBrowser.Server.Implementations.Library.Resolvers
{
/// <summary>
/// Resolves a Path into a Video
/// </summary>
public class VideoResolver : BaseVideoResolver<Video>
{
public VideoResolver(ILibraryManager libraryManager)
: base(libraryManager)
{
}
protected override Video Resolve(ItemResolveArgs args)
{
if (args.Parent != null)
{
// The movie resolver will handle this
return null;
}
return base.Resolve(args);
}
/// <summary>
/// Gets the priority.
/// </summary>
/// <value>The priority.</value>
public override ResolverPriority Priority
{
get { return ResolverPriority.Last; }
}
}
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new ()
{
public GenericVideoResolver(ILibraryManager libraryManager) : base(libraryManager)
{
}
}
}

View File

@@ -1,280 +0,0 @@
using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Search;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.Extensions;
using MediaBrowser.Model.Extensions;
namespace MediaBrowser.Server.Implementations.Library
{
/// <summary>
/// Class LuceneSearchEngine
/// http://www.codeproject.com/Articles/320219/Lucene-Net-ultra-fast-search-for-MVC-or-WebForms
/// </summary>
public class SearchEngine : ISearchEngine
{
private readonly ILibraryManager _libraryManager;
private readonly IUserManager _userManager;
private readonly ILogger _logger;
public SearchEngine(ILogManager logManager, ILibraryManager libraryManager, IUserManager userManager)
{
_libraryManager = libraryManager;
_userManager = userManager;
_logger = logManager.GetLogger("Lucene");
}
public async Task<QueryResult<SearchHintInfo>> GetSearchHints(SearchQuery query)
{
User user = null;
if (string.IsNullOrWhiteSpace(query.UserId))
{
}
else
{
user = _userManager.GetUserById(query.UserId);
}
var results = await GetSearchHints(query, user).ConfigureAwait(false);
var searchResultArray = results.ToArray();
results = searchResultArray;
var count = searchResultArray.Length;
if (query.StartIndex.HasValue)
{
results = results.Skip(query.StartIndex.Value);
}
if (query.Limit.HasValue)
{
results = results.Take(query.Limit.Value);
}
return new QueryResult<SearchHintInfo>
{
TotalRecordCount = count,
Items = results.ToArray()
};
}
private void AddIfMissing(List<string> list, string value)
{
if (!list.Contains(value, StringComparer.OrdinalIgnoreCase))
{
list.Add(value);
}
}
/// <summary>
/// Gets the search hints.
/// </summary>
/// <param name="query">The query.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{SearchHintResult}.</returns>
/// <exception cref="System.ArgumentNullException">searchTerm</exception>
private Task<IEnumerable<SearchHintInfo>> GetSearchHints(SearchQuery query, User user)
{
var searchTerm = query.SearchTerm;
if (searchTerm != null)
{
searchTerm = searchTerm.Trim().RemoveDiacritics();
}
if (string.IsNullOrWhiteSpace(searchTerm))
{
throw new ArgumentNullException("searchTerm");
}
var terms = GetWords(searchTerm);
var hints = new List<Tuple<BaseItem, string, int>>();
var excludeItemTypes = new List<string>();
var includeItemTypes = (query.IncludeItemTypes ?? new string[] { }).ToList();
excludeItemTypes.Add(typeof(Year).Name);
excludeItemTypes.Add(typeof(Folder).Name);
if (query.IncludeGenres && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Genre", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, typeof(Genre).Name);
AddIfMissing(includeItemTypes, typeof(GameGenre).Name);
AddIfMissing(includeItemTypes, typeof(MusicGenre).Name);
}
}
else
{
AddIfMissing(excludeItemTypes, typeof(Genre).Name);
AddIfMissing(excludeItemTypes, typeof(GameGenre).Name);
AddIfMissing(excludeItemTypes, typeof(MusicGenre).Name);
}
if (query.IncludePeople && (includeItemTypes.Count == 0 || includeItemTypes.Contains("People", StringComparer.OrdinalIgnoreCase) || includeItemTypes.Contains("Person", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, typeof(Person).Name);
}
}
else
{
AddIfMissing(excludeItemTypes, typeof(Person).Name);
}
if (query.IncludeStudios && (includeItemTypes.Count == 0 || includeItemTypes.Contains("Studio", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, typeof(Studio).Name);
}
}
else
{
AddIfMissing(excludeItemTypes, typeof(Studio).Name);
}
if (query.IncludeArtists && (includeItemTypes.Count == 0 || includeItemTypes.Contains("MusicArtist", StringComparer.OrdinalIgnoreCase)))
{
if (!query.IncludeMedia)
{
AddIfMissing(includeItemTypes, typeof(MusicArtist).Name);
}
}
else
{
AddIfMissing(excludeItemTypes, typeof(MusicArtist).Name);
}
AddIfMissing(excludeItemTypes, typeof(CollectionFolder).Name);
var mediaItems = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
NameContains = searchTerm,
ExcludeItemTypes = excludeItemTypes.ToArray(),
IncludeItemTypes = includeItemTypes.ToArray(),
Limit = query.Limit,
IncludeItemsByName = true,
IsVirtualItem = false
});
// Add search hints based on item name
hints.AddRange(mediaItems.Select(item =>
{
var index = GetIndex(item.Name, searchTerm, terms);
return new Tuple<BaseItem, string, int>(item, index.Item1, index.Item2);
}));
var returnValue = hints.Where(i => i.Item3 >= 0).OrderBy(i => i.Item3).Select(i => new SearchHintInfo
{
Item = i.Item1,
MatchedTerm = i.Item2
});
return Task.FromResult(returnValue);
}
/// <summary>
/// Gets the index.
/// </summary>
/// <param name="input">The input.</param>
/// <param name="searchInput">The search input.</param>
/// <param name="searchWords">The search input.</param>
/// <returns>System.Int32.</returns>
private Tuple<string, int> GetIndex(string input, string searchInput, List<string> searchWords)
{
if (string.IsNullOrWhiteSpace(input))
{
throw new ArgumentNullException("input");
}
input = input.RemoveDiacritics();
if (string.Equals(input, searchInput, StringComparison.OrdinalIgnoreCase))
{
return new Tuple<string, int>(searchInput, 0);
}
var index = input.IndexOf(searchInput, StringComparison.OrdinalIgnoreCase);
if (index == 0)
{
return new Tuple<string, int>(searchInput, 1);
}
if (index > 0)
{
return new Tuple<string, int>(searchInput, 2);
}
var items = GetWords(input);
for (var i = 0; i < searchWords.Count; i++)
{
var searchTerm = searchWords[i];
for (var j = 0; j < items.Count; j++)
{
var item = items[j];
if (string.Equals(item, searchTerm, StringComparison.OrdinalIgnoreCase))
{
return new Tuple<string, int>(searchTerm, 3 + (i + 1) * (j + 1));
}
index = item.IndexOf(searchTerm, StringComparison.OrdinalIgnoreCase);
if (index == 0)
{
return new Tuple<string, int>(searchTerm, 4 + (i + 1) * (j + 1));
}
if (index > 0)
{
return new Tuple<string, int>(searchTerm, 5 + (i + 1) * (j + 1));
}
}
}
return new Tuple<string, int>(null, -1);
}
/// <summary>
/// Gets the words.
/// </summary>
/// <param name="term">The term.</param>
/// <returns>System.String[][].</returns>
private List<string> GetWords(string term)
{
var stoplist = GetStopList().ToList();
return term.Split()
.Where(i => !string.IsNullOrWhiteSpace(i) && !stoplist.Contains(i, StringComparer.OrdinalIgnoreCase))
.ToList();
}
private IEnumerable<string> GetStopList()
{
return new[]
{
"the",
"a",
"of",
"an"
};
}
}
}

View File

@@ -1,292 +0,0 @@
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Model.Channels;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Model.Globalization;
namespace MediaBrowser.Server.Implementations.Library
{
public class UserViewManager : IUserViewManager
{
private readonly ILibraryManager _libraryManager;
private readonly ILocalizationManager _localizationManager;
private readonly IUserManager _userManager;
private readonly IChannelManager _channelManager;
private readonly ILiveTvManager _liveTvManager;
private readonly IServerConfigurationManager _config;
public UserViewManager(ILibraryManager libraryManager, ILocalizationManager localizationManager, IUserManager userManager, IChannelManager channelManager, ILiveTvManager liveTvManager, IServerConfigurationManager config)
{
_libraryManager = libraryManager;
_localizationManager = localizationManager;
_userManager = userManager;
_channelManager = channelManager;
_liveTvManager = liveTvManager;
_config = config;
}
public async Task<IEnumerable<Folder>> GetUserViews(UserViewQuery query, CancellationToken cancellationToken)
{
var user = _userManager.GetUserById(query.UserId);
var folders = user.RootFolder
.GetChildren(user, true)
.OfType<Folder>()
.ToList();
if (!query.IncludeHidden)
{
folders = folders.Where(i =>
{
var hidden = i as IHiddenFromDisplay;
return hidden == null || !hidden.IsHiddenFromUser(user);
}).ToList();
}
var plainFolderIds = user.Configuration.PlainFolderViews.Select(i => new Guid(i)).ToList();
var groupedFolders = new List<ICollectionFolder>();
var list = new List<Folder>();
foreach (var folder in folders)
{
var collectionFolder = folder as ICollectionFolder;
var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
if (UserView.IsUserSpecific(folder))
{
list.Add(await _libraryManager.GetNamedView(user, folder.Name, folder.Id.ToString("N"), folderViewType, null, cancellationToken).ConfigureAwait(false));
continue;
}
if (plainFolderIds.Contains(folder.Id) && UserView.IsEligibleForEnhancedView(folderViewType))
{
list.Add(folder);
continue;
}
if (collectionFolder != null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folder.Id))
{
groupedFolders.Add(collectionFolder);
continue;
}
if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
{
list.Add(await GetUserView(folder, folderViewType, string.Empty, cancellationToken).ConfigureAwait(false));
}
else
{
list.Add(folder);
}
}
foreach (var viewType in new[] { CollectionType.Movies, CollectionType.TvShows })
{
var parents = groupedFolders.Where(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType))
.ToList();
if (parents.Count > 0)
{
list.Add(await GetUserView(parents, viewType, string.Empty, user, query.PresetViews, cancellationToken).ConfigureAwait(false));
}
}
if (_config.Configuration.EnableFolderView)
{
var name = _localizationManager.GetLocalizedString("ViewType" + CollectionType.Folders);
list.Add(await _libraryManager.GetNamedView(name, CollectionType.Folders, string.Empty, cancellationToken).ConfigureAwait(false));
}
if (query.IncludeExternalContent)
{
var channelResult = await _channelManager.GetChannelsInternal(new ChannelQuery
{
UserId = query.UserId
}, cancellationToken).ConfigureAwait(false);
var channels = channelResult.Items;
if (_config.Configuration.EnableChannelView && channels.Length > 0)
{
list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false));
}
else
{
list.AddRange(channels);
}
if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId))
{
list.Add(await _liveTvManager.GetInternalLiveTvFolder(CancellationToken.None).ConfigureAwait(false));
}
}
var sorted = _libraryManager.Sort(list, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList();
var orders = user.Configuration.OrderedViews.ToList();
return list
.OrderBy(i =>
{
var index = orders.IndexOf(i.Id.ToString("N"));
if (index == -1)
{
var view = i as UserView;
if (view != null)
{
if (view.DisplayParentId != Guid.Empty)
{
index = orders.IndexOf(view.DisplayParentId.ToString("N"));
}
}
}
return index == -1 ? int.MaxValue : index;
})
.ThenBy(sorted.IndexOf)
.ThenBy(i => i.SortName);
}
public Task<UserView> GetUserSubView(string name, string parentId, string type, string sortName, CancellationToken cancellationToken)
{
var uniqueId = parentId + "subview" + type;
return _libraryManager.GetNamedView(name, parentId, type, sortName, uniqueId, cancellationToken);
}
public Task<UserView> GetUserSubView(string parentId, string type, string sortName, CancellationToken cancellationToken)
{
var name = _localizationManager.GetLocalizedString("ViewType" + type);
return GetUserSubView(name, parentId, type, sortName, cancellationToken);
}
private async Task<Folder> GetUserView(List<ICollectionFolder> parents, string viewType, string sortName, User user, string[] presetViews, CancellationToken cancellationToken)
{
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
{
if (!presetViews.Contains(viewType, StringComparer.OrdinalIgnoreCase))
{
return (Folder)parents[0];
}
return await GetUserView((Folder)parents[0], viewType, string.Empty, cancellationToken).ConfigureAwait(false);
}
var name = _localizationManager.GetLocalizedString("ViewType" + viewType);
return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false);
}
public Task<UserView> GetUserView(Folder parent, string viewType, string sortName, CancellationToken cancellationToken)
{
return _libraryManager.GetShadowView(parent, viewType, sortName, cancellationToken);
}
public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)
{
var user = _userManager.GetUserById(request.UserId);
var libraryItems = GetItemsForLatestItems(user, request);
var list = new List<Tuple<BaseItem, List<BaseItem>>>();
foreach (var item in libraryItems)
{
// Only grab the index container for media
var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer;
if (container == null)
{
list.Add(new Tuple<BaseItem, List<BaseItem>>(null, new List<BaseItem> { item }));
}
else
{
var current = list.FirstOrDefault(i => i.Item1 != null && i.Item1.Id == container.Id);
if (current != null)
{
current.Item2.Add(item);
}
else
{
list.Add(new Tuple<BaseItem, List<BaseItem>>(container, new List<BaseItem> { item }));
}
}
if (list.Count >= request.Limit)
{
break;
}
}
return list;
}
private IEnumerable<BaseItem> GetItemsForLatestItems(User user, LatestItemsQuery request)
{
var parentId = request.ParentId;
var includeItemTypes = request.IncludeItemTypes;
var limit = request.Limit ?? 10;
var parentIds = string.IsNullOrEmpty(parentId)
? new string[] { }
: new[] { parentId };
if (parentIds.Length == 0)
{
parentIds = user.RootFolder.GetChildren(user, true)
.OfType<Folder>()
.Select(i => i.Id.ToString("N"))
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i))
.ToArray();
}
if (parentIds.Length == 0)
{
return new List<BaseItem>();
}
var excludeItemTypes = includeItemTypes.Length == 0 ? new[]
{
typeof(Person).Name,
typeof(Studio).Name,
typeof(Year).Name,
typeof(GameGenre).Name,
typeof(MusicGenre).Name,
typeof(Genre).Name
} : new string[] { };
return _libraryManager.GetItemList(new InternalItemsQuery(user)
{
IncludeItemTypes = includeItemTypes,
SortOrder = SortOrder.Descending,
SortBy = new[] { ItemSortBy.DateCreated },
IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
ExcludeItemTypes = excludeItemTypes,
ExcludeLocationTypes = new[] { LocationType.Virtual },
Limit = limit * 5,
SourceTypes = parentIds.Length == 0 ? new[] { SourceType.Library } : new SourceType[] { },
IsPlayed = request.IsPlayed
}, parentIds);
}
}
}

View File

@@ -1,44 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
/// <summary>
/// Class ArtistsPostScanTask
/// </summary>
public class ArtistsPostScanTask : ILibraryPostScanTask
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public ArtistsPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
return new ArtistsValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
}

View File

@@ -1,84 +0,0 @@
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
/// <summary>
/// Class ArtistsValidator
/// </summary>
public class ArtistsValidator
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
public ArtistsValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var names = _itemRepo.GetAllArtistNames();
var numComplete = 0;
var count = names.Count;
foreach (var name in names)
{
try
{
var item = _libraryManager.GetArtist(name);
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Don't clutter the log
break;
}
catch (Exception ex)
{
_logger.ErrorException("Error refreshing {0}", ex, name);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
}
progress.Report(100);
}
}
}

View File

@@ -1,45 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
/// <summary>
/// Class GameGenresPostScanTask
/// </summary>
public class GameGenresPostScanTask : ILibraryPostScanTask
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="GameGenresPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
public GameGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
return new GameGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
}

View File

@@ -1,74 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
class GameGenresValidator
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
public GameGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var names = _itemRepo.GetGameGenreNames();
var numComplete = 0;
var count = names.Count;
foreach (var name in names)
{
try
{
var item = _libraryManager.GetGameGenre(name);
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Don't clutter the log
break;
}
catch (Exception ex)
{
_logger.ErrorException("Error refreshing {0}", ex, name);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
}
progress.Report(100);
}
}
}

View File

@@ -1,42 +0,0 @@
using MediaBrowser.Controller.Library;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
public class GenresPostScanTask : ILibraryPostScanTask
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
public GenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
return new GenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
}

View File

@@ -1,75 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
class GenresValidator
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
public GenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var names = _itemRepo.GetGenreNames();
var numComplete = 0;
var count = names.Count;
foreach (var name in names)
{
try
{
var item = _libraryManager.GetGenre(name);
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Don't clutter the log
break;
}
catch (Exception ex)
{
_logger.ErrorException("Error refreshing {0}", ex, name);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
}
progress.Report(100);
}
}
}

View File

@@ -1,45 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
/// <summary>
/// Class MusicGenresPostScanTask
/// </summary>
public class MusicGenresPostScanTask : ILibraryPostScanTask
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
public MusicGenresPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
return new MusicGenresValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
}

View File

@@ -1,75 +0,0 @@
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
class MusicGenresValidator
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var names = _itemRepo.GetMusicGenreNames();
var numComplete = 0;
var count = names.Count;
foreach (var name in names)
{
try
{
var item = _libraryManager.GetMusicGenre(name);
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Don't clutter the log
break;
}
catch (Exception ex)
{
_logger.ErrorException("Error refreshing {0}", ex, name);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
}
progress.Report(100);
}
}
}

View File

@@ -1,172 +0,0 @@
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Controller.IO;
using MediaBrowser.Model.IO;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
/// <summary>
/// Class PeopleValidator
/// </summary>
public class PeopleValidator
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
/// <summary>
/// Initializes a new instance of the <see cref="PeopleValidator" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
/// <param name="logger">The logger.</param>
public PeopleValidator(ILibraryManager libraryManager, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem)
{
_libraryManager = libraryManager;
_logger = logger;
_config = config;
_fileSystem = fileSystem;
}
private bool DownloadMetadata(PersonInfo i, PeopleMetadataOptions options)
{
if (i.IsType(PersonType.Actor))
{
return options.DownloadActorMetadata;
}
if (i.IsType(PersonType.Director))
{
return options.DownloadDirectorMetadata;
}
if (i.IsType(PersonType.Composer))
{
return options.DownloadComposerMetadata;
}
if (i.IsType(PersonType.Writer))
{
return options.DownloadWriterMetadata;
}
if (i.IsType(PersonType.Producer))
{
return options.DownloadProducerMetadata;
}
if (i.IsType(PersonType.GuestStar))
{
return options.DownloadGuestStarMetadata;
}
return options.DownloadOtherPeopleMetadata;
}
/// <summary>
/// Validates the people.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task.</returns>
public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
{
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(pct => progress.Report(pct * .15));
var peopleOptions = _config.Configuration.PeopleMetadataOptions;
var people = _libraryManager.GetPeople(new InternalPeopleQuery());
var dict = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach (var person in people)
{
var isMetadataEnabled = DownloadMetadata(person, peopleOptions);
bool currentValue;
if (dict.TryGetValue(person.Name, out currentValue))
{
if (!currentValue && isMetadataEnabled)
{
dict[person.Name] = true;
}
}
else
{
dict[person.Name] = isMetadataEnabled;
}
}
var numComplete = 0;
_logger.Debug("Will refresh {0} people", dict.Count);
var numPeople = dict.Count;
foreach (var person in dict)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
var item = _libraryManager.GetPerson(person.Key);
var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview);
var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 30;
var defaultMetadataRefreshMode = performFullRefresh
? MetadataRefreshMode.FullRefresh
: MetadataRefreshMode.Default;
var imageRefreshMode = performFullRefresh
? ImageRefreshMode.FullRefresh
: ImageRefreshMode.Default;
var options = new MetadataRefreshOptions(_fileSystem)
{
MetadataRefreshMode = person.Value ? defaultMetadataRefreshMode : MetadataRefreshMode.ValidationOnly,
ImageRefreshMode = person.Value ? imageRefreshMode : ImageRefreshMode.ValidationOnly,
ForceSave = performFullRefresh
};
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error validating IBN entry {0}", ex, person);
}
// Update progress
numComplete++;
double percent = numComplete;
percent /= numPeople;
progress.Report(100 * percent);
}
progress.Report(100);
_logger.Info("People validation complete");
}
}
}

View File

@@ -1,45 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
/// <summary>
/// Class MusicGenresPostScanTask
/// </summary>
public class StudiosPostScanTask : ILibraryPostScanTask
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
private readonly IItemRepository _itemRepo;
/// <summary>
/// Initializes a new instance of the <see cref="ArtistsPostScanTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public StudiosPostScanTask(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
return new StudiosValidator(_libraryManager, _logger, _itemRepo).Run(progress, cancellationToken);
}
}
}

View File

@@ -1,74 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Persistence;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
class StudiosValidator
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
/// <summary>
/// The _logger
/// </summary>
private readonly ILogger _logger;
public StudiosValidator(ILibraryManager libraryManager, ILogger logger, IItemRepository itemRepo)
{
_libraryManager = libraryManager;
_logger = logger;
_itemRepo = itemRepo;
}
/// <summary>
/// Runs the specified progress.
/// </summary>
/// <param name="progress">The progress.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var names = _itemRepo.GetStudioNames();
var numComplete = 0;
var count = names.Count;
foreach (var name in names)
{
try
{
var item = _libraryManager.GetStudio(name);
await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Don't clutter the log
break;
}
catch (Exception ex)
{
_logger.ErrorException("Error refreshing {0}", ex, name);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
}
progress.Report(100);
}
}
}

View File

@@ -1,55 +0,0 @@
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Server.Implementations.Library.Validators
{
public class YearsPostScanTask : ILibraryPostScanTask
{
private readonly ILibraryManager _libraryManager;
private readonly ILogger _logger;
public YearsPostScanTask(ILibraryManager libraryManager, ILogger logger)
{
_libraryManager = libraryManager;
_logger = logger;
}
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
{
var yearNumber = 1900;
var maxYear = DateTime.UtcNow.Year + 3;
var count = maxYear - yearNumber + 1;
var numComplete = 0;
while (yearNumber < maxYear)
{
try
{
var year = _libraryManager.GetYear(yearNumber);
await year.RefreshMetadata(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
// Don't clutter the log
break;
}
catch (Exception ex)
{
_logger.ErrorException("Error refreshing year {0}", ex, yearNumber);
}
numComplete++;
double percent = numComplete;
percent /= count;
percent *= 100;
progress.Report(percent);
yearNumber++;
}
}
}
}

View File

@@ -15,7 +15,6 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Server.Implementations.FileOrganization;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -1528,28 +1527,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private async void OnSuccessfulRecording(TimerInfo timer, string path)
{
if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize)
{
try
{
// this is to account for the library monitor holding a lock for additional time after the change is complete.
// ideally this shouldn't be hard-coded
await Task.Delay(30000).ConfigureAwait(false);
//if (timer.IsProgramSeries && GetConfiguration().EnableAutoOrganize)
//{
// try
// {
// // this is to account for the library monitor holding a lock for additional time after the change is complete.
// // ideally this shouldn't be hard-coded
// await Task.Delay(30000).ConfigureAwait(false);
var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
// var organize = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager);
var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false);
// var result = await organize.OrganizeEpisodeFile(path, _config.GetAutoOrganizeOptions(), false, CancellationToken.None).ConfigureAwait(false);
if (result.Status == FileSortingStatus.Success)
{
return;
}
}
catch (Exception ex)
{
_logger.ErrorException("Error processing new recording", ex);
}
}
// if (result.Status == FileSortingStatus.Success)
// {
// return;
// }
// }
// catch (Exception ex)
// {
// _logger.ErrorException("Error processing new recording", ex);
// }
//}
}
private void SaveNfo(TimerInfo timer, string recordingPath, string seriesPath)

View File

@@ -108,7 +108,6 @@
<Compile Include="..\SharedVersion.cs">
<Link>Properties\SharedVersion.cs</Link>
</Compile>
<Compile Include="Activity\ActivityManager.cs" />
<Compile Include="Activity\ActivityRepository.cs" />
<Compile Include="Archiving\ZipClient.cs" />
<Compile Include="Collections\CollectionsDynamicFolder.cs" />
@@ -122,7 +121,6 @@
<Compile Include="Devices\DeviceManager.cs" />
<Compile Include="Devices\DeviceRepository.cs" />
<Compile Include="Devices\CameraUploadsFolder.cs" />
<Compile Include="Dto\DtoService.cs" />
<Compile Include="EntryPoints\ActivityLogEntryPoint.cs" />
<Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" />
<Compile Include="EntryPoints\ExternalPortForwarding.cs" />
@@ -134,16 +132,9 @@
<Compile Include="EntryPoints\RefreshUsersMetadata.cs" />
<Compile Include="EntryPoints\UsageEntryPoint.cs" />
<Compile Include="EntryPoints\UsageReporter.cs" />
<Compile Include="FileOrganization\EpisodeFileOrganizer.cs" />
<Compile Include="FileOrganization\Extensions.cs" />
<Compile Include="FileOrganization\FileOrganizationNotifier.cs" />
<Compile Include="FileOrganization\FileOrganizationService.cs" />
<Compile Include="FileOrganization\NameUtils.cs" />
<Compile Include="FileOrganization\TvFolderOrganizer.cs" />
<Compile Include="EntryPoints\UdpServerEntryPoint.cs" />
<Compile Include="EntryPoints\ServerEventNotifier.cs" />
<Compile Include="EntryPoints\UserDataChangeNotifier.cs" />
<Compile Include="FileOrganization\OrganizerScheduledTask.cs" />
<Compile Include="HttpServer\IHttpListener.cs" />
<Compile Include="HttpServer\Security\AuthorizationContext.cs" />
<Compile Include="HttpServer\ContainerAdapter.cs" />
@@ -171,45 +162,8 @@
<Compile Include="IO\LibraryMonitor.cs" />
<Compile Include="IO\MemoryStreamProvider.cs" />
<Compile Include="IO\ThrottledStream.cs" />
<Compile Include="Library\CoreResolutionIgnoreRule.cs" />
<Compile Include="Library\LibraryManager.cs" />
<Compile Include="Library\LocalTrailerPostScanTask.cs" />
<Compile Include="Library\MediaSourceManager.cs" />
<Compile Include="Library\MusicManager.cs" />
<Compile Include="Library\PathExtensions.cs" />
<Compile Include="Library\Resolvers\SpecialFolderResolver.cs" />
<Compile Include="Library\Resolvers\BaseVideoResolver.cs" />
<Compile Include="Library\Resolvers\PhotoAlbumResolver.cs" />
<Compile Include="Library\Resolvers\PhotoResolver.cs" />
<Compile Include="Library\Resolvers\PlaylistResolver.cs" />
<Compile Include="Library\SearchEngine.cs" />
<Compile Include="Library\ResolverHelper.cs" />
<Compile Include="Library\Resolvers\Audio\AudioResolver.cs" />
<Compile Include="Library\Resolvers\Audio\MusicAlbumResolver.cs" />
<Compile Include="Library\Resolvers\Audio\MusicArtistResolver.cs" />
<Compile Include="Library\Resolvers\ItemResolver.cs" />
<Compile Include="Library\Resolvers\FolderResolver.cs" />
<Compile Include="Library\Resolvers\Movies\BoxSetResolver.cs" />
<Compile Include="Library\Resolvers\Movies\MovieResolver.cs" />
<Compile Include="Library\Resolvers\TV\EpisodeResolver.cs" />
<Compile Include="Library\Resolvers\TV\SeasonResolver.cs" />
<Compile Include="Library\Resolvers\TV\SeriesResolver.cs" />
<Compile Include="Library\Resolvers\VideoResolver.cs" />
<Compile Include="Library\UserDataManager.cs" />
<Compile Include="Library\UserManager.cs" />
<Compile Include="Library\UserViewManager.cs" />
<Compile Include="Library\Validators\ArtistsPostScanTask.cs" />
<Compile Include="Library\Validators\ArtistsValidator.cs" />
<Compile Include="Library\Validators\GameGenresPostScanTask.cs" />
<Compile Include="Library\Validators\GameGenresValidator.cs" />
<Compile Include="Library\Validators\GenresPostScanTask.cs" />
<Compile Include="Library\Validators\GenresValidator.cs" />
<Compile Include="Library\Validators\MusicGenresPostScanTask.cs" />
<Compile Include="Library\Validators\MusicGenresValidator.cs" />
<Compile Include="Library\Validators\PeopleValidator.cs" />
<Compile Include="Library\Validators\StudiosPostScanTask.cs" />
<Compile Include="Library\Validators\StudiosValidator.cs" />
<Compile Include="Library\Validators\YearsPostScanTask.cs" />
<Compile Include="LiveTv\ChannelImageProvider.cs" />
<Compile Include="LiveTv\EmbyTV\DirectRecorder.cs" />
<Compile Include="LiveTv\EmbyTV\EmbyTV.cs" />
@@ -267,19 +221,16 @@
<Compile Include="News\NewsEntryPoint.cs" />
<Compile Include="Notifications\IConfigurableNotificationService.cs" />
<Compile Include="Persistence\BaseSqliteRepository.cs" />
<Compile Include="Persistence\CleanDatabaseScheduledTask.cs" />
<Compile Include="Persistence\DataExtensions.cs" />
<Compile Include="Persistence\IDbConnector.cs" />
<Compile Include="Persistence\MediaStreamColumns.cs" />
<Compile Include="Reflection\AssemblyInfo.cs" />
<Compile Include="ScheduledTasks\RefreshMediaLibraryTask.cs" />
<Compile Include="Security\MBLicenseFile.cs" />
<Compile Include="Security\PluginSecurityManager.cs" />
<Compile Include="Security\RegRecord.cs" />
<Compile Include="Serialization\JsonSerializer.cs" />
<Compile Include="Social\SharingManager.cs" />
<Compile Include="Social\SharingRepository.cs" />
<Compile Include="Sorting\StartDateComparer.cs" />
<Compile Include="Sync\SyncHelper.cs" />
<Compile Include="Sync\SyncJobOptions.cs" />
<Compile Include="Sync\SyncNotificationEntryPoint.cs" />
@@ -311,40 +262,10 @@
</Compile>
<Compile Include="Session\SessionWebSocketListener.cs" />
<Compile Include="Session\WebSocketController.cs" />
<Compile Include="Sorting\AiredEpisodeOrderComparer.cs" />
<Compile Include="Sorting\AirTimeComparer.cs" />
<Compile Include="Sorting\AlbumArtistComparer.cs" />
<Compile Include="Sorting\AlbumComparer.cs" />
<Compile Include="Sorting\AlphanumComparator.cs" />
<Compile Include="Sorting\ArtistComparer.cs" />
<Compile Include="Sorting\BudgetComparer.cs" />
<Compile Include="Sorting\CommunityRatingComparer.cs" />
<Compile Include="Sorting\CriticRatingComparer.cs" />
<Compile Include="Sorting\DateCreatedComparer.cs" />
<Compile Include="Sorting\DateLastMediaAddedComparer.cs" />
<Compile Include="Sorting\DatePlayedComparer.cs" />
<Compile Include="Sorting\GameSystemComparer.cs" />
<Compile Include="Sorting\IsFavoriteOrLikeComparer.cs" />
<Compile Include="Sorting\IsFolderComparer.cs" />
<Compile Include="Sorting\IsPlayedComparer.cs" />
<Compile Include="Sorting\IsUnplayedComparer.cs" />
<Compile Include="Sorting\MetascoreComparer.cs" />
<Compile Include="Sorting\NameComparer.cs" />
<Compile Include="Sorting\OfficialRatingComparer.cs" />
<Compile Include="Sorting\PlayCountComparer.cs" />
<Compile Include="Sorting\PlayersComparer.cs" />
<Compile Include="Sorting\PremiereDateComparer.cs" />
<Compile Include="Sorting\ProductionYearComparer.cs" />
<Compile Include="Sorting\RandomComparer.cs" />
<Compile Include="Sorting\RevenueComparer.cs" />
<Compile Include="Sorting\RuntimeComparer.cs" />
<Compile Include="Sorting\SeriesSortNameComparer.cs" />
<Compile Include="Sorting\SortNameComparer.cs" />
<Compile Include="Persistence\SqliteDisplayPreferencesRepository.cs" />
<Compile Include="Persistence\SqliteItemRepository.cs" />
<Compile Include="Persistence\SqliteUserDataRepository.cs" />
<Compile Include="Persistence\SqliteUserRepository.cs" />
<Compile Include="Sorting\StudioComparer.cs" />
<Compile Include="Sync\AppSyncProvider.cs" />
<Compile Include="Sync\CloudSyncProfile.cs" />
<Compile Include="Sync\IHasSyncQuality.cs" />

View File

@@ -1,360 +0,0 @@
using MediaBrowser.Common.Progress;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.IO;
using MediaBrowser.Model.IO;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using MediaBrowser.Server.Implementations.ScheduledTasks;
namespace MediaBrowser.Server.Implementations.Persistence
{
public class CleanDatabaseScheduledTask : IScheduledTask
{
private readonly ILibraryManager _libraryManager;
private readonly IItemRepository _itemRepo;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;
private readonly IHttpServer _httpServer;
private readonly ILocalizationManager _localization;
private readonly ITaskManager _taskManager;
public const int MigrationVersion = 23;
public static bool EnableUnavailableMessage = false;
public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IHttpServer httpServer, ILocalizationManager localization, ITaskManager taskManager)
{
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_logger = logger;
_config = config;
_fileSystem = fileSystem;
_httpServer = httpServer;
_localization = localization;
_taskManager = taskManager;
}
public string Name
{
get { return "Clean Database"; }
}
public string Description
{
get { return "Deletes obsolete content from the database."; }
}
public string Category
{
get { return "Library"; }
}
public async Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
OnProgress(0);
// Ensure these objects are lazy loaded.
// Without this there is a deadlock that will need to be investigated
var rootChildren = _libraryManager.RootFolder.Children.ToList();
rootChildren = _libraryManager.GetUserRootFolder().Children.ToList();
var innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p =>
{
double newPercentCommplete = .4 * p;
OnProgress(newPercentCommplete);
progress.Report(newPercentCommplete);
});
await UpdateToLatestSchema(cancellationToken, innerProgress).ConfigureAwait(false);
innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p =>
{
double newPercentCommplete = 40 + .05 * p;
OnProgress(newPercentCommplete);
progress.Report(newPercentCommplete);
});
await CleanDeadItems(cancellationToken, innerProgress).ConfigureAwait(false);
progress.Report(45);
innerProgress = new ActionableProgress<double>();
innerProgress.RegisterAction(p =>
{
double newPercentCommplete = 45 + .55 * p;
OnProgress(newPercentCommplete);
progress.Report(newPercentCommplete);
});
await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false);
progress.Report(100);
await _itemRepo.UpdateInheritedValues(cancellationToken).ConfigureAwait(false);
if (_config.Configuration.MigrationVersion < MigrationVersion)
{
_config.Configuration.MigrationVersion = MigrationVersion;
_config.SaveConfiguration();
}
if (_config.Configuration.SchemaVersion < SqliteItemRepository.LatestSchemaVersion)
{
_config.Configuration.SchemaVersion = SqliteItemRepository.LatestSchemaVersion;
_config.SaveConfiguration();
}
if (EnableUnavailableMessage)
{
EnableUnavailableMessage = false;
_httpServer.GlobalResponse = null;
_taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
}
_taskManager.SuspendTriggers = false;
}
private void OnProgress(double newPercentCommplete)
{
if (EnableUnavailableMessage)
{
var html = "<!doctype html><html><head><title>Emby</title></head><body>";
var text = _localization.GetLocalizedString("DbUpgradeMessage");
html += string.Format(text, newPercentCommplete.ToString("N2", CultureInfo.InvariantCulture));
html += "<script>setTimeout(function(){window.location.reload(true);}, 5000);</script>";
html += "</body></html>";
_httpServer.GlobalResponse = html;
}
}
private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress<double> progress)
{
var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
IsCurrentSchema = false,
ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name }
});
var numComplete = 0;
var numItems = itemIds.Count;
_logger.Debug("Upgrading schema for {0} items", numItems);
var list = new List<BaseItem>();
foreach (var itemId in itemIds)
{
cancellationToken.ThrowIfCancellationRequested();
if (itemId != Guid.Empty)
{
// Somehow some invalid data got into the db. It probably predates the boundary checking
var item = _libraryManager.GetItemById(itemId);
if (item != null)
{
list.Add(item);
}
}
if (list.Count >= 1000)
{
try
{
await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error saving item", ex);
}
list.Clear();
}
numComplete++;
double percent = numComplete;
percent /= numItems;
progress.Report(percent * 100);
}
if (list.Count > 0)
{
try
{
await _itemRepo.SaveItems(list, cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error saving item", ex);
}
}
progress.Report(100);
}
private async Task CleanDeadItems(CancellationToken cancellationToken, IProgress<double> progress)
{
var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery
{
HasDeadParentId = true
});
var numComplete = 0;
var numItems = itemIds.Count;
_logger.Debug("Cleaning {0} items with dead parent links", numItems);
foreach (var itemId in itemIds)
{
cancellationToken.ThrowIfCancellationRequested();
var item = _libraryManager.GetItemById(itemId);
if (item != null)
{
_logger.Info("Cleaning item {0} type: {1} path: {2}", item.Name, item.GetType().Name, item.Path ?? string.Empty);
await item.Delete(new DeleteOptions
{
DeleteFileLocation = false
}).ConfigureAwait(false);
}
numComplete++;
double percent = numComplete;
percent /= numItems;
progress.Report(percent * 100);
}
progress.Report(100);
}
private async Task CleanDeletedItems(CancellationToken cancellationToken, IProgress<double> progress)
{
var result = _itemRepo.GetItemIdsWithPath(new InternalItemsQuery
{
LocationTypes = new[] { LocationType.FileSystem },
//Limit = limit,
// These have their own cleanup routines
ExcludeItemTypes = new[]
{
typeof(Person).Name,
typeof(Genre).Name,
typeof(MusicGenre).Name,
typeof(GameGenre).Name,
typeof(Studio).Name,
typeof(Year).Name,
typeof(Channel).Name,
typeof(AggregateFolder).Name,
typeof(CollectionFolder).Name
}
});
var numComplete = 0;
var numItems = result.Items.Length;
foreach (var item in result.Items)
{
cancellationToken.ThrowIfCancellationRequested();
var path = item.Item2;
try
{
if (_fileSystem.FileExists(path) || _fileSystem.DirectoryExists(path))
{
continue;
}
var libraryItem = _libraryManager.GetItemById(item.Item1);
if (libraryItem.IsTopParent)
{
continue;
}
var hasDualAccess = libraryItem as IHasDualAccess;
if (hasDualAccess != null && hasDualAccess.IsAccessedByName)
{
continue;
}
var libraryItemPath = libraryItem.Path;
if (!string.Equals(libraryItemPath, path, StringComparison.OrdinalIgnoreCase))
{
_logger.Error("CleanDeletedItems aborting delete for item {0}-{1} because paths don't match. {2}---{3}", libraryItem.Id, libraryItem.Name, libraryItem.Path ?? string.Empty, path ?? string.Empty);
continue;
}
if (Folder.IsPathOffline(path))
{
await libraryItem.UpdateIsOffline(true).ConfigureAwait(false);
continue;
}
_logger.Info("Deleting item from database {0} because path no longer exists. type: {1} path: {2}", libraryItem.Name, libraryItem.GetType().Name, libraryItemPath ?? string.Empty);
await libraryItem.OnFileDeleted().ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
_logger.ErrorException("Error in CleanDeletedItems. File {0}", ex, path);
}
numComplete++;
double percent = numComplete;
percent /= numItems;
progress.Report(percent * 100);
}
}
/// <summary>
/// Creates the triggers that define when the task will run
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[] {
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(24).Ticks}
};
}
public string Key
{
get { return "CleanDatabase"; }
}
}
}

View File

@@ -1,96 +0,0 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Library;
using MediaBrowser.Server.Implementations.Library;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Tasks;
namespace MediaBrowser.Server.Implementations.ScheduledTasks
{
/// <summary>
/// Class RefreshMediaLibraryTask
/// </summary>
public class RefreshMediaLibraryTask : IScheduledTask
{
/// <summary>
/// The _library manager
/// </summary>
private readonly ILibraryManager _libraryManager;
private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="RefreshMediaLibraryTask" /> class.
/// </summary>
/// <param name="libraryManager">The library manager.</param>
public RefreshMediaLibraryTask(ILibraryManager libraryManager, IServerConfigurationManager config)
{
_libraryManager = libraryManager;
_config = config;
}
/// <summary>
/// Creates the triggers that define when the task will run
/// </summary>
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
return new[] {
// Every so often
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(12).Ticks}
};
}
/// <summary>
/// Executes the internal.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <param name="progress">The progress.</param>
/// <returns>Task.</returns>
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
{
cancellationToken.ThrowIfCancellationRequested();
progress.Report(0);
return ((LibraryManager)_libraryManager).ValidateMediaLibraryInternal(progress, cancellationToken);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return "Scan media library"; }
}
/// <summary>
/// Gets the description.
/// </summary>
/// <value>The description.</value>
public string Description
{
get { return "Scans your media library and refreshes metatata based on configuration."; }
}
/// <summary>
/// Gets the category.
/// </summary>
/// <value>The category.</value>
public string Category
{
get
{
return "Library";
}
}
public string Key
{
get { return "RefreshLibrary"; }
}
}
}

View File

@@ -1,71 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class AirTimeComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return DateTime.Compare(GetValue(x), GetValue(y));
}
/// <summary>
/// Gets the value.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
private DateTime GetValue(BaseItem x)
{
var series = x as Series;
if (series == null)
{
var season = x as Season;
if (season != null)
{
series = season.Series;
}
else
{
var episode = x as Episode;
if (episode != null)
{
series = episode.Series;
}
}
}
if (series != null)
{
DateTime result;
if (DateTime.TryParse(series.AirTime, out result))
{
return result;
}
}
return DateTime.MinValue;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.AirTime; }
}
}
}

View File

@@ -1,160 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
class AiredEpisodeOrderComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
if (x.PremiereDate.HasValue && y.PremiereDate.HasValue)
{
var val = DateTime.Compare(x.PremiereDate.Value, y.PremiereDate.Value);
if (val != 0)
{
//return val;
}
}
var episode1 = x as Episode;
var episode2 = y as Episode;
if (episode1 == null)
{
if (episode2 == null)
{
return 0;
}
return 1;
}
if (episode2 == null)
{
return -1;
}
return Compare(episode1, episode2);
}
private int Compare(Episode x, Episode y)
{
var isXSpecial = (x.ParentIndexNumber ?? -1) == 0;
var isYSpecial = (y.ParentIndexNumber ?? -1) == 0;
if (isXSpecial && isYSpecial)
{
return CompareSpecials(x, y);
}
if (!isXSpecial && !isYSpecial)
{
return CompareEpisodes(x, y);
}
if (!isXSpecial)
{
return CompareEpisodeToSpecial(x, y);
}
return CompareEpisodeToSpecial(y, x) * -1;
}
private int CompareEpisodeToSpecial(Episode x, Episode y)
{
// http://thetvdb.com/wiki/index.php?title=Special_Episodes
var xSeason = x.ParentIndexNumber ?? -1;
var ySeason = y.AirsAfterSeasonNumber ?? y.AirsBeforeSeasonNumber ?? -1;
if (xSeason != ySeason)
{
return xSeason.CompareTo(ySeason);
}
// Special comes after episode
if (y.AirsAfterSeasonNumber.HasValue)
{
return -1;
}
var yEpisode = y.AirsBeforeEpisodeNumber;
// Special comes before the season
if (!yEpisode.HasValue)
{
return 1;
}
// Compare episode number
var xEpisode = x.IndexNumber;
if (!xEpisode.HasValue)
{
// Can't really compare if this happens
return 0;
}
// Special comes before episode
if (xEpisode.Value == yEpisode.Value)
{
return 1;
}
return xEpisode.Value.CompareTo(yEpisode.Value);
}
private int CompareSpecials(Episode x, Episode y)
{
return GetSpecialCompareValue(x).CompareTo(GetSpecialCompareValue(y));
}
private int GetSpecialCompareValue(Episode item)
{
// First sort by season number
// Since there are three sort orders, pad with 9 digits (3 for each, figure 1000 episode buffer should be enough)
var val = (item.AirsAfterSeasonNumber ?? item.AirsBeforeSeasonNumber ?? 0) * 1000000000;
// Second sort order is if it airs after the season
if (item.AirsAfterSeasonNumber.HasValue)
{
val += 1000000;
}
// Third level is the episode number
val += (item.AirsBeforeEpisodeNumber ?? 0) * 1000;
// Finally, if that's still the same, last resort is the special number itself
val += item.IndexNumber ?? 0;
return val;
}
private int CompareEpisodes(Episode x, Episode y)
{
var xValue = (x.ParentIndexNumber ?? -1) * 1000 + (x.IndexNumber ?? -1);
var yValue = (y.ParentIndexNumber ?? -1) * 1000 + (y.IndexNumber ?? -1);
return xValue.CompareTo(yValue);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.AiredEpisodeOrder; }
}
}
}

View File

@@ -1,47 +0,0 @@
using System.Linq;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class AlbumArtistComparer
/// </summary>
public class AlbumArtistComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
private string GetValue(BaseItem x)
{
var audio = x as IHasAlbumArtist;
return audio != null ? audio.AlbumArtists.FirstOrDefault() : null;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.AlbumArtist; }
}
}
}

View File

@@ -1,46 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class AlbumComparer
/// </summary>
public class AlbumComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
private string GetValue(BaseItem x)
{
var audio = x as Audio;
return audio == null ? string.Empty : audio.Album;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Album; }
}
}
}

View File

@@ -1,99 +0,0 @@
using System.Collections.Generic;
using System.Text;
using MediaBrowser.Controller.Sorting;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class AlphanumComparator : IComparer<string>
{
public static int CompareValues(string s1, string s2)
{
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || SortHelper.InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || SortHelper.InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
if (!int.TryParse(thisChunk.ToString(), out thisNumericChunk))
{
return 0;
}
if (!int.TryParse(thatChunk.ToString(), out thatNumericChunk))
{
return 0;
}
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
public int Compare(string x, string y)
{
return CompareValues(x, y);
}
}
}

View File

@@ -1,51 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class ArtistComparer
/// </summary>
public class ArtistComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
private string GetValue(BaseItem x)
{
var audio = x as Audio;
if (audio == null)
{
return string.Empty;
}
return audio.Artists.Count == 0 ? null : audio.Artists[0];
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Artist; }
}
}
}

View File

@@ -1,39 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class BudgetComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
private double GetValue(BaseItem x)
{
var hasBudget = x as IHasBudget;
if (hasBudget != null)
{
return hasBudget.Budget ?? 0;
}
return 0;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Budget; }
}
}
}

View File

@@ -1,29 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class CommunityRatingComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return (x.CommunityRating ?? 0).CompareTo(y.CommunityRating ?? 0);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.CommunityRating; }
}
}
}

View File

@@ -1,37 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class CriticRatingComparer
/// </summary>
public class CriticRatingComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
private float GetValue(BaseItem x)
{
return x.CriticRating ?? 0;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.CriticRating; }
}
}
}

View File

@@ -1,33 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class DateCreatedComparer
/// </summary>
public class DateCreatedComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return DateTime.Compare(x.DateCreated, y.DateCreated);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.DateCreated; }
}
}
}

View File

@@ -1,69 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class DateLastMediaAddedComparer : IUserBaseItemComparer
{
/// <summary>
/// Gets or sets the user.
/// </summary>
/// <value>The user.</value>
public User User { get; set; }
/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
public IUserManager UserManager { get; set; }
/// <summary>
/// Gets or sets the user data repository.
/// </summary>
/// <value>The user data repository.</value>
public IUserDataManager UserDataRepository { get; set; }
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetDate(x).CompareTo(GetDate(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private DateTime GetDate(BaseItem x)
{
var folder = x as Folder;
if (folder != null)
{
if (folder.DateLastMediaAdded.HasValue)
{
return folder.DateLastMediaAdded.Value;
}
}
return DateTime.MinValue;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.DateLastContentAdded; }
}
}
}

View File

@@ -1,69 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class DatePlayedComparer
/// </summary>
public class DatePlayedComparer : IUserBaseItemComparer
{
/// <summary>
/// Gets or sets the user.
/// </summary>
/// <value>The user.</value>
public User User { get; set; }
/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
public IUserManager UserManager { get; set; }
/// <summary>
/// Gets or sets the user data repository.
/// </summary>
/// <value>The user data repository.</value>
public IUserDataManager UserDataRepository { get; set; }
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetDate(x).CompareTo(GetDate(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private DateTime GetDate(BaseItem x)
{
var userdata = UserDataRepository.GetUserData(User, x);
if (userdata != null && userdata.LastPlayedDate.HasValue)
{
return userdata.LastPlayedDate.Value;
}
return DateTime.MinValue;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.DatePlayed; }
}
}
}

View File

@@ -1,54 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class GameSystemComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
}
/// <summary>
/// Gets the value.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
private string GetValue(BaseItem x)
{
var game = x as Game;
if (game != null)
{
return game.GameSystem;
}
var system = x as GameSystem;
if (system != null)
{
return system.GameSystemName;
}
return string.Empty;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.GameSystem; }
}
}
}

View File

@@ -1,58 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class IsFavoriteOrLikeComparer : IUserBaseItemComparer
{
/// <summary>
/// Gets or sets the user.
/// </summary>
/// <value>The user.</value>
public User User { get; set; }
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
return x.IsFavoriteOrLiked(User) ? 0 : 1;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.IsFavoriteOrLiked; }
}
/// <summary>
/// Gets or sets the user data repository.
/// </summary>
/// <value>The user data repository.</value>
public IUserDataManager UserDataRepository { get; set; }
/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
public IUserManager UserManager { get; set; }
}
}

View File

@@ -1,39 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class IsFolderComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
/// <summary>
/// Gets the value.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
private int GetValue(BaseItem x)
{
return x.IsFolder ? 0 : 1;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.IsFolder; }
}
}
}

View File

@@ -1,58 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class IsPlayedComparer : IUserBaseItemComparer
{
/// <summary>
/// Gets or sets the user.
/// </summary>
/// <value>The user.</value>
public User User { get; set; }
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
return x.IsPlayed(User) ? 0 : 1;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.IsUnplayed; }
}
/// <summary>
/// Gets or sets the user data repository.
/// </summary>
/// <value>The user data repository.</value>
public IUserDataManager UserDataRepository { get; set; }
/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
public IUserManager UserManager { get; set; }
}
}

View File

@@ -1,58 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class IsUnplayedComparer : IUserBaseItemComparer
{
/// <summary>
/// Gets or sets the user.
/// </summary>
/// <value>The user.</value>
public User User { get; set; }
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
return x.IsUnplayed(User) ? 0 : 1;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.IsUnplayed; }
}
/// <summary>
/// Gets or sets the user data repository.
/// </summary>
/// <value>The user data repository.</value>
public IUserDataManager UserDataRepository { get; set; }
/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
public IUserManager UserManager { get; set; }
}
}

View File

@@ -1,41 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class MetascoreComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
private float GetValue(BaseItem x)
{
var hasMetascore = x as IHasMetascore;
if (hasMetascore != null)
{
return hasMetascore.Metascore ?? 0;
}
return 0;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Metascore; }
}
}
}

View File

@@ -1,33 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class NameComparer
/// </summary>
public class NameComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return string.Compare(x.Name, y.Name, StringComparison.CurrentCultureIgnoreCase);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Name; }
}
}
}

View File

@@ -1,40 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class OfficialRatingComparer : IBaseItemComparer
{
private readonly ILocalizationManager _localization;
public OfficialRatingComparer(ILocalizationManager localization)
{
_localization = localization;
}
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
var levelX = string.IsNullOrEmpty(x.OfficialRating) ? 0 : _localization.GetRatingLevel(x.OfficialRating) ?? 0;
var levelY = string.IsNullOrEmpty(y.OfficialRating) ? 0 : _localization.GetRatingLevel(y.OfficialRating) ?? 0;
return levelX.CompareTo(levelY);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.OfficialRating; }
}
}
}

View File

@@ -1,63 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class PlayCountComparer
/// </summary>
public class PlayCountComparer : IUserBaseItemComparer
{
/// <summary>
/// Gets or sets the user.
/// </summary>
/// <value>The user.</value>
public User User { get; set; }
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
var userdata = UserDataRepository.GetUserData(User, x);
return userdata == null ? 0 : userdata.PlayCount;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.PlayCount; }
}
/// <summary>
/// Gets or sets the user data repository.
/// </summary>
/// <value>The user data repository.</value>
public IUserDataManager UserDataRepository { get; set; }
/// <summary>
/// Gets or sets the user manager.
/// </summary>
/// <value>The user manager.</value>
public IUserManager UserManager { get; set; }
}
}

View File

@@ -1,46 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class PlayersComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
/// <summary>
/// Gets the value.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
private int GetValue(BaseItem x)
{
var game = x as Game;
if (game != null)
{
return game.PlayersSupported ?? 0;
}
return 0;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Players; }
}
}
}

View File

@@ -1,59 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class PremiereDateComparer
/// </summary>
public class PremiereDateComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetDate(x).CompareTo(GetDate(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private DateTime GetDate(BaseItem x)
{
if (x.PremiereDate.HasValue)
{
return x.PremiereDate.Value;
}
if (x.ProductionYear.HasValue)
{
try
{
return new DateTime(x.ProductionYear.Value, 1, 1, 0, 0, 0, DateTimeKind.Utc);
}
catch (ArgumentOutOfRangeException)
{
// Don't blow up if the item has a bad ProductionYear, just return MinValue
}
}
return DateTime.MinValue;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.PremiereDate; }
}
}
}

View File

@@ -1,52 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class ProductionYearComparer
/// </summary>
public class ProductionYearComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private int GetValue(BaseItem x)
{
if (x.ProductionYear.HasValue)
{
return x.ProductionYear.Value;
}
if (x.PremiereDate.HasValue)
{
return x.PremiereDate.Value.Year;
}
return 0;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.ProductionYear; }
}
}
}

View File

@@ -1,33 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class RandomComparer
/// </summary>
public class RandomComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return Guid.NewGuid().CompareTo(Guid.NewGuid());
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Random; }
}
}
}

View File

@@ -1,39 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class RevenueComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetValue(x).CompareTo(GetValue(y));
}
private double GetValue(BaseItem x)
{
var hasBudget = x as IHasBudget;
if (hasBudget != null)
{
return hasBudget.Revenue ?? 0;
}
return 0;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Revenue; }
}
}
}

View File

@@ -1,32 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class RuntimeComparer
/// </summary>
public class RuntimeComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return (x.RunTimeTicks ?? 0).CompareTo(y.RunTimeTicks ?? 0);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Runtime; }
}
}
}

View File

@@ -1,37 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
class SeriesSortNameComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase);
}
private string GetValue(BaseItem item)
{
var hasSeries = item as IHasSeries;
return hasSeries != null ? hasSeries.SeriesSortName : null;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.SeriesSortName; }
}
}
}

View File

@@ -1,33 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
/// <summary>
/// Class SortNameComparer
/// </summary>
public class SortNameComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return string.Compare(x.SortName, y.SortName, StringComparison.CurrentCultureIgnoreCase);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.SortName; }
}
}
}

View File

@@ -1,47 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class StartDateComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return GetDate(x).CompareTo(GetDate(y));
}
/// <summary>
/// Gets the date.
/// </summary>
/// <param name="x">The x.</param>
/// <returns>DateTime.</returns>
private DateTime GetDate(BaseItem x)
{
var hasStartDate = x as LiveTvProgram;
if (hasStartDate != null)
{
return hasStartDate.StartDate;
}
return DateTime.MinValue;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.StartDate; }
}
}
}

View File

@@ -1,30 +0,0 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Sorting;
using MediaBrowser.Model.Querying;
using System.Linq;
namespace MediaBrowser.Server.Implementations.Sorting
{
public class StudioComparer : IBaseItemComparer
{
/// <summary>
/// Compares the specified x.
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
/// <returns>System.Int32.</returns>
public int Compare(BaseItem x, BaseItem y)
{
return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty);
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name.</value>
public string Name
{
get { return ItemSortBy.Studio; }
}
}
}