add option to merge metadata and IBN paths

This commit is contained in:
Luke Pulverenti 2015-01-26 11:47:15 -05:00
parent 91416cb8a8
commit 63f3cf97da
33 changed files with 411 additions and 379 deletions

View File

@ -73,44 +73,44 @@ namespace MediaBrowser.Api.Music
public object Get(GetInstantMixFromArtistId request)
{
var item = (MusicArtist)_libraryManager.GetItemById(request.Id);
var item = _libraryManager.GetItemById(request.Id);
var user = _userManager.GetUserById(request.UserId.Value);
var items = _musicManager.GetInstantMixFromArtist(item.Name, user);
var items = _musicManager.GetInstantMixFromItem(item, user);
return GetResult(items, user, request);
}
public object Get(GetInstantMixFromMusicGenreId request)
{
var item = (MusicGenre)_libraryManager.GetItemById(request.Id);
var item = _libraryManager.GetItemById(request.Id);
var user = _userManager.GetUserById(request.UserId.Value);
var items = _musicManager.GetInstantMixFromGenres(new[] { item.Name }, user);
var items = _musicManager.GetInstantMixFromItem(item, user);
return GetResult(items, user, request);
}
public object Get(GetInstantMixFromSong request)
{
var item = (Audio)_libraryManager.GetItemById(request.Id);
var item = _libraryManager.GetItemById(request.Id);
var user = _userManager.GetUserById(request.UserId.Value);
var items = _musicManager.GetInstantMixFromSong(item, user);
var items = _musicManager.GetInstantMixFromItem(item, user);
return GetResult(items, user, request);
}
public object Get(GetInstantMixFromAlbum request)
{
var album = (MusicAlbum)_libraryManager.GetItemById(request.Id);
var album = _libraryManager.GetItemById(request.Id);
var user = _userManager.GetUserById(request.UserId.Value);
var items = _musicManager.GetInstantMixFromAlbum(album, user);
var items = _musicManager.GetInstantMixFromItem(album, user);
return GetResult(items, user, request);
}
@ -121,7 +121,7 @@ namespace MediaBrowser.Api.Music
var user = _userManager.GetUserById(request.UserId.Value);
var items = _musicManager.GetInstantMixFromPlaylist(playlist, user);
var items = _musicManager.GetInstantMixFromItem(playlist, user);
return GetResult(items, user, request);
}

View File

@ -824,7 +824,7 @@ namespace MediaBrowser.Api.Playback
{
get
{
return true;
return false;
}
}

View File

@ -62,7 +62,7 @@ namespace MediaBrowser.Api
{
_config.Configuration.IsStartupWizardCompleted = true;
_config.Configuration.EnableLocalizedGuids = true;
_config.Configuration.StoreArtistsInMetadata = true;
_config.Configuration.MergeMetadataAndImagesByName = true;
_config.Configuration.EnableStandaloneMetadata = true;
_config.Configuration.EnableLibraryMetadataSubFolder = true;
_config.SaveConfiguration();

View File

@ -228,7 +228,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
/// <value>The user id.</value>
[ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
public Guid UserId { get; set; }
public string UserId { get; set; }
[ApiMember(Name = "Limit", Description = "Limit", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
public int Limit { get; set; }
@ -304,81 +304,15 @@ namespace MediaBrowser.Api.UserLibrary
{
var user = _userManager.GetUserById(request.UserId);
var includeTypes = string.IsNullOrWhiteSpace(request.IncludeItemTypes)
? new string[] { }
: request.IncludeItemTypes.Split(',');
var currentUser = user;
Func<BaseItem, bool> filter = i =>
var list = _userViewManager.GetLatestItems(new LatestItemsQuery
{
if (includeTypes.Length > 0)
{
if (!includeTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
}
if (request.IsPlayed.HasValue)
{
var val = request.IsPlayed.Value;
if (i.IsPlayed(currentUser) != val)
{
return false;
}
}
return i.LocationType != LocationType.Virtual && !i.IsFolder;
};
// Avoid implicitly captured closure
var libraryItems = string.IsNullOrEmpty(request.ParentId) && user != null ?
GetItemsConfiguredForLatest(user, filter) :
GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, filter);
libraryItems = libraryItems.OrderByDescending(i => i.DateCreated);
if (request.IsPlayed.HasValue)
{
var takeLimit = request.Limit * 20;
libraryItems = libraryItems.Take(takeLimit);
}
// Avoid implicitly captured closure
var items = libraryItems
.ToList();
var list = new List<Tuple<BaseItem, List<BaseItem>>>();
foreach (var item in items)
{
// 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;
}
}
GroupItems = request.GroupItems,
IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray(),
IsPlayed = request.IsPlayed,
Limit = request.Limit,
ParentId = request.ParentId,
UserId = request.UserId
});
var options = GetDtoOptions(request);
@ -403,18 +337,6 @@ namespace MediaBrowser.Api.UserLibrary
return ToOptimizedResult(dtos.ToList());
}
private IEnumerable<BaseItem> GetItemsConfiguredForLatest(User user, Func<BaseItem,bool> filter)
{
// Avoid implicitly captured closure
var currentUser = user;
return user.RootFolder.GetChildren(user, true)
.OfType<Folder>()
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N")))
.SelectMany(i => i.GetRecursiveChildren(currentUser, filter))
.DistinctBy(i => i.Id);
}
public async Task<object> Get(GetUserViews request)
{
var user = _userManager.GetUserById(request.UserId);

View File

@ -108,13 +108,9 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// </summary>
private TaskResult _lastExecutionResult;
/// <summary>
/// The _last execution resultinitialized
/// </summary>
private bool _lastExecutionResultinitialized;
/// <summary>
/// The _last execution result sync lock
/// </summary>
private object _lastExecutionResultSyncLock = new object();
private readonly object _lastExecutionResultSyncLock = new object();
/// <summary>
/// Gets the last execution result.
/// </summary>
@ -123,38 +119,39 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
get
{
LazyInitializer.EnsureInitialized(ref _lastExecutionResult, ref _lastExecutionResultinitialized, ref _lastExecutionResultSyncLock, () =>
if (_lastExecutionResult == null)
{
var path = GetHistoryFilePath();
lock (_lastExecutionResultSyncLock)
{
if (_lastExecutionResult == null)
{
var path = GetHistoryFilePath();
try
{
return JsonSerializer.DeserializeFromFile<TaskResult>(path);
try
{
return JsonSerializer.DeserializeFromFile<TaskResult>(path);
}
catch (DirectoryNotFoundException)
{
// File doesn't exist. No biggie
}
catch (FileNotFoundException)
{
// File doesn't exist. No biggie
}
catch (Exception ex)
{
Logger.ErrorException("Error deserializing {0}", ex, path);
}
}
}
catch (DirectoryNotFoundException)
{
// File doesn't exist. No biggie
return null;
}
catch (FileNotFoundException)
{
// File doesn't exist. No biggie
return null;
}
catch (Exception ex)
{
Logger.ErrorException("Error deserializing {0}", ex, path);
return null;
}
});
}
return _lastExecutionResult;
}
private set
{
_lastExecutionResult = value;
_lastExecutionResultinitialized = value != null;
}
}
@ -227,13 +224,9 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
/// </summary>
private IEnumerable<ITaskTrigger> _triggers;
/// <summary>
/// The _triggers initialized
/// </summary>
private bool _triggersInitialized;
/// <summary>
/// The _triggers sync lock
/// </summary>
private object _triggersSyncLock = new object();
private readonly object _triggersSyncLock = new object();
/// <summary>
/// Gets the triggers that define when the task will run
/// </summary>
@ -243,7 +236,16 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
{
get
{
LazyInitializer.EnsureInitialized(ref _triggers, ref _triggersInitialized, ref _triggersSyncLock, LoadTriggers);
if (_triggers == null)
{
lock (_triggersSyncLock)
{
if (_triggers == null)
{
_triggers = LoadTriggers();
}
}
}
return _triggers;
}
@ -262,8 +264,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
_triggers = value.ToList();
_triggersInitialized = true;
ReloadTriggerEvents(false);
SaveTriggers(_triggers);

View File

@ -0,0 +1,10 @@
using MediaBrowser.Model.Devices;
namespace MediaBrowser.Controller.Devices
{
public class CameraImageUploadInfo
{
public LocalFileInfo FileInfo { get; set; }
public DeviceInfo Device { get; set; }
}
}

View File

@ -3,7 +3,6 @@ using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
@ -15,6 +14,10 @@ namespace MediaBrowser.Controller.Devices
/// Occurs when [device options updated].
/// </summary>
event EventHandler<GenericEventArgs<DeviceInfo>> DeviceOptionsUpdated;
/// <summary>
/// Occurs when [camera image uploaded].
/// </summary>
event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
/// <summary>
/// Registers the device.

View File

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities
{
[Obsolete]
public class AdultVideo : Video, IHasProductionLocations, IHasTaglines
{
public List<string> ProductionLocations { get; set; }
public List<string> Taglines { get; set; }
public AdultVideo()
{
Taglines = new List<string>();
ProductionLocations = new List<string>();
}
}
}

View File

@ -1,11 +1,11 @@
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Users;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using MediaBrowser.Model.Users;
namespace MediaBrowser.Controller.Entities.Audio
{
@ -181,10 +181,4 @@ namespace MediaBrowser.Controller.Entities.Audio
return id;
}
}
[Obsolete]
public class MusicAlbumDisc : Folder
{
}
}

View File

@ -129,44 +129,21 @@ namespace MediaBrowser.Controller.Entities.Audio
var others = items.Except(songs).ToList();
var totalItems = songs.Count + others.Count;
var percentages = new Dictionary<Guid, double>(totalItems);
var tasks = new List<Task>();
var numComplete = 0;
// Refresh songs
foreach (var item in songs)
{
if (tasks.Count >= 2)
{
await Task.WhenAll(tasks).ConfigureAwait(false);
tasks.Clear();
}
cancellationToken.ThrowIfCancellationRequested();
var innerProgress = new ActionableProgress<double>();
// Avoid implicitly captured closure
var currentChild = item;
innerProgress.RegisterAction(p =>
{
lock (percentages)
{
percentages[currentChild.Id] = p / 100;
await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
var percent = percentages.Values.Sum();
percent /= totalItems;
percent *= 100;
progress.Report(percent);
}
});
var taskChild = item;
tasks.Add(Task.Run(async () => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken));
numComplete++;
double percent = numComplete;
percent /= totalItems;
progress.Report(percent * 100);
}
await Task.WhenAll(tasks).ConfigureAwait(false);
tasks.Clear();
// Refresh current item
await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
@ -175,31 +152,17 @@ namespace MediaBrowser.Controller.Entities.Audio
{
cancellationToken.ThrowIfCancellationRequested();
// Avoid implicitly captured closure
var currentChild = item;
await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
lock (percentages)
{
percentages[currentChild.Id] = 1;
var percent = percentages.Values.Sum();
percent /= totalItems;
percent *= 100;
progress.Report(percent);
}
numComplete++;
double percent = numComplete;
percent /= totalItems;
progress.Report(percent * 100);
}
progress.Report(100);
}
private async Task RefreshItem(BaseItem item, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
{
await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
progress.Report(100);
}
public ArtistInfo GetLookupInfo()
{
var info = GetItemLookupInfo<ArtistInfo>();

View File

@ -283,7 +283,17 @@ namespace MediaBrowser.Controller.Entities
{
get
{
return _children ?? (_children = LoadChildrenInternal());
if (_children == null)
{
lock (_childrenSyncLock)
{
if (_children == null)
{
_children = LoadChildrenInternal();
}
}
}
return _children;
}
}
@ -749,28 +759,6 @@ namespace MediaBrowser.Controller.Entities
return childrenItems;
}
/// <summary>
/// Retrieves the child.
/// </summary>
/// <param name="child">The child.</param>
/// <returns>BaseItem.</returns>
private BaseItem RetrieveChild(Guid child)
{
var item = LibraryManager.GetItemById(child);
if (item != null)
{
if (item is IByReferenceItem)
{
return LibraryManager.GetOrAddByReferenceItem(item);
}
item.Parent = this;
}
return item;
}
private BaseItem RetrieveChild(BaseItem child)
{
if (child.Id == Guid.Empty)

View File

@ -143,31 +143,19 @@ namespace MediaBrowser.Controller.Entities.Movies
var items = GetRecursiveChildren().ToList();
var totalItems = items.Count;
var percentages = new Dictionary<Guid, double>(totalItems);
var numComplete = 0;
// Refresh songs
foreach (var item in items)
{
cancellationToken.ThrowIfCancellationRequested();
var innerProgress = new ActionableProgress<double>();
// Avoid implicitly captured closure
var currentChild = item;
innerProgress.RegisterAction(p =>
{
lock (percentages)
{
percentages[currentChild.Id] = p / 100;
await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
var percent = percentages.Values.Sum();
percent /= totalItems;
percent *= 100;
progress.Report(percent);
}
});
// Avoid implicitly captured closure
await RefreshItem(item, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
numComplete++;
double percent = numComplete;
percent /= totalItems;
progress.Report(percent * 100);
}
// Refresh current item
@ -176,13 +164,6 @@ namespace MediaBrowser.Controller.Entities.Movies
progress.Report(100);
}
private async Task RefreshItem(BaseItem item, MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
{
await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
progress.Report(100);
}
public override bool IsVisible(User user)
{
if (base.IsVisible(user))

View File

@ -64,8 +64,7 @@ namespace MediaBrowser.Controller.Entities
{
CollectionType.Books,
CollectionType.HomeVideos,
CollectionType.Photos,
string.Empty
CollectionType.Photos
};
var collectionFolder = folder as ICollectionFolder;

View File

@ -409,12 +409,21 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetMusicLatest(Folder parent, User user, InternalItemsQuery query)
{
query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
query.SortOrder = SortOrder.Descending;
var items = _userViewManager.GetLatestItems(new LatestItemsQuery
{
UserId = user.Id.ToString("N"),
Limit = GetSpecialItemsLimit(),
IncludeItemTypes = new[] { typeof(Audio.Audio).Name },
ParentId = (parent == null ? null : parent.Id.ToString("N")),
GroupItems = true
var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }, i => i is MusicVideo || i is Audio.Audio && FilterItem(i, query));
}).Select(i => i.Item1);
return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
query.SortBy = new string[] { };
//var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }, i => i is MusicVideo || i is Audio.Audio && FilterItem(i, query));
return PostFilterAndSort(items, parent, null, query);
}
private async Task<QueryResult<BaseItem>> GetMovieFolders(Folder parent, User user, InternalItemsQuery query)
@ -741,7 +750,7 @@ namespace MediaBrowser.Controller.Entities
private async Task<QueryResult<BaseItem>> GetGameGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
{
var items = GetRecursiveChildren(queryParent, user, new[] {CollectionType.Games},
var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Games },
i => i is Game && i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
return GetResult(items, queryParent, query);
@ -1686,7 +1695,7 @@ namespace MediaBrowser.Controller.Entities
return parent.GetRecursiveChildren(user);
}
private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, IEnumerable<string> viewTypes, Func<BaseItem,bool> filter)
private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, IEnumerable<string> viewTypes, Func<BaseItem, bool> filter)
{
if (parent == null || parent is UserView)
{

View File

@ -1,6 +1,5 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Playlists;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Library
@ -13,7 +12,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="item">The item.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{Audio}.</returns>
IEnumerable<Audio> GetInstantMixFromSong(Audio item, User user);
IEnumerable<Audio> GetInstantMixFromItem(BaseItem item, User user);
/// <summary>
/// Gets the instant mix from artist.
/// </summary>
@ -22,20 +21,6 @@ namespace MediaBrowser.Controller.Library
/// <returns>IEnumerable{Audio}.</returns>
IEnumerable<Audio> GetInstantMixFromArtist(string name, User user);
/// <summary>
/// Gets the instant mix from album.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable{Audio}.</returns>
IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user);
/// <summary>
/// Gets the instant mix from playlist.
/// </summary>
/// <param name="item">The item.</param>
/// <param name="user">The user.</param>
/// <returns>IEnumerable&lt;Audio&gt;.</returns>
IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user);
/// <summary>
/// Gets the instant mix from genre.
/// </summary>
/// <param name="genres">The genres.</param>

View File

@ -1,5 +1,7 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -16,5 +18,7 @@ namespace MediaBrowser.Controller.Library
Task<UserView> GetUserView(string type, string sortName, CancellationToken cancellationToken);
Task<UserView> GetUserView(string category, string type, User user, string sortName, CancellationToken cancellationToken);
List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request);
}
}

View File

@ -101,6 +101,7 @@
<Compile Include="Collections\ICollectionManager.cs" />
<Compile Include="Connect\IConnectManager.cs" />
<Compile Include="Connect\UserLinkResult.cs" />
<Compile Include="Devices\CameraImageUploadInfo.cs" />
<Compile Include="Devices\IDeviceManager.cs" />
<Compile Include="Devices\IDeviceRepository.cs" />
<Compile Include="Dlna\ControlRequest.cs" />
@ -117,7 +118,6 @@
<Compile Include="Drawing\ImageStream.cs" />
<Compile Include="Dto\DtoOptions.cs" />
<Compile Include="Dto\IDtoService.cs" />
<Compile Include="Entities\AdultVideo.cs" />
<Compile Include="Entities\Audio\IHasAlbumArtist.cs" />
<Compile Include="Entities\Audio\IHasMusicGenres.cs" />
<Compile Include="Entities\Book.cs" />

View File

@ -113,38 +113,17 @@ namespace MediaBrowser.Controller.Playlists
return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending);
}
// Grab these explicitly to avoid the sorting that will happen below
var collection = item as BoxSet;
if (collection != null)
{
var items = user == null
? collection.Children
: collection.GetChildren(user, true);
return items
.Where(m => !m.IsFolder);
}
// Grab these explicitly to avoid the sorting that will happen below
var season = item as Season;
if (season != null)
{
var items = user == null
? season.Children
: season.GetChildren(user, true);
return items
.Where(m => !m.IsFolder);
}
var folder = item as Folder;
if (folder != null)
{
var items = user == null
? folder.GetRecursiveChildren(m => !m.IsFolder)
: folder.GetRecursiveChildren(user, m => !m.IsFolder);
if (folder.IsPreSorted)
{
return items;
}
return LibraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending);
}

View File

@ -168,7 +168,7 @@ namespace MediaBrowser.Model.Configuration
/// <value>The dashboard source path.</value>
public string DashboardSourcePath { get; set; }
public bool StoreArtistsInMetadata { get; set; }
public bool MergeMetadataAndImagesByName { get; set; }
public bool EnableStandaloneMetadata { get; set; }
/// <summary>

View File

@ -18,6 +18,7 @@ namespace MediaBrowser.Model.Notifications
NewLibraryContent,
NewLibraryContentMultiple,
ServerRestartRequired,
TaskFailed
TaskFailed,
CameraImageUploaded
}
}

View File

@ -88,9 +88,12 @@ namespace MediaBrowser.Server.Implementations.Configuration
/// </summary>
private void UpdateItemsByNamePath()
{
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ?
null :
Configuration.ItemsByNamePath;
if (!Configuration.MergeMetadataAndImagesByName)
{
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = string.IsNullOrEmpty(Configuration.ItemsByNamePath) ?
null :
Configuration.ItemsByNamePath;
}
}
/// <summary>
@ -101,6 +104,11 @@ namespace MediaBrowser.Server.Implementations.Configuration
((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrEmpty(Configuration.MetadataPath) ?
GetInternalMetadataPath() :
Configuration.MetadataPath;
if (Configuration.MergeMetadataAndImagesByName)
{
((ServerApplicationPaths)ApplicationPaths).ItemsByNamePath = ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath;
}
}
private string GetInternalMetadataPath()

View File

@ -27,6 +27,8 @@ namespace MediaBrowser.Server.Implementations.Devices
private readonly IConfigurationManager _config;
private readonly ILogger _logger;
public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
/// <summary>
/// Occurs when [device options updated].
/// </summary>
@ -116,7 +118,7 @@ namespace MediaBrowser.Server.Implementations.Devices
{
devices = devices.Where(i => CanAccessDevice(query.UserId, i.Id));
}
var array = devices.ToArray();
return new QueryResult<DeviceInfo>
{
@ -137,7 +139,8 @@ namespace MediaBrowser.Server.Implementations.Devices
public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file)
{
var path = GetUploadPath(deviceId);
var device = GetDevice(deviceId);
var path = GetUploadPath(device);
if (!string.IsNullOrWhiteSpace(file.Album))
{
@ -163,11 +166,27 @@ namespace MediaBrowser.Server.Implementations.Devices
{
_libraryMonitor.ReportFileSystemChangeComplete(path, true);
}
if (CameraImageUploaded != null)
{
EventHelper.FireEventIfNotNull(CameraImageUploaded, this, new GenericEventArgs<CameraImageUploadInfo>
{
Argument = new CameraImageUploadInfo
{
Device = device,
FileInfo = file
}
}, _logger);
}
}
private string GetUploadPath(string deviceId)
{
var device = GetDevice(deviceId);
return GetUploadPath(GetDevice(deviceId));
}
private string GetUploadPath(DeviceInfo device)
{
if (!string.IsNullOrWhiteSpace(device.CameraUploadPath))
{
return device.CameraUploadPath;

View File

@ -3,6 +3,7 @@ using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.ScheduledTasks;
using MediaBrowser.Common.Updates;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
@ -44,8 +45,9 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
private readonly object _libraryChangedSyncLock = new object();
private readonly IConfigurationManager _config;
private readonly IDeviceManager _deviceManager;
public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config)
public Notifications(IInstallationManager installationManager, IUserManager userManager, ILogger logger, ITaskManager taskManager, INotificationManager notificationManager, ILibraryManager libraryManager, ISessionManager sessionManager, IServerApplicationHost appHost, IConfigurationManager config, IDeviceManager deviceManager)
{
_installationManager = installationManager;
_userManager = userManager;
@ -56,6 +58,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
_sessionManager = sessionManager;
_appHost = appHost;
_config = config;
_deviceManager = deviceManager;
}
public void Run()
@ -74,6 +77,21 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
_appHost.HasPendingRestartChanged += _appHost_HasPendingRestartChanged;
_appHost.HasUpdateAvailableChanged += _appHost_HasUpdateAvailableChanged;
_appHost.ApplicationUpdated += _appHost_ApplicationUpdated;
_deviceManager.CameraImageUploaded +=_deviceManager_CameraImageUploaded;
}
async void _deviceManager_CameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
{
var type = NotificationType.CameraImageUploaded.ToString();
var notification = new NotificationRequest
{
NotificationType = type
};
notification.Variables["DeviceName"] = e.Argument.Device.Name;
await SendNotification(notification).ConfigureAwait(false);
}
async void _appHost_ApplicationUpdated(object sender, GenericEventArgs<PackageVersionInfo> e)
@ -451,6 +469,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
_appHost.HasPendingRestartChanged -= _appHost_HasPendingRestartChanged;
_appHost.HasUpdateAvailableChanged -= _appHost_HasUpdateAvailableChanged;
_appHost.ApplicationUpdated -= _appHost_ApplicationUpdated;
_deviceManager.CameraImageUploaded -= _deviceManager_CameraImageUploaded;
}
private void DisposeLibraryUpdateTimer()

View File

@ -566,7 +566,10 @@ namespace MediaBrowser.Server.Implementations.IO
.Distinct()
.ToList();
foreach (var p in paths) Logger.Info(p + " reports change.");
foreach (var p in paths)
{
Logger.Info(p + " reports change.");
}
// If the root folder changed, run the library task so the user can see it
if (itemsToRefresh.Any(i => i is AggregateFolder))

View File

@ -219,11 +219,7 @@ namespace MediaBrowser.Server.Implementations.Library
/// <summary>
/// The _root folder sync lock
/// </summary>
private object _rootFolderSyncLock = new object();
/// <summary>
/// The _root folder initialized
/// </summary>
private bool _rootFolderInitialized;
private readonly object _rootFolderSyncLock = new object();
/// <summary>
/// Gets the root folder.
/// </summary>
@ -232,17 +228,17 @@ namespace MediaBrowser.Server.Implementations.Library
{
get
{
LazyInitializer.EnsureInitialized(ref _rootFolder, ref _rootFolderInitialized, ref _rootFolderSyncLock, CreateRootFolder);
return _rootFolder;
}
private set
{
_rootFolder = value;
if (value == null)
if (_rootFolder == null)
{
_rootFolderInitialized = false;
lock (_rootFolderSyncLock)
{
if (_rootFolder == null)
{
_rootFolder = CreateRootFolder();
}
}
}
return _rootFolder;
}
}
@ -849,11 +845,6 @@ namespace MediaBrowser.Server.Implementations.Library
{
get
{
if (ConfigurationManager.Configuration.StoreArtistsInMetadata)
{
return Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "artists");
}
return Path.Combine(ConfigurationManager.ApplicationPaths.ItemsByNamePath, "artists");
}
}

View File

@ -83,5 +83,40 @@ namespace MediaBrowser.Server.Implementations.Library
.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.Name, user);
}
var song = item as Audio;
if (song != null)
{
return GetInstantMixFromSong(song, user);
}
return new Audio[] { };
}
}
}

View File

@ -16,6 +16,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MoreLinq;
namespace MediaBrowser.Server.Implementations.Library
{
@ -56,7 +57,9 @@ namespace MediaBrowser.Server.Implementations.Library
var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
var standaloneFolders = folders.Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id)).ToList();
var standaloneFolders = folders
.Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id))
.ToList();
var foldersWithViewTypes = folders
.Except(standaloneFolders)
@ -164,5 +167,141 @@ namespace MediaBrowser.Server.Implementations.Library
return _libraryManager.GetNamedView(name, type, sortName, cancellationToken);
}
public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)
{
var user = _userManager.GetUserById(request.UserId);
var includeTypes = request.IncludeItemTypes;
var currentUser = user;
Func<BaseItem, bool> filter = i =>
{
if (includeTypes.Length > 0)
{
if (!includeTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
{
return false;
}
}
if (request.IsPlayed.HasValue)
{
var val = request.IsPlayed.Value;
if (i.IsPlayed(currentUser) != val)
{
return false;
}
}
return i.LocationType != LocationType.Virtual && !i.IsFolder;
};
// Avoid implicitly captured closure
var libraryItems = string.IsNullOrEmpty(request.ParentId) && user != null ?
GetItemsConfiguredForLatest(user, filter) :
GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, filter);
libraryItems = libraryItems.OrderByDescending(i => i.DateCreated);
if (request.IsPlayed.HasValue)
{
var takeLimit = (request.Limit ?? 20) * 20;
libraryItems = libraryItems.Take(takeLimit);
}
// Avoid implicitly captured closure
var items = libraryItems
.ToList();
var list = new List<Tuple<BaseItem, List<BaseItem>>>();
foreach (var item in items)
{
// 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;
}
protected IList<BaseItem> GetAllLibraryItems(string userId, IUserManager userManager, ILibraryManager libraryManager, string parentId, Func<BaseItem, bool> filter)
{
if (!string.IsNullOrEmpty(parentId))
{
var folder = (Folder)libraryManager.GetItemById(new Guid(parentId));
if (!string.IsNullOrWhiteSpace(userId))
{
var user = userManager.GetUserById(userId);
if (user == null)
{
throw new ArgumentException("User not found");
}
return folder
.GetRecursiveChildren(user, filter)
.ToList();
}
return folder
.GetRecursiveChildren(filter);
}
if (!string.IsNullOrWhiteSpace(userId))
{
var user = userManager.GetUserById(userId);
if (user == null)
{
throw new ArgumentException("User not found");
}
return user
.RootFolder
.GetRecursiveChildren(user, filter)
.ToList();
}
return libraryManager
.RootFolder
.GetRecursiveChildren(filter);
}
private IEnumerable<BaseItem> GetItemsConfiguredForLatest(User user, Func<BaseItem, bool> filter)
{
// Avoid implicitly captured closure
var currentUser = user;
return user.RootFolder.GetChildren(user, true)
.OfType<Folder>()
.Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N")))
.SelectMany(i => i.GetRecursiveChildren(currentUser, filter))
.DistinctBy(i => i.Id);
}
}
}

View File

@ -55,6 +55,7 @@
"HeaderAudio": "Audio",
"HeaderVideo": "Video",
"HeaderPaths": "Paths",
"CategorySync": "Sync",
"HeaderSyncRequiresSupporterMembership": "Sync Requires a Supporter Membership",
"HeaderEnjoyDayTrial": "Enjoy a 14 Day Free Trial",
"LabelSyncTempPath": "Temporary file path:",
@ -669,6 +670,7 @@
"NotificationOptionInstallationFailed": "Installation failure",
"NotificationOptionNewLibraryContent": "New content added",
"NotificationOptionNewLibraryContentMultiple": "New content added (multiple)",
"NotificationOptionCameraImageUploaded": "Camera image uploaded",
"SendNotificationHelp": "By default, notifications are delivered to the dashboard inbox. Browse the plugin catalog to install additional notification options.",
"NotificationOptionServerRestartRequired": "Server restart required",
"LabelNotificationEnabled": "Enable this notification",
@ -893,7 +895,7 @@
"OptionCommunityMostWatchedSort": "Most Watched",
"TabNextUp": "Next Up",
"HeaderBecomeMediaBrowserSupporter": "Become a Media Browser Supporter",
"TextAccessPremiumFeatures": "Enjoy Premium Features",
"TextEnjoyBonusFeatures": "Enjoy Bonus Features",
"MessageNoMovieSuggestionsAvailable": "No movie suggestions are currently available. Start watching and rating your movies, and then come back to view your recommendations.",
"MessageNoCollectionsAvailable": "Collections allow you to enjoy personalized groupings of Movies, Series, Albums, Books and Games. Click the + button to start creating Collections.",
"MessageNoPlaylistsAvailable": "Playlists allow you to create lists of content to play consecutively at a time. To add items to playlists, right click or tap and hold, then select Add to Playlist.",
@ -957,7 +959,7 @@
"OptionLatestTvRecordings": "Latest recordings",
"LabelProtocolInfo": "Protocol info:",
"LabelProtocolInfoHelp": "The value that will be used when responding to GetProtocolInfo requests from the device.",
"TabKodiMetadata": "Kodi",
"TabNfo": "Nfo",
"HeaderKodiMetadataHelp": "Media Browser includes native support for Kodi Nfo metadata and images. To enable or disable Kodi metadata, use the Advanced tab to configure options for your media types.",
"LabelKodiMetadataUser": "Sync user watch data to nfo's for:",
"LabelKodiMetadataUserHelp": "Enable this to keep watch data in sync between Media Browser and Kodi.",

View File

@ -1,7 +1,6 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Localization;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Notifications;
using System;
using System.Collections.Generic;
@ -137,6 +136,13 @@ namespace MediaBrowser.Server.Implementations.Notifications
Type = NotificationType.VideoPlaybackStopped.ToString(),
DefaultTitle = "{UserName} has finished playing {ItemName} on {DeviceName}.",
Variables = new List<string>{"UserName", "ItemName", "DeviceName", "AppName"}
},
new NotificationTypeInfo
{
Type = NotificationType.CameraImageUploaded.ToString(),
DefaultTitle = "A new camera image has been uploaded from {DeviceName}.",
Variables = new List<string>{"DeviceName"}
}
};
@ -171,10 +177,14 @@ namespace MediaBrowser.Server.Implementations.Notifications
{
note.Category = _localization.GetLocalizedString("CategoryUser");
}
else if (note.Type.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) != -1)
else if (note.Type.IndexOf("Plugin", StringComparison.OrdinalIgnoreCase) != -1)
{
note.Category = _localization.GetLocalizedString("CategoryPlugin");
}
else if (note.Type.IndexOf("CameraImageUploaded", StringComparison.OrdinalIgnoreCase) != -1)
{
note.Category = _localization.GetLocalizedString("CategorySync");
}
else
{
note.Category = _localization.GetLocalizedString("CategorySystem");

View File

@ -870,14 +870,14 @@ namespace MediaBrowser.Server.Implementations.Session
{
if (items.Any(i => !session.QueueableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
{
throw new ArgumentException(string.Format("{0} is unable to queue the requested media type.", session.DeviceName ?? session.Id.ToString()));
throw new ArgumentException(string.Format("{0} is unable to queue the requested media type.", session.DeviceName ?? session.Id));
}
}
else
{
if (items.Any(i => !session.PlayableMediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
{
throw new ArgumentException(string.Format("{0} is unable to play the requested media type.", session.DeviceName ?? session.Id.ToString()));
throw new ArgumentException(string.Format("{0} is unable to play the requested media type.", session.DeviceName ?? session.Id));
}
}
@ -895,6 +895,19 @@ namespace MediaBrowser.Server.Implementations.Session
{
var item = _libraryManager.GetItemById(new Guid(id));
var byName = item as IItemByName;
if (byName != null)
{
var items = user == null ?
_libraryManager.RootFolder.GetRecursiveChildren(i => !i.IsFolder && byName.ItemFilter(i)) :
user.RootFolder.GetRecursiveChildren(user, i => !i.IsFolder && byName.ItemFilter(i));
items = items.OrderBy(i => i.SortName);
return items;
}
if (item.IsFolder)
{
var folder = (Folder)item;
@ -913,37 +926,9 @@ namespace MediaBrowser.Server.Implementations.Session
private IEnumerable<BaseItem> TranslateItemForInstantMix(string id, User user)
{
var item = _libraryManager.GetItemById(new Guid(id));
var item = _libraryManager.GetItemById(id);
var audio = item as Audio;
if (audio != null)
{
return _musicManager.GetInstantMixFromSong(audio, user);
}
var artist = item as MusicArtist;
if (artist != null)
{
return _musicManager.GetInstantMixFromArtist(artist.Name, user);
}
var album = item as MusicAlbum;
if (album != null)
{
return _musicManager.GetInstantMixFromAlbum(album, user);
}
var genre = item as MusicGenre;
if (genre != null)
{
return _musicManager.GetInstantMixFromGenres(new[] { genre.Name }, user);
}
return new BaseItem[] { };
return _musicManager.GetInstantMixFromItem(item, user);
}
public Task SendBrowseCommand(string controllingSessionId, string sessionId, BrowseRequest command, CancellationToken cancellationToken)

View File

@ -93,7 +93,8 @@ namespace MediaBrowser.Server.Implementations.TV
return FilterSeries(request, series)
.AsParallel()
.Select(i => GetNextUp(i, currentUser))
.Where(i => i.Item1 != null)
// Include if an episode was found, and either the series is not unwatched or the specific series was requested
.Where(i => i.Item1 != null && (!i.Item3 || !string.IsNullOrWhiteSpace(request.SeriesId)))
.OrderByDescending(i =>
{
var episode = i.Item1;
@ -123,7 +124,7 @@ namespace MediaBrowser.Server.Implementations.TV
/// <param name="series">The series.</param>
/// <param name="user">The user.</param>
/// <returns>Task{Episode}.</returns>
private Tuple<Episode, DateTime> GetNextUp(Series series, User user)
private Tuple<Episode, DateTime, bool> GetNextUp(Series series, User user)
{
// Get them in display order, then reverse
var allEpisodes = series.GetSeasons(user, true, true)
@ -162,13 +163,13 @@ namespace MediaBrowser.Server.Implementations.TV
if (lastWatched != null)
{
return new Tuple<Episode, DateTime>(nextUp, lastWatchedDate);
return new Tuple<Episode, DateTime, bool>(nextUp, lastWatchedDate, false);
}
var firstEpisode = allEpisodes.LastOrDefault(i => i.LocationType != LocationType.Virtual && !i.IsPlayed(user));
// Return the first episode
return new Tuple<Episode, DateTime>(firstEpisode, DateTime.MinValue);
return new Tuple<Episode, DateTime, bool>(firstEpisode, DateTime.MinValue, true);
}
private IEnumerable<Series> FilterSeries(NextUpQuery request, IEnumerable<Series> items)

View File

@ -441,7 +441,7 @@ namespace MediaBrowser.WebDashboard.Api
"metadataconfigurationpage.js",
"metadataimagespage.js",
"metadatasubtitles.js",
"metadatakodi.js",
"metadatanfo.js",
"moviegenres.js",
"moviecollections.js",
"movies.js",

View File

@ -480,7 +480,7 @@
<Content Include="dashboard-ui\librarypathmapping.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\metadatakodi.html">
<Content Include="dashboard-ui\metadatanfo.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\mypreferencesdisplay.html">
@ -795,7 +795,7 @@
<Content Include="dashboard-ui\scripts\librarypathmapping.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\metadatakodi.js">
<Content Include="dashboard-ui\scripts\metadatanfo.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\mypreferencesdisplay.js">