aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CONTRIBUTORS.md32
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs7
-rw-r--r--MediaBrowser.Api/PackageService.cs2
-rw-r--r--MediaBrowser.Api/SessionsService.cs146
-rw-r--r--MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs40
-rw-r--r--MediaBrowser.Api/UserLibrary/UserLibraryService.cs136
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs12
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs12
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs1
-rw-r--r--MediaBrowser.Controller/Dto/DtoBuilder.cs10
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs6
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs25
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs44
-rw-r--r--MediaBrowser.Controller/IO/FileData.cs6
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs36
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj6
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj6
-rw-r--r--MediaBrowser.Model/ApiClient/IApiClient.cs24
-rw-r--r--MediaBrowser.Model/Entities/MetadataProviders.cs3
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj2
-rw-r--r--MediaBrowser.Model/Session/MessageCommand.cs12
-rw-r--r--MediaBrowser.Model/Session/SystemCommand.cs14
-rw-r--r--MediaBrowser.Providers/Movies/MovieDbProvider.cs308
-rw-r--r--MediaBrowser.Providers/Movies/MovieProviderFromXml.cs11
-rw-r--r--MediaBrowser.Providers/Music/FanArtAlbumProvider.cs15
-rw-r--r--MediaBrowser.Providers/Savers/MovieXmlSaver.cs11
-rw-r--r--MediaBrowser.Providers/Savers/XmlSaverHelpers.cs11
-rw-r--r--MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs50
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs13
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj2
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs103
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf9
-rw-r--r--MediaBrowser.ServerApplication/ApplicationHost.cs2
-rw-r--r--MediaBrowser.WebDashboard/ApiClient.js102
-rw-r--r--MediaBrowser.WebDashboard/packages.config2
-rw-r--r--MediaBrowser.sln3
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec4
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
-rw-r--r--README.md2
41 files changed, 705 insertions, 533 deletions
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index da838823e..cd9ed9ef3 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -15,4 +15,34 @@
- [sfnetwork](https://github.com/sfnetwork)
- [Logos302] (https://github.com/Logos302)
- [TheWorkz](https://github.com/TheWorkz)
- - [madFloyd] (https://github.com/madFloyd)
+ - [mboehler](https://github.com/mboehler)
+ - [KaHooli](https://github.com/KaHooli)
+ - [xzener](https://github.com/xzener)
+ - [CBers] (https://github.com/CBers)
+ - [Sagaia] (https://github.com/Sagaia)
+ - [JHawk111](https://github.com/JHawk111)
+ - [David3663](https://github.com/david3663)
+ - [Smyken](https://github.com/Smyken)
+ - [doron1] (https://github.com/doron1)
+ - [brainfryd] (https://github.com/brainfryd)
+ - [DGMayor] (http://github.com/DGMayor)
+ - [Jon-theHTPC] (https://github.com/Jon-theHTPC)
+ - [aspdend] (https://github.com/aspdend)
+ - [RedshirtMB](https://github.com/RedshirtMB)
+ - [thealienamongus](https://github.com/thealienamongus)
+ - [brocass](https://github.com/brocass)
+ - [pjrollo2000](https://github.com/pjrollo2000)
+ - [abobader](https://github.com/abobader)
+ - [milli260876](https://github.com/milli260876)
+ - [vileboy] (https://github.com/vileboy)
+ - [starkadius] (https://github.com/starkadius)
+ - [wraslor](https://github.com/wraslor)
+ - [mrwebsmith](https://github.com/mrwebsmith)
+ - [rickster53](https://github.com/rickster53)
+ - [Tharnax] (https://github.com/Tharnax)
+ - [0sm0] (https://github.com/0sm0)
+ - [swhitmore](https://github.com/swhitmore)
+ - [DigiTM](https://github.com/DigiTM)
+ - [crisliv / xliv](https://github.com/crisliv)
+ - [Yogi](https://github.com/yogi12)
+ - [madFloyd] (https://github.com/madFloyd) \ No newline at end of file
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 54b2c0ea7..09e175e30 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -273,13 +273,6 @@ namespace MediaBrowser.Api
song.Artist = request.Artists[0];
}
- var musicAlbum = item as MusicAlbum;
-
- if (musicAlbum != null)
- {
- musicAlbum.MusicBrainzReleaseGroupId = request.GetProviderId("MusicBrainzReleaseGroupId");
- }
-
var musicVideo = item as MusicVideo;
if (musicVideo != null)
diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs
index 7e6444e16..563625a54 100644
--- a/MediaBrowser.Api/PackageService.cs
+++ b/MediaBrowser.Api/PackageService.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.Api
[ApiMember(Name = "TargetSystems", Description = "Optional. Filter by target system type. Allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET", AllowMultiple = true)]
public string TargetSystems { get; set; }
- [ApiMember(Name = "IsPremium", Description = "Optiona. Filter by premium status", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+ [ApiMember(Name = "IsPremium", Description = "Optional. Filter by premium status", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? IsPremium { get; set; }
}
diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs
index 4554b35fa..bd05c3250 100644
--- a/MediaBrowser.Api/SessionsService.cs
+++ b/MediaBrowser.Api/SessionsService.cs
@@ -130,6 +130,46 @@ namespace MediaBrowser.Api
public PlaystateCommand Command { get; set; }
}
+ [Route("/Sessions/{Id}/System/{Command}", "POST")]
+ [Api(("Issues a system command to a client"))]
+ public class SendSystemCommand : IReturnVoid
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public Guid Id { get; set; }
+
+ /// <summary>
+ /// Gets or sets the command.
+ /// </summary>
+ /// <value>The play command.</value>
+ [ApiMember(Name = "Command", Description = "The command to send.", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public SystemCommand Command { get; set; }
+ }
+
+ [Route("/Sessions/{Id}/Message", "POST")]
+ [Api(("Issues a command to a client to display a message to the user"))]
+ public class SendMessageCommand : IReturnVoid
+ {
+ /// <summary>
+ /// Gets or sets the id.
+ /// </summary>
+ /// <value>The id.</value>
+ [ApiMember(Name = "Id", Description = "Session Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+ public Guid Id { get; set; }
+
+ [ApiMember(Name = "Text", Description = "The message text.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string Text { get; set; }
+
+ [ApiMember(Name = "Header", Description = "The message header.", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public string Header { get; set; }
+
+ [ApiMember(Name = "TimeoutMs", Description = "The message timeout. If omitted the user will have to confirm viewing the message.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
+ public long? TimeoutMs { get; set; }
+ }
+
/// <summary>
/// Class SessionsService
/// </summary>
@@ -215,7 +255,7 @@ namespace MediaBrowser.Api
throw new InvalidOperationException("The requested session does not have an open web socket.");
}
}
-
+
/// <summary>
/// Posts the specified request.
/// </summary>
@@ -277,6 +317,110 @@ namespace MediaBrowser.Api
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
+ public void Post(SendSystemCommand request)
+ {
+ var task = SendSystemCommand(request);
+
+ Task.WaitAll(task);
+ }
+
+ private async Task SendSystemCommand(SendSystemCommand request)
+ {
+ var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
+
+ if (session == null)
+ {
+ throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
+ }
+
+ if (!session.SupportsRemoteControl)
+ {
+ throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
+ }
+
+ var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+
+ if (socket != null)
+ {
+ try
+ {
+ await socket.SendAsync(new WebSocketMessage<string>
+ {
+ MessageType = "SystemCommand",
+ Data = request.Command.ToString()
+
+ }, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error sending web socket message", ex);
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException("The requested session does not have an open web socket.");
+ }
+ }
+
+ /// <summary>
+ /// Posts the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
+ public void Post(SendMessageCommand request)
+ {
+ var task = SendMessageCommand(request);
+
+ Task.WaitAll(task);
+ }
+
+ private async Task SendMessageCommand(SendMessageCommand request)
+ {
+ var session = _sessionManager.Sessions.FirstOrDefault(i => i.Id == request.Id);
+
+ if (session == null)
+ {
+ throw new ResourceNotFoundException(string.Format("Session {0} not found.", request.Id));
+ }
+
+ if (!session.SupportsRemoteControl)
+ {
+ throw new ArgumentException(string.Format("Session {0} does not support remote control.", session.Id));
+ }
+
+ var socket = session.WebSockets.OrderByDescending(i => i.LastActivityDate).FirstOrDefault(i => i.State == WebSocketState.Open);
+
+ if (socket != null)
+ {
+ try
+ {
+ await socket.SendAsync(new WebSocketMessage<MessageCommand>
+ {
+ MessageType = "MessageCommand",
+
+ Data = new MessageCommand
+ {
+ Header = string.IsNullOrEmpty(request.Header) ? "Message from Server" : request.Header,
+ TimeoutMs = request.TimeoutMs,
+ Text = request.Text
+ }
+
+ }, CancellationToken.None).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error sending web socket message", ex);
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException("The requested session does not have an open web socket.");
+ }
+ }
+
+ /// <summary>
+ /// Posts the specified request.
+ /// </summary>
+ /// <param name="request">The request.</param>
public void Post(Play request)
{
var task = Play(request);
diff --git a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs
index 4196db497..1e990b3e9 100644
--- a/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs
+++ b/MediaBrowser.Api/UserLibrary/ItemByNameUserDataService.cs
@@ -1,5 +1,7 @@
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Dto;
using ServiceStack.ServiceHost;
using ServiceStack.Text.Controller;
using System;
@@ -18,7 +20,7 @@ namespace MediaBrowser.Api.UserLibrary
[Route("/Users/{UserId}/Favorites/MusicGenres/{Name}", "POST")]
[Route("/Users/{UserId}/Favorites/GameGenres/{Name}", "POST")]
[Api(Description = "Marks something as a favorite")]
- public class MarkItemByNameFavorite : IReturnVoid
+ public class MarkItemByNameFavorite : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -45,7 +47,7 @@ namespace MediaBrowser.Api.UserLibrary
[Route("/Users/{UserId}/Favorites/MusicGenres/{Name}", "DELETE")]
[Route("/Users/{UserId}/Favorites/GameGenres/{Name}", "DELETE")]
[Api(Description = "Unmarks something as a favorite")]
- public class UnmarkItemByNameFavorite : IReturnVoid
+ public class UnmarkItemByNameFavorite : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -71,7 +73,7 @@ namespace MediaBrowser.Api.UserLibrary
[Route("/Users/{UserId}/Ratings/Genres/{Name}", "POST")]
[Route("/Users/{UserId}/Ratings/MusicGenres/{Name}", "POST")]
[Api(Description = "Updates a user's rating for an item")]
- public class UpdateItemByNameRating : IReturnVoid
+ public class UpdateItemByNameRating : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -105,7 +107,7 @@ namespace MediaBrowser.Api.UserLibrary
[Route("/Users/{UserId}/Ratings/MusicGenres/{Name}", "DELETE")]
[Route("/Users/{UserId}/Ratings/GameGenres/{Name}", "DELETE")]
[Api(Description = "Deletes a user's saved personal rating for an item")]
- public class DeleteItemByNameRating : IReturnVoid
+ public class DeleteItemByNameRating : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -152,56 +154,56 @@ namespace MediaBrowser.Api.UserLibrary
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(MarkItemByNameFavorite request)
+ public object Post(MarkItemByNameFavorite request)
{
var pathInfo = PathInfo.Parse(RequestContext.PathInfo);
var type = pathInfo.GetArgumentValue<string>(3);
var task = MarkFavorite(request.UserId, type, request.Name, true);
- Task.WaitAll(task);
+ return ToOptimizedResult(task.Result);
}
/// <summary>
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(UpdateItemByNameRating request)
+ public object Post(UpdateItemByNameRating request)
{
var pathInfo = PathInfo.Parse(RequestContext.PathInfo);
var type = pathInfo.GetArgumentValue<string>(3);
var task = MarkLike(request.UserId, type, request.Name, request.Likes);
- Task.WaitAll(task);
+ return ToOptimizedResult(task.Result);
}
/// <summary>
/// Deletes the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Delete(UnmarkItemByNameFavorite request)
+ public object Delete(UnmarkItemByNameFavorite request)
{
var pathInfo = PathInfo.Parse(RequestContext.PathInfo);
var type = pathInfo.GetArgumentValue<string>(3);
var task = MarkFavorite(request.UserId, type, request.Name, false);
- Task.WaitAll(task);
+ return ToOptimizedResult(task.Result);
}
/// <summary>
/// Deletes the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Delete(DeleteItemByNameRating request)
+ public object Delete(DeleteItemByNameRating request)
{
var pathInfo = PathInfo.Parse(RequestContext.PathInfo);
var type = pathInfo.GetArgumentValue<string>(3);
var task = MarkLike(request.UserId, type, request.Name, null);
- Task.WaitAll(task);
+ return ToOptimizedResult(task.Result);
}
/// <summary>
@@ -212,7 +214,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="name">The name.</param>
/// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
/// <returns>Task.</returns>
- protected async Task MarkFavorite(Guid userId, string type, string name, bool isFavorite)
+ protected async Task<UserItemDataDto> MarkFavorite(Guid userId, string type, string name, bool isFavorite)
{
var item = await GetItemByName(name, type, LibraryManager).ConfigureAwait(false);
@@ -225,6 +227,10 @@ namespace MediaBrowser.Api.UserLibrary
data.IsFavorite = isFavorite;
await UserDataRepository.SaveUserData(userId, key, data, CancellationToken.None).ConfigureAwait(false);
+
+ data = UserDataRepository.GetUserData(userId, key);
+
+ return DtoBuilder.GetUserItemDataDto(data);
}
/// <summary>
@@ -235,7 +241,7 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="name">The name.</param>
/// <param name="likes">if set to <c>true</c> [likes].</param>
/// <returns>Task.</returns>
- protected async Task MarkLike(Guid userId, string type, string name, bool? likes)
+ protected async Task<UserItemDataDto> MarkLike(Guid userId, string type, string name, bool? likes)
{
var item = await GetItemByName(name, type, LibraryManager).ConfigureAwait(false);
@@ -247,6 +253,10 @@ namespace MediaBrowser.Api.UserLibrary
data.Likes = likes;
await UserDataRepository.SaveUserData(userId, key, data, CancellationToken.None).ConfigureAwait(false);
+
+ data = UserDataRepository.GetUserData(userId, key);
+
+ return DtoBuilder.GetUserItemDataDto(data);
}
}
}
diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
index 46254435f..304ff27d0 100644
--- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
+++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs
@@ -79,7 +79,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
[Route("/Users/{UserId}/FavoriteItems/{Id}", "POST")]
[Api(Description = "Marks an item as a favorite")]
- public class MarkFavoriteItem : IReturnVoid
+ public class MarkFavoriteItem : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -101,7 +101,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
[Route("/Users/{UserId}/FavoriteItems/{Id}", "DELETE")]
[Api(Description = "Unmarks an item as a favorite")]
- public class UnmarkFavoriteItem : IReturnVoid
+ public class UnmarkFavoriteItem : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -123,7 +123,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
[Route("/Users/{UserId}/Items/{Id}/Rating", "DELETE")]
[Api(Description = "Deletes a user's saved personal rating for an item")]
- public class DeleteUserItemRating : IReturnVoid
+ public class DeleteUserItemRating : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -145,7 +145,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
[Route("/Users/{UserId}/Items/{Id}/Rating", "POST")]
[Api(Description = "Updates a user's rating for an item")]
- public class UpdateUserItemRating : IReturnVoid
+ public class UpdateUserItemRating : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -174,7 +174,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
[Route("/Users/{UserId}/PlayedItems/{Id}", "POST")]
[Api(Description = "Marks an item as played")]
- public class MarkPlayedItem : IReturnVoid
+ public class MarkPlayedItem : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -196,7 +196,7 @@ namespace MediaBrowser.Api.UserLibrary
/// </summary>
[Route("/Users/{UserId}/PlayedItems/{Id}", "DELETE")]
[Api(Description = "Marks an item as unplayed")]
- public class MarkUnplayedItem : IReturnVoid
+ public class MarkUnplayedItem : IReturn<UserItemDataDto>
{
/// <summary>
/// Gets or sets the user id.
@@ -372,7 +372,6 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="itemRepo">The item repo.</param>
/// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
public UserLibraryService(IUserManager userManager, ILibraryManager libraryManager, IUserDataRepository userDataRepository, IItemRepository itemRepo, ISessionManager sessionManager)
- : base()
{
_userManager = userManager;
_libraryManager = libraryManager;
@@ -388,6 +387,13 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetSpecialFeatures request)
{
+ var result = GetAsync(request).Result;
+
+ return ToOptimizedResult(result);
+ }
+
+ private Task<BaseItemDto[]> GetAsync(GetSpecialFeatures request)
+ {
var user = _userManager.GetUserById(request.UserId);
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
@@ -399,14 +405,12 @@ namespace MediaBrowser.Api.UserLibrary
var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo);
- var items = movie.SpecialFeatureIds
+ var tasks = movie.SpecialFeatureIds
.Select(_itemRepo.RetrieveItem)
.OrderBy(i => i.SortName)
- .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, movie))
- .Select(t => t.Result)
- .ToList();
+ .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, movie));
- return ToOptimizedResult(items);
+ return Task.WhenAll(tasks);
}
/// <summary>
@@ -416,6 +420,13 @@ namespace MediaBrowser.Api.UserLibrary
/// <returns>System.Object.</returns>
public object Get(GetLocalTrailers request)
{
+ var result = GetAsync(request).Result;
+
+ return ToOptimizedResult(result);
+ }
+
+ private Task<BaseItemDto[]> GetAsync(GetLocalTrailers request)
+ {
var user = _userManager.GetUserById(request.UserId);
var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
@@ -425,14 +436,12 @@ namespace MediaBrowser.Api.UserLibrary
var dtoBuilder = new DtoBuilder(Logger, _libraryManager, _userDataRepository, _itemRepo);
- var items = item.LocalTrailerIds
+ var tasks = item.LocalTrailerIds
.Select(_itemRepo.RetrieveItem)
.OrderBy(i => i.SortName)
- .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, item))
- .Select(t => t.Result)
- .ToList();
+ .Select(i => dtoBuilder.GetBaseItemDto(i, fields, user, item));
- return ToOptimizedResult(items);
+ return Task.WhenAll(tasks);
}
/// <summary>
@@ -497,34 +506,29 @@ namespace MediaBrowser.Api.UserLibrary
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(MarkFavoriteItem request)
+ public object Post(MarkFavoriteItem request)
{
- var user = _userManager.GetUserById(request.UserId);
+ var dto = MarkFavorite(request.UserId, request.Id, true).Result;
- var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
-
- // Get the user data for this item
- var key = item.GetUserDataKey();
-
- var data = _userDataRepository.GetUserData(user.Id, key);
-
- // Set favorite status
- data.IsFavorite = true;
-
- var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
-
- Task.WaitAll(task);
+ return ToOptimizedResult(dto);
}
/// <summary>
/// Deletes the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Delete(UnmarkFavoriteItem request)
+ public object Delete(UnmarkFavoriteItem request)
{
- var user = _userManager.GetUserById(request.UserId);
+ var dto = MarkFavorite(request.UserId, request.Id, false).Result;
- var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
+ return ToOptimizedResult(dto);
+ }
+
+ private async Task<UserItemDataDto> MarkFavorite(Guid userId, string itemId, bool isFavorite)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id);
var key = item.GetUserDataKey();
@@ -532,68 +536,68 @@ namespace MediaBrowser.Api.UserLibrary
var data = _userDataRepository.GetUserData(user.Id, key);
// Set favorite status
- data.IsFavorite = false;
+ data.IsFavorite = isFavorite;
- var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
+ await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
- Task.WaitAll(task);
+ data = _userDataRepository.GetUserData(user.Id, key);
+
+ return DtoBuilder.GetUserItemDataDto(data);
}
/// <summary>
/// Deletes the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Delete(DeleteUserItemRating request)
+ public object Delete(DeleteUserItemRating request)
{
- var user = _userManager.GetUserById(request.UserId);
-
- var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
-
- var key = item.GetUserDataKey();
-
- // Get the user data for this item
- var data = _userDataRepository.GetUserData(user.Id, key);
-
- data.Rating = null;
-
- var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
+ var dto = UpdateUserItemRating(request.UserId, request.Id, null).Result;
- Task.WaitAll(task);
+ return ToOptimizedResult(dto);
}
/// <summary>
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(UpdateUserItemRating request)
+ public object Post(UpdateUserItemRating request)
{
- var user = _userManager.GetUserById(request.UserId);
+ var dto = UpdateUserItemRating(request.UserId, request.Id, request.Likes).Result;
- var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : DtoBuilder.GetItemByClientId(request.Id, _userManager, _libraryManager, user.Id);
+ return ToOptimizedResult(dto);
+ }
+
+ private async Task<UserItemDataDto> UpdateUserItemRating(Guid userId, string itemId, bool? likes)
+ {
+ var user = _userManager.GetUserById(userId);
+
+ var item = string.IsNullOrEmpty(itemId) ? user.RootFolder : DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id);
var key = item.GetUserDataKey();
// Get the user data for this item
var data = _userDataRepository.GetUserData(user.Id, key);
- data.Likes = request.Likes;
+ data.Likes = likes;
- var task = _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None);
+ await _userDataRepository.SaveUserData(user.Id, key, data, CancellationToken.None).ConfigureAwait(false);
- Task.WaitAll(task);
- }
+ data = _userDataRepository.GetUserData(user.Id, key);
+ return DtoBuilder.GetUserItemDataDto(data);
+ }
+
/// <summary>
/// Posts the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Post(MarkPlayedItem request)
+ public object Post(MarkPlayedItem request)
{
var user = _userManager.GetUserById(request.UserId);
var task = UpdatePlayedStatus(user, request.Id, true);
- Task.WaitAll(task);
+ return ToOptimizedResult(task.Result);
}
private SessionInfo GetSession()
@@ -660,13 +664,13 @@ namespace MediaBrowser.Api.UserLibrary
/// Deletes the specified request.
/// </summary>
/// <param name="request">The request.</param>
- public void Delete(MarkUnplayedItem request)
+ public object Delete(MarkUnplayedItem request)
{
var user = _userManager.GetUserById(request.UserId);
var task = UpdatePlayedStatus(user, request.Id, false);
- Task.WaitAll(task);
+ return ToOptimizedResult(task.Result);
}
/// <summary>
@@ -676,11 +680,13 @@ namespace MediaBrowser.Api.UserLibrary
/// <param name="itemId">The item id.</param>
/// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
/// <returns>Task.</returns>
- private Task UpdatePlayedStatus(User user, string itemId, bool wasPlayed)
+ private async Task<UserItemDataDto> UpdatePlayedStatus(User user, string itemId, bool wasPlayed)
{
var item = DtoBuilder.GetItemByClientId(itemId, _userManager, _libraryManager, user.Id);
- return item.SetPlayedStatus(user, wasPlayed, _userDataRepository);
+ await item.SetPlayedStatus(user, wasPlayed, _userDataRepository).ConfigureAwait(false);
+
+ return DtoBuilder.GetUserItemDataDto(_userDataRepository.GetUserData(user.Id, item.GetUserDataKey()));
}
}
}
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
index 9a62f4ed3..d4b6c0b9e 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
@@ -35,9 +35,15 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
{
- var trigger = new DailyTrigger { TimeOfDay = TimeSpan.FromHours(2) }; //2am
-
- return new[] { trigger };
+ // Until we can vary these default triggers per server and MBT, we need something that makes sense for both
+ return new ITaskTrigger[] {
+
+ // At startup
+ new StartupTrigger (),
+
+ // Every so often
+ new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
+ };
}
/// <summary>
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
index bcead321d..15f955723 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
@@ -35,9 +35,15 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
{
- var trigger = new DailyTrigger { TimeOfDay = TimeSpan.FromHours(2) }; //2am
-
- return new[] { trigger };
+ // Until we can vary these default triggers per server and MBT, we need something that makes sense for both
+ return new ITaskTrigger[] {
+
+ // At startup
+ new StartupTrigger (),
+
+ // Every so often
+ new IntervalTrigger { Interval = TimeSpan.FromHours(24)}
+ };
}
/// <summary>
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs
index 0a134b5e5..33917125e 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/SystemUpdateTask.cs
@@ -48,6 +48,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
public IEnumerable<ITaskTrigger> GetDefaultTriggers()
{
+ // Until we can vary these default triggers per server and MBT, we need something that makes sense for both
return new ITaskTrigger[] {
// At startup
diff --git a/MediaBrowser.Controller/Dto/DtoBuilder.cs b/MediaBrowser.Controller/Dto/DtoBuilder.cs
index 85d2a2003..b2b858b6d 100644
--- a/MediaBrowser.Controller/Dto/DtoBuilder.cs
+++ b/MediaBrowser.Controller/Dto/DtoBuilder.cs
@@ -90,6 +90,11 @@ namespace MediaBrowser.Controller.Dto
}
}
+ if (fields.Contains(ItemFields.DisplayPreferencesId))
+ {
+ dto.DisplayPreferencesId = item.DisplayPreferencesId.ToString("N");
+ }
+
if (user != null)
{
AttachUserSpecificInfo(dto, item, user, fields);
@@ -272,11 +277,6 @@ namespace MediaBrowser.Controller.Dto
/// <param name="fields">The fields.</param>
private void AttachUserSpecificInfo(BaseItemDto dto, BaseItem item, User user, List<ItemFields> fields)
{
- if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId))
- {
- dto.DisplayPreferencesId = ((Folder) item).DisplayPreferencesId.ToString("N");
- }
-
if (item.IsFolder)
{
var hasItemCounts = fields.Contains(ItemFields.ItemCounts);
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 1526cf46a..1668b91d4 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -91,11 +91,5 @@ namespace MediaBrowser.Controller.Entities.Audio
{
return RecursiveChildren.OfType<Audio>().Any(i => i.HasArtist(artist));
}
-
- /// <summary>
- /// Gets or sets the music brainz release group id.
- /// </summary>
- /// <value>The music brainz release group id.</value>
- public string MusicBrainzReleaseGroupId { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index cc2e82b1f..a75dc47e1 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Localization;
@@ -84,6 +85,21 @@ namespace MediaBrowser.Controller.Entities
public Guid Id { get; set; }
/// <summary>
+ /// Return the id that should be used to key display prefs for this item.
+ /// Default is based on the type for everything except actual generic folders.
+ /// </summary>
+ /// <value>The display prefs id.</value>
+ [IgnoreDataMember]
+ public virtual Guid DisplayPreferencesId
+ {
+ get
+ {
+ var thisType = GetType();
+ return thisType == typeof(Folder) ? Id : thisType.FullName.GetMD5();
+ }
+ }
+
+ /// <summary>
/// Gets or sets the path.
/// </summary>
/// <value>The path.</value>
@@ -393,7 +409,12 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- return ForcedSortName ?? _sortName ?? (_sortName = CreateSortName());
+ if (!string.IsNullOrEmpty(ForcedSortName))
+ {
+ return ForcedSortName;
+ }
+
+ return _sortName ?? (_sortName = CreateSortName());
}
}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index ca43ca616..91f4504c2 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -16,6 +16,7 @@ using System.Linq;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using MoreLinq;
namespace MediaBrowser.Controller.Entities
{
@@ -66,21 +67,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- /// <summary>
- /// Return the id that should be used to key display prefs for this item.
- /// Default is based on the type for everything except actual generic folders.
- /// </summary>
- /// <value>The display prefs id.</value>
- [IgnoreDataMember]
- public virtual Guid DisplayPreferencesId
- {
- get
- {
- var thisType = GetType();
- return thisType == typeof(Folder) ? Id : thisType.FullName.GetMD5();
- }
- }
-
public virtual List<LinkedChild> LinkedChildren { get; set; }
protected virtual bool SupportsShortcutChildren
@@ -974,7 +960,31 @@ namespace MediaBrowser.Controller.Entities
/// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
/// <returns>IEnumerable{BaseItem}.</returns>
/// <exception cref="System.ArgumentNullException"></exception>
- public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = false)
+ public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
+ {
+ if (user == null)
+ {
+ throw new ArgumentNullException();
+ }
+
+ var children = GetRecursiveChildrenInternal(user, includeLinkedChildren);
+
+ if (includeLinkedChildren)
+ {
+ children = children.DistinctBy(i => i.Id);
+ }
+
+ return children;
+ }
+
+ /// <summary>
+ /// Gets allowed recursive children of an item
+ /// </summary>
+ /// <param name="user">The user.</param>
+ /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
+ /// <returns>IEnumerable{BaseItem}.</returns>
+ /// <exception cref="System.ArgumentNullException"></exception>
+ private IEnumerable<BaseItem> GetRecursiveChildrenInternal(User user, bool includeLinkedChildren)
{
if (user == null)
{
@@ -989,7 +999,7 @@ namespace MediaBrowser.Controller.Entities
if (subFolder != null)
{
- foreach (var subitem in subFolder.GetRecursiveChildren(user, includeLinkedChildren))
+ foreach (var subitem in subFolder.GetRecursiveChildrenInternal(user, includeLinkedChildren))
{
yield return subitem;
}
diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs
index c1ca5336b..dc2b49c75 100644
--- a/MediaBrowser.Controller/IO/FileData.cs
+++ b/MediaBrowser.Controller/IO/FileData.cs
@@ -4,6 +4,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using MoreLinq;
namespace MediaBrowser.Controller.IO
{
@@ -38,7 +39,10 @@ namespace MediaBrowser.Controller.IO
if (!resolveShortcuts && flattenFolderDepth == 0)
{
- return entries.ToDictionary(i => i.FullName, StringComparer.OrdinalIgnoreCase);
+ // Seeing dupes on some users file system for some reason
+ return entries
+ .DistinctBy(i => i.FullName, StringComparer.OrdinalIgnoreCase)
+ .ToDictionary(i => i.FullName, StringComparer.OrdinalIgnoreCase);
}
var dict = new Dictionary<string, FileSystemInfo>(StringComparer.OrdinalIgnoreCase);
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index 2761c51e0..ba4f84ac2 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -173,8 +173,16 @@ namespace MediaBrowser.Controller.Providers
break;
}
case "SortTitle":
- item.ForcedSortName = reader.ReadElementContentAsString();
- break;
+ {
+ var val = reader.ReadElementContentAsString();
+
+ if (!string.IsNullOrWhiteSpace(val))
+ {
+ item.ForcedSortName = val;
+ }
+
+ break;
+ }
case "Overview":
case "Description":
@@ -503,7 +511,7 @@ namespace MediaBrowser.Controller.Providers
{
DateTime airDate;
- if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850)
+ if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out airDate) && airDate.Year > 1850)
{
item.PremiereDate = airDate.ToUniversalTime();
item.ProductionYear = airDate.Year;
@@ -522,7 +530,7 @@ namespace MediaBrowser.Controller.Providers
{
DateTime airDate;
- if (DateTime.TryParse(firstAired, out airDate) && airDate.Year > 1850)
+ if (DateTime.TryParseExact(firstAired, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out airDate) && airDate.Year > 1850)
{
item.EndDate = airDate.ToUniversalTime();
}
@@ -548,13 +556,23 @@ namespace MediaBrowser.Controller.Providers
break;
case "MusicbrainzId":
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
{
- item.SetProviderId(MetadataProviders.Musicbrainz, mbz);
+ var mbz = reader.ReadElementContentAsString();
+ if (!string.IsNullOrWhiteSpace(mbz))
+ {
+ item.SetProviderId(MetadataProviders.Musicbrainz, mbz);
+ }
+ break;
+ }
+ case "MusicBrainzReleaseGroupId":
+ {
+ var mbz = reader.ReadElementContentAsString();
+ if (!string.IsNullOrWhiteSpace(mbz))
+ {
+ item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, mbz);
+ }
+ break;
}
- break;
-
case "RottenTomatoesId":
var rtId = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(rtId))
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 7606f7a94..5a8bd2097 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -314,6 +314,9 @@
<Compile Include="..\MediaBrowser.Model\Session\BrowseRequest.cs">
<Link>Session\BrowseRequest.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
+ <Link>Session\MessageCommand.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs">
<Link>Session\PlayRequest.cs</Link>
</Compile>
@@ -323,6 +326,9 @@
<Compile Include="..\MediaBrowser.Model\Session\SessionInfoDto.cs">
<Link>Session\SessionInfoDto.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Session\SystemCommand.cs">
+ <Link>Session\SystemCommand.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\System\SystemInfo.cs">
<Link>System\SystemInfo.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index 4c27d74b3..855d65fd5 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -295,6 +295,9 @@
<Compile Include="..\MediaBrowser.Model\Session\BrowseRequest.cs">
<Link>Session\BrowseRequest.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Session\MessageCommand.cs">
+ <Link>Session\MessageCommand.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\Session\PlayRequest.cs">
<Link>Session\PlayRequest.cs</Link>
</Compile>
@@ -304,6 +307,9 @@
<Compile Include="..\MediaBrowser.Model\Session\SessionInfoDto.cs">
<Link>Session\SessionInfoDto.cs</Link>
</Compile>
+ <Compile Include="..\MediaBrowser.Model\Session\SystemCommand.cs">
+ <Link>Session\SystemCommand.cs</Link>
+ </Compile>
<Compile Include="..\MediaBrowser.Model\System\SystemInfo.cs">
<Link>System\SystemInfo.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs
index ecb13f83a..eaa3dc15a 100644
--- a/MediaBrowser.Model/ApiClient/IApiClient.cs
+++ b/MediaBrowser.Model/ApiClient/IApiClient.cs
@@ -437,7 +437,7 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="wasPlayed">if set to <c>true</c> [was played].</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">itemId</exception>
- Task UpdatePlayedStatusAsync(string itemId, string userId, bool wasPlayed);
+ Task<UserItemDataDto> UpdatePlayedStatusAsync(string itemId, string userId, bool wasPlayed);
/// <summary>
/// Updates the favorite status async.
@@ -447,7 +447,7 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="isFavorite">if set to <c>true</c> [is favorite].</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">itemId</exception>
- Task UpdateFavoriteStatusAsync(string itemId, string userId, bool isFavorite);
+ Task<UserItemDataDto> UpdateFavoriteStatusAsync(string itemId, string userId, bool isFavorite);
/// <summary>
/// Reports to the server that the user has begun playing an item
@@ -512,13 +512,29 @@ namespace MediaBrowser.Model.ApiClient
Task SendPlayCommandAsync(string sessionId, PlayRequest request);
/// <summary>
+ /// Sends a system command to the client
+ /// </summary>
+ /// <param name="sessionId"></param>
+ /// <param name="command"></param>
+ /// <returns></returns>
+ Task SendSystemCommandAsync(string sessionId, SystemCommand command);
+
+ /// <summary>
+ /// Instructs the client to display a message to the user
+ /// </summary>
+ /// <param name="sessionId"></param>
+ /// <param name="command"></param>
+ /// <returns></returns>
+ Task SendMessageCommandAsync(string sessionId, MessageCommand command);
+
+ /// <summary>
/// Clears a user's rating for an item
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="userId">The user id.</param>
/// <returns>Task{UserItemDataDto}.</returns>
/// <exception cref="ArgumentNullException">itemId</exception>
- Task ClearUserItemRatingAsync(string itemId, string userId);
+ Task<UserItemDataDto> ClearUserItemRatingAsync(string itemId, string userId);
/// <summary>
/// Updates a user's rating for an item, based on likes or dislikes
@@ -528,7 +544,7 @@ namespace MediaBrowser.Model.ApiClient
/// <param name="likes">if set to <c>true</c> [likes].</param>
/// <returns>Task.</returns>
/// <exception cref="ArgumentNullException">itemId</exception>
- Task UpdateUserItemRatingAsync(string itemId, string userId, bool likes);
+ Task<UserItemDataDto> UpdateUserItemRatingAsync(string itemId, string userId, bool likes);
/// <summary>
/// Authenticates a user and returns the result
diff --git a/MediaBrowser.Model/Entities/MetadataProviders.cs b/MediaBrowser.Model/Entities/MetadataProviders.cs
index b089b668c..db784a35f 100644
--- a/MediaBrowser.Model/Entities/MetadataProviders.cs
+++ b/MediaBrowser.Model/Entities/MetadataProviders.cs
@@ -34,6 +34,7 @@ namespace MediaBrowser.Model.Entities
/// <summary>
/// Tmdb Collection Id
/// </summary>
- TmdbCollection
+ TmdbCollection,
+ MusicBrainzReleaseGroup
}
}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index 8f1194f7f..280df9fe9 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -76,6 +76,7 @@
<Compile Include="Querying\SimilarItemsQuery.cs" />
<Compile Include="Querying\UserQuery.cs" />
<Compile Include="Session\BrowseRequest.cs" />
+ <Compile Include="Session\MessageCommand.cs" />
<Compile Include="Session\PlayRequest.cs" />
<Compile Include="Session\PlaystateCommand.cs" />
<Compile Include="Entities\ImageDownloadOptions.cs" />
@@ -113,6 +114,7 @@
<Compile Include="Serialization\IJsonSerializer.cs" />
<Compile Include="Serialization\IXmlSerializer.cs" />
<Compile Include="Session\SessionInfoDto.cs" />
+ <Compile Include="Session\SystemCommand.cs" />
<Compile Include="Updates\CheckForUpdateResult.cs" />
<Compile Include="Updates\PackageTargetSystem.cs" />
<Compile Include="Updates\InstallationInfo.cs" />
diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs
new file mode 100644
index 000000000..5ab580815
--- /dev/null
+++ b/MediaBrowser.Model/Session/MessageCommand.cs
@@ -0,0 +1,12 @@
+
+namespace MediaBrowser.Model.Session
+{
+ public class MessageCommand
+ {
+ public string Header { get; set; }
+
+ public string Text { get; set; }
+
+ public long? TimeoutMs { get; set; }
+ }
+}
diff --git a/MediaBrowser.Model/Session/SystemCommand.cs b/MediaBrowser.Model/Session/SystemCommand.cs
new file mode 100644
index 000000000..2fcaef6e3
--- /dev/null
+++ b/MediaBrowser.Model/Session/SystemCommand.cs
@@ -0,0 +1,14 @@
+
+namespace MediaBrowser.Model.Session
+{
+ public enum SystemCommand
+ {
+ GoHome,
+ GoToSettings,
+ VolumeUp,
+ VolumeDown,
+ Mute,
+ Unmute,
+ ToggleMute
+ }
+}
diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
index e80ba2b7d..69a23b84f 100644
--- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs
+++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs
@@ -180,8 +180,7 @@ namespace MediaBrowser.Providers.Movies
}
private const string TmdbConfigUrl = "http://api.themoviedb.org/3/configuration?api_key={0}";
- private const string Search3 = @"http://api.themoviedb.org/3/search/movie?api_key={1}&query={0}&language={2}";
- private const string AltTitleSearch = @"http://api.themoviedb.org/3/movie/{0}/alternative_titles?api_key={1}&country={2}";
+ private const string Search3 = @"http://api.themoviedb.org/3/search/{3}?api_key={1}&query={0}&language={2}";
private const string GetMovieInfo3 = @"http://api.themoviedb.org/3/movie/{0}?api_key={1}&language={2}&append_to_response=casts,releases,images,keywords,trailers";
private const string GetBoxSetInfo3 = @"http://api.themoviedb.org/3/collection/{0}?api_key={1}&language={2}&append_to_response=images";
@@ -193,8 +192,6 @@ namespace MediaBrowser.Providers.Movies
new Regex(@"(?<name>.*)") // last resort matches the whole string as the name
};
- public const string AltMetaFileName = "movie.xml";
-
protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
{
if (HasAltMeta(item))
@@ -237,7 +234,10 @@ namespace MediaBrowser.Providers.Movies
{
return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName("collection.xml");
}
- return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName(AltMetaFileName);
+
+ var xmlFileName = MovieProviderFromXml.GetXmlFilename(item);
+
+ return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName(xmlFileName);
}
/// <summary>
@@ -248,8 +248,19 @@ namespace MediaBrowser.Providers.Movies
/// <returns>Task.</returns>
private async Task FetchMovieData(BaseItem item, CancellationToken cancellationToken)
{
- string id = item.GetProviderId(MetadataProviders.Tmdb) ?? await FindId(item, item.ProductionYear, cancellationToken).ConfigureAwait(false);
- if (id != null)
+ var id = item.GetProviderId(MetadataProviders.Tmdb);
+
+ if (string.IsNullOrEmpty(id))
+ {
+ id = item.GetProviderId(MetadataProviders.Imdb);
+ }
+
+ if (string.IsNullOrEmpty(id))
+ {
+ id = await FindId(item, cancellationToken).ConfigureAwait(false);
+ }
+
+ if (!string.IsNullOrEmpty(id))
{
Logger.Debug("MovieDbProvider - getting movie info with id: " + id);
@@ -291,21 +302,17 @@ namespace MediaBrowser.Providers.Movies
/// Finds the id.
/// </summary>
/// <param name="item">The item.</param>
- /// <param name="productionYear">The production year.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task{System.String}.</returns>
- public async Task<string> FindId(BaseItem item, int? productionYear, CancellationToken cancellationToken)
+ public async Task<string> FindId(BaseItem item, CancellationToken cancellationToken)
{
- int? year;
+ int? yearInName;
string name = item.Name;
- ParseName(name, out name, out year);
+ ParseName(name, out name, out yearInName);
- if (year == null && productionYear != null)
- {
- year = productionYear;
- }
+ var year = item.ProductionYear ?? yearInName;
- Logger.Info("MovieDbProvider: Finding id for movie: " + name);
+ Logger.Info("MovieDbProvider: Finding id for item: " + name);
string language = ConfigurationManager.Configuration.PreferredMetadataLanguage.ToLower();
//if we are a boxset - look at our first child
@@ -313,19 +320,23 @@ namespace MediaBrowser.Providers.Movies
if (boxset != null)
{
// See if any movies have a collection id already
- return boxset.Children.Concat(boxset.GetLinkedChildren()).OfType<Video>()
+ var collId = boxset.Children.Concat(boxset.GetLinkedChildren()).OfType<Video>()
.Select(i => i.GetProviderId(MetadataProviders.TmdbCollection))
.FirstOrDefault(i => i != null);
+
+ if (collId != null) return collId;
+
}
//nope - search for it
- var id = await AttemptFindId(name, year, language, cancellationToken).ConfigureAwait(false);
+ var searchType = item is BoxSet ? "collection" : "movie";
+ var id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false);
if (id == null)
{
//try in english if wasn't before
if (language != "en")
{
- id = await AttemptFindId(name, year, "en", cancellationToken).ConfigureAwait(false);
+ id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false);
}
else
{
@@ -340,12 +351,12 @@ namespace MediaBrowser.Providers.Movies
// Search again if the new name is different
if (!string.Equals(name, originalName))
{
- id = await AttemptFindId(name, year, language, cancellationToken).ConfigureAwait(false);
+ id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false);
if (id == null && language != "en")
{
//one more time, in english
- id = await AttemptFindId(name, year, "en", cancellationToken).ConfigureAwait(false);
+ id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false);
}
}
@@ -359,7 +370,7 @@ namespace MediaBrowser.Providers.Movies
if (!string.Equals(pathName, name, StringComparison.OrdinalIgnoreCase)
&& !string.Equals(pathName, originalName, StringComparison.OrdinalIgnoreCase))
{
- id = await AttemptFindId(pathName, year, "en", cancellationToken).ConfigureAwait(false);
+ id = await AttemptFindId(pathName, searchType, year, "en", cancellationToken).ConfigureAwait(false);
}
}
}
@@ -372,13 +383,14 @@ namespace MediaBrowser.Providers.Movies
/// Attempts the find id.
/// </summary>
/// <param name="name">The name.</param>
+ /// <param name="type">movie or collection</param>
/// <param name="year">The year.</param>
/// <param name="language">The language.</param>
/// <param name="cancellationToken">The cancellation token</param>
/// <returns>Task{System.String}.</returns>
- private async Task<string> AttemptFindId(string name, int? year, string language, CancellationToken cancellationToken)
+ private async Task<string> AttemptFindId(string name, string type, int? year, string language, CancellationToken cancellationToken)
{
- string url3 = string.Format(Search3, UrlEncode(name), ApiKey, language);
+ string url3 = string.Format(Search3, UrlEncode(name), ApiKey, language, type);
TmdbMovieSearchResults searchResult = null;
using (Stream json = await GetMovieDbResponse(new HttpRequestOptions
@@ -392,95 +404,12 @@ namespace MediaBrowser.Providers.Movies
searchResult = JsonSerializer.DeserializeFromStream<TmdbMovieSearchResults>(json);
}
- if (searchResult == null || searchResult.results.Count == 0)
- {
- //try replacing numbers
- foreach (var pair in ReplaceStartNumbers)
- {
- if (name.StartsWith(pair.Key))
- {
- name = name.Remove(0, pair.Key.Length);
- name = pair.Value + name;
- }
- }
- foreach (var pair in ReplaceEndNumbers)
- {
- if (name.EndsWith(pair.Key))
- {
- name = name.Remove(name.IndexOf(pair.Key), pair.Key.Length);
- name = name + pair.Value;
- }
- }
- Logger.Info("MovieDBProvider - No results. Trying replacement numbers: " + name);
- url3 = string.Format(Search3, UrlEncode(name), ApiKey, language);
-
- using (var json = await GetMovieDbResponse(new HttpRequestOptions
- {
- Url = url3,
- CancellationToken = cancellationToken,
- AcceptHeader = AcceptHeader
-
- }).ConfigureAwait(false))
- {
- searchResult = JsonSerializer.DeserializeFromStream<TmdbMovieSearchResults>(json);
- }
- }
if (searchResult != null)
{
- string compName = GetComparableName(name, Logger);
foreach (var possible in searchResult.results)
{
- string matchedName = null;
+ string matchedName = possible.title ?? possible.name;
string id = possible.id.ToString(CultureInfo.InvariantCulture);
- string n = possible.title;
- if (GetComparableName(n, Logger) == compName)
- {
- matchedName = n;
- }
- else
- {
- n = possible.original_title;
- if (GetComparableName(n, Logger) == compName)
- {
- matchedName = n;
- }
- }
-
- Logger.Debug("MovieDbProvider - " + compName + " didn't match " + n);
- //if main title matches we don't have to look for alternatives
- if (matchedName == null)
- {
- //that title didn't match - look for alternatives
- url3 = string.Format(AltTitleSearch, id, ApiKey, ConfigurationManager.Configuration.MetadataCountryCode);
-
- using (var json = await GetMovieDbResponse(new HttpRequestOptions
- {
- Url = url3,
- CancellationToken = cancellationToken,
- AcceptHeader = AcceptHeader
-
- }).ConfigureAwait(false))
- {
- var response = JsonSerializer.DeserializeFromStream<TmdbAltTitleResults>(json);
-
- if (response != null && response.titles != null)
- {
- foreach (var title in response.titles)
- {
- var t = GetComparableName(title.title, Logger);
- if (t == compName)
- {
- Logger.Debug("MovieDbProvider - " + compName +
- " matched " + t);
- matchedName = t;
- break;
- }
- Logger.Debug("MovieDbProvider - " + compName +
- " did not match " + t);
- }
- }
- }
- }
if (matchedName != null)
{
@@ -520,45 +449,6 @@ namespace MediaBrowser.Providers.Movies
}
/// <summary>
- /// Gets the boxset id from movie.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="year">The year.</param>
- /// <param name="language">The language.</param>
- /// <param name="cancellationToken">The cancellation token</param>
- /// <returns>Task{System.String}.</returns>
- protected async Task<string> GetBoxsetIdFromMovie(string name, int? year, string language, CancellationToken cancellationToken)
- {
- string id = null;
- string childId = await AttemptFindId(name, year, language, cancellationToken).ConfigureAwait(false);
- if (childId != null)
- {
- string url = string.Format(GetMovieInfo3, childId, ApiKey, language);
-
- using (Stream json = await GetMovieDbResponse(new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken,
- AcceptHeader = AcceptHeader
-
- }).ConfigureAwait(false))
- {
- var movieResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
-
- if (movieResult != null && movieResult.belongs_to_collection != null)
- {
- id = movieResult.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture);
- }
- else
- {
- Logger.Error("Unable to obtain boxset id.");
- }
- }
- }
- return id;
- }
-
- /// <summary>
/// Fetches the movie data.
/// </summary>
/// <param name="item">The item.</param>
@@ -850,126 +740,6 @@ namespace MediaBrowser.Providers.Movies
}
}
- /// <summary>
- /// The remove
- /// </summary>
- const string Remove = "\"'!`?";
- // "Face/Off" support.
- /// <summary>
- /// The spacers
- /// </summary>
- const string Spacers = "/,.:;\\(){}[]+-_=–*"; // (there are not actually two - in the they are different char codes)
- /// <summary>
- /// The replace start numbers
- /// </summary>
- static readonly Dictionary<string, string> ReplaceStartNumbers = new Dictionary<string, string> {
- {"1 ","one "},
- {"2 ","two "},
- {"3 ","three "},
- {"4 ","four "},
- {"5 ","five "},
- {"6 ","six "},
- {"7 ","seven "},
- {"8 ","eight "},
- {"9 ","nine "},
- {"10 ","ten "},
- {"11 ","eleven "},
- {"12 ","twelve "},
- {"13 ","thirteen "},
- {"100 ","one hundred "},
- {"101 ","one hundred one "}
- };
-
- /// <summary>
- /// The replace end numbers
- /// </summary>
- static readonly Dictionary<string, string> ReplaceEndNumbers = new Dictionary<string, string> {
- {" 1"," i"},
- {" 2"," ii"},
- {" 3"," iii"},
- {" 4"," iv"},
- {" 5"," v"},
- {" 6"," vi"},
- {" 7"," vii"},
- {" 8"," viii"},
- {" 9"," ix"},
- {" 10"," x"}
- };
-
- /// <summary>
- /// Gets the name of the comparable.
- /// </summary>
- /// <param name="name">The name.</param>
- /// <param name="logger">The logger.</param>
- /// <returns>System.String.</returns>
- internal static string GetComparableName(string name, ILogger logger)
- {
- name = name.ToLower();
- name = name.Replace("á", "a");
- name = name.Replace("é", "e");
- name = name.Replace("í", "i");
- name = name.Replace("ó", "o");
- name = name.Replace("ú", "u");
- name = name.Replace("ü", "u");
- name = name.Replace("ñ", "n");
- foreach (var pair in ReplaceStartNumbers)
- {
- if (name.StartsWith(pair.Key))
- {
- name = name.Remove(0, pair.Key.Length);
- name = pair.Value + name;
- logger.Info("MovieDbProvider - Replaced Start Numbers: " + name);
- }
- }
- foreach (var pair in ReplaceEndNumbers)
- {
- if (name.EndsWith(pair.Key))
- {
- name = name.Remove(name.IndexOf(pair.Key), pair.Key.Length);
- name = name + pair.Value;
- logger.Info("MovieDbProvider - Replaced End Numbers: " + name);
- }
- }
- name = name.Normalize(NormalizationForm.FormKD);
- var sb = new StringBuilder();
- foreach (var c in name)
- {
- if (c >= 0x2B0 && c <= 0x0333)
- {
- // skip char modifier and diacritics
- }
- else if (Remove.IndexOf(c) > -1)
- {
- // skip chars we are removing
- }
- else if (Spacers.IndexOf(c) > -1)
- {
- sb.Append(" ");
- }
- else if (c == '&')
- {
- sb.Append(" and ");
- }
- else
- {
- sb.Append(c);
- }
- }
- name = sb.ToString();
- name = name.Replace(", the", "");
- name = name.Replace(" the ", " ");
- name = name.Replace("the ", "");
-
- string prev_name;
- do
- {
- prev_name = name;
- name = name.Replace(" ", " ");
- } while (name.Length != prev_name.Length);
-
- return name.Trim();
- }
-
#region Result Objects
@@ -1058,6 +828,10 @@ namespace MediaBrowser.Providers.Movies
/// <value>The vote_average.</value>
public double vote_average { get; set; }
/// <summary>
+ /// For collection search results
+ /// </summary>
+ public string name { get; set; }
+ /// <summary>
/// Gets or sets the vote_count.
/// </summary>
/// <value>The vote_count.</value>
diff --git a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
index 76fee746c..762920955 100644
--- a/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
+++ b/MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
@@ -52,6 +52,13 @@ namespace MediaBrowser.Providers.Movies
get { return MetadataProviderPriority.First; }
}
+ internal static string GetXmlFilename(BaseItem item)
+ {
+ var filename = "movie.xml";
+
+ return Path.Combine(item.MetaLocation, filename);
+ }
+
/// <summary>
/// Override this to return the date that should be compared to the last refresh date
/// to determine if this provider should be re-fetched.
@@ -60,7 +67,7 @@ namespace MediaBrowser.Providers.Movies
/// <returns>DateTime.</returns>
protected override DateTime CompareDate(BaseItem item)
{
- var entry = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, "movie.xml"));
+ var entry = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, GetXmlFilename(item)));
return entry != null ? entry.LastWriteTimeUtc : DateTime.MinValue;
}
@@ -86,7 +93,7 @@ namespace MediaBrowser.Providers.Movies
{
cancellationToken.ThrowIfCancellationRequested();
- var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, "movie.xml"));
+ var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, GetXmlFilename(item)));
if (metadataFile != null)
{
diff --git a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
index b02128a37..fa7daaf25 100644
--- a/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
+++ b/MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
@@ -182,10 +182,13 @@ namespace MediaBrowser.Providers.Music
var releaseEntryId = item.GetProviderId(MetadataProviders.Musicbrainz);
+ var musicBrainzReleaseGroupId = album.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
// Fanart uses the release group id so we'll have to get that now using the release entry id
- if (string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId))
+ if (string.IsNullOrEmpty(musicBrainzReleaseGroupId))
{
- album.MusicBrainzReleaseGroupId = await GetReleaseGroupId(releaseEntryId, cancellationToken).ConfigureAwait(false);
+ musicBrainzReleaseGroupId = await GetReleaseGroupId(releaseEntryId, cancellationToken).ConfigureAwait(false);
+
+ album.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, musicBrainzReleaseGroupId);
}
var doc = new XmlDocument();
@@ -199,9 +202,9 @@ namespace MediaBrowser.Providers.Music
// Try try with the release entry Id, if that doesn't produce anything try the release group id
var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + releaseEntryId + "\"]/cdart/@url");
- if (node == null && !string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId))
+ if (node == null && !string.IsNullOrEmpty(musicBrainzReleaseGroupId))
{
- node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/cdart/@url");
+ node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + musicBrainzReleaseGroupId + "\"]/cdart/@url");
}
var path = node != null ? node.Value : null;
@@ -218,9 +221,9 @@ namespace MediaBrowser.Providers.Music
// Try try with the release entry Id, if that doesn't produce anything try the release group id
var node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + releaseEntryId + "\"]/albumcover/@url");
- if (node == null && !string.IsNullOrEmpty(album.MusicBrainzReleaseGroupId))
+ if (node == null && !string.IsNullOrEmpty(musicBrainzReleaseGroupId))
{
- node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + album.MusicBrainzReleaseGroupId + "\"]/albumcover/@url");
+ node = doc.SelectSingleNode("//fanart/music/albums/album[@id=\"" + musicBrainzReleaseGroupId + "\"]/albumcover/@url");
}
var path = node != null ? node.Value : null;
diff --git a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs
index 9a4899cba..154b4ab60 100644
--- a/MediaBrowser.Providers/Savers/MovieXmlSaver.cs
+++ b/MediaBrowser.Providers/Savers/MovieXmlSaver.cs
@@ -119,11 +119,14 @@ namespace MediaBrowser.Providers.Savers
public string GetSavePath(BaseItem item)
{
- var video = (Video)item;
+ if (item.IsInMixedFolder)
+ {
+ return Path.ChangeExtension(item.Path, ".xml");
+ }
+
+ var filename = MovieProviderFromXml.GetXmlFilename(item);
- return video.IsInMixedFolder ?
- Path.ChangeExtension(item.Path, ".xml") :
- Path.Combine(item.MetaLocation, "movie.xml");
+ return Path.Combine(item.MetaLocation, filename);
}
}
}
diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
index 5c9e90312..edeea611a 100644
--- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
+++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
@@ -65,6 +65,7 @@ namespace MediaBrowser.Providers.Savers
"IMDbId",
"TMDbId",
"TVcomId",
+ "TvDbId",
"RottenTomatoesId",
"MusicbrainzId",
"TMDbCollectionId",
@@ -81,7 +82,8 @@ namespace MediaBrowser.Providers.Savers
"BirthDate",
"DeathDate",
"LockedFields",
- "Chapters"
+ "Chapters",
+ "MusicBrainzReleaseGroupId"
});
var position = xml.ToString().LastIndexOf("</", StringComparison.OrdinalIgnoreCase);
@@ -332,6 +334,13 @@ namespace MediaBrowser.Providers.Savers
builder.Append("<MusicbrainzId>" + SecurityElement.Escape(mbz) + "</MusicbrainzId>");
}
+ mbz = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
+
+ if (!string.IsNullOrEmpty(mbz))
+ {
+ builder.Append("<MusicBrainzReleaseGroupId>" + SecurityElement.Escape(mbz) + "</MusicBrainzReleaseGroupId>");
+ }
+
var gamesdb = item.GetProviderId(MetadataProviders.Gamesdb);
if (!string.IsNullOrEmpty(gamesdb))
diff --git a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs
index 9ccbd3eb7..9099d10d6 100644
--- a/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs
+++ b/MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs
@@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.TV
return ItemUpdateType.ImageUpdate | ItemUpdateType.MetadataDownload;
}
}
-
+
/// <summary>
/// Gets the priority.
/// </summary>
@@ -147,7 +147,7 @@ namespace MediaBrowser.Providers.TV
return seriesXmlFileInfo.LastWriteTimeUtc;
}
}
-
+
return base.CompareDate(item);
}
@@ -240,7 +240,7 @@ namespace MediaBrowser.Providers.TV
{
return status;
}
- IEnumerable<XmlDocument> extraEpisodesNode = new XmlDocument[]{};
+ IEnumerable<XmlDocument> extraEpisodesNode = new XmlDocument[] { };
if (episode.IndexNumberEnd.HasValue)
{
@@ -262,7 +262,7 @@ namespace MediaBrowser.Providers.TV
var xElements = all.Where(x => int.Parse(x.Element("EpisodeNumber").Value) > episode.IndexNumber && int.Parse(x.Element("EpisodeNumber").Value) <= episode.IndexNumberEnd.Value);
extraEpisodesNode = xElements.OrderBy(x => x.Element("EpisodeNumber").Value).Select(x => x.ToXmlDocument());
}
-
+
}
var doc = new XmlDocument();
doc.LoadXml(episodeNode.OuterXml);
@@ -320,14 +320,15 @@ namespace MediaBrowser.Providers.TV
var persons = Regex.Matches(actors, @"([^|()]|\([^)]*\)*)+")
.Cast<Match>()
.Select(m => m.Value).Where(i => !string.IsNullOrWhiteSpace(i) && !string.IsNullOrEmpty(i));
- foreach (var person in persons.Select(str => {
- var nameGroup = str.Split(new[] {'('}, 2, StringSplitOptions.RemoveEmptyEntries);
- var name = nameGroup[0].Trim();
- var roles = nameGroup.Count() > 1 ? nameGroup[1].Trim() : null;
- if (roles != null)
- roles = roles.EndsWith(")") ? roles.Substring(0, roles.Length - 1) : roles;
- return new PersonInfo {Type = PersonType.GuestStar, Name = name, Role = roles};
- }))
+ foreach (var person in persons.Select(str =>
+ {
+ var nameGroup = str.Split(new[] { '(' }, 2, StringSplitOptions.RemoveEmptyEntries);
+ var name = nameGroup[0].Trim();
+ var roles = nameGroup.Count() > 1 ? nameGroup[1].Trim() : null;
+ if (roles != null)
+ roles = roles.EndsWith(")") ? roles.Substring(0, roles.Length - 1) : roles;
+ return new PersonInfo { Type = PersonType.GuestStar, Name = name, Role = roles };
+ }))
{
episode.AddPerson(person);
}
@@ -340,14 +341,15 @@ namespace MediaBrowser.Providers.TV
var persons = Regex.Matches(extraActors, @"([^|()]|\([^)]*\)*)+")
.Cast<Match>()
.Select(m => m.Value).Where(i => !string.IsNullOrWhiteSpace(i) && !string.IsNullOrEmpty(i));
- foreach (var person in persons.Select(str => {
- var nameGroup = str.Split(new[] {'('}, 2, StringSplitOptions.RemoveEmptyEntries);
- var name = nameGroup[0].Trim();
- var roles = nameGroup.Count() > 1 ? nameGroup[1].Trim() : null;
- if (roles != null)
- roles = roles.EndsWith(")") ? roles.Substring(0, roles.Length - 1) : roles;
- return new PersonInfo {Type = PersonType.GuestStar, Name = name, Role = roles};
- }).Where(person => !episode.People.Any(x => x.Type == person.Type && x.Name == person.Name))
+ foreach (var person in persons.Select(str =>
+ {
+ var nameGroup = str.Split(new[] { '(' }, 2, StringSplitOptions.RemoveEmptyEntries);
+ var name = nameGroup[0].Trim();
+ var roles = nameGroup.Count() > 1 ? nameGroup[1].Trim() : null;
+ if (roles != null)
+ roles = roles.EndsWith(")") ? roles.Substring(0, roles.Length - 1) : roles;
+ return new PersonInfo { Type = PersonType.GuestStar, Name = name, Role = roles };
+ }).Where(person => !episode.People.Any(x => x.Type == person.Type && x.Name == person.Name))
)
{
episode.AddPerson(person);
@@ -358,9 +360,9 @@ namespace MediaBrowser.Providers.TV
if (directors != null)
{
// Sometimes tvdb actors have leading spaces
- foreach (var person in directors.Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries)
+ foreach (var person in directors.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
- .Select(str => new PersonInfo {Type = PersonType.Director, Name = str.Trim()}))
+ .Select(str => new PersonInfo { Type = PersonType.Director, Name = str.Trim() }))
{
episode.AddPerson(person);
}
@@ -371,9 +373,9 @@ namespace MediaBrowser.Providers.TV
if (writers != null)
{
// Sometimes tvdb actors have leading spaces
- foreach (var person in writers.Split(new[] {'|'}, StringSplitOptions.RemoveEmptyEntries)
+ foreach (var person in writers.Split(new[] { '|', ',' }, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
- .Select(str => new PersonInfo {Type = PersonType.Writer, Name = str.Trim()}))
+ .Select(str => new PersonInfo { Type = PersonType.Writer, Name = str.Trim() }))
{
episode.AddPerson(person);
}
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs
index e98a0a2c5..cb6097504 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
private readonly INotificationsRepository _notificationsRepo;
private readonly IUserManager _userManager;
- private readonly TimeSpan _frequency = TimeSpan.FromHours(3);
+ private readonly TimeSpan _frequency = TimeSpan.FromHours(6);
private readonly TimeSpan _maxAge = TimeSpan.FromDays(31);
public RemoteNotifications(IApplicationPaths appPaths, ILogger logger, IHttpClient httpClient, IJsonSerializer json, INotificationsRepository notificationsRepo, IUserManager userManager)
diff --git a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
index f6e5af7b5..c2e87cbd7 100644
--- a/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
+++ b/MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
@@ -35,6 +35,15 @@ namespace MediaBrowser.Server.Implementations.Library
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
public bool ShouldIgnore(ItemResolveArgs args)
{
+ var filename = args.FileInfo.Name;
+
+ // 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 (args.IsHidden)
{
@@ -71,8 +80,6 @@ namespace MediaBrowser.Server.Implementations.Library
if (args.IsDirectory)
{
- var filename = args.FileInfo.Name;
-
// Ignore any folders in our list
if (IgnoreFolders.ContainsKey(filename))
{
@@ -100,8 +107,6 @@ namespace MediaBrowser.Server.Implementations.Library
{
if (args.Parent != null)
{
- var filename = args.FileInfo.Name;
-
if (string.Equals(Path.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && EntityResolutionHelper.IsAudioFile(filename))
{
return true;
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index d408e082a..4991db8e2 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -230,8 +230,6 @@
<EmbeddedResource Include="MediaEncoder\readme.txt" />
</ItemGroup>
<ItemGroup>
- <EmbeddedResource Include="MediaEncoder\fonts\ARIALUNI.TTF" />
- <EmbeddedResource Include="MediaEncoder\fonts\fonts.conf" />
<EmbeddedResource Include="MediaEncoder\ffmpeg20130813.zip" />
<None Include="packages.config" />
</ItemGroup>
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
index f5b5ea6c0..1eeade08d 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Common.Net;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Logging;
@@ -47,6 +48,8 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <value>The json serializer.</value>
private readonly IJsonSerializer _jsonSerializer;
+ private readonly IHttpClient _httpClient;
+
/// <summary>
/// The video image resource pool
/// </summary>
@@ -81,12 +84,13 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <param name="appPaths">The app paths.</param>
/// <param name="jsonSerializer">The json serializer.</param>
public MediaEncoder(ILogger logger, IZipClient zipClient, IApplicationPaths appPaths,
- IJsonSerializer jsonSerializer)
+ IJsonSerializer jsonSerializer, IHttpClient httpClient)
{
_logger = logger;
_zipClient = zipClient;
_appPaths = appPaths;
_jsonSerializer = jsonSerializer;
+ _httpClient = httpClient;
// Not crazy about this but it's the only way to suppress ffmpeg crash dialog boxes
SetErrorMode(ErrorModes.SEM_FAILCRITICALERRORS | ErrorModes.SEM_NOALIGNMENTFAULTEXCEPT |
@@ -194,22 +198,30 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
/// <param name="assembly">The assembly.</param>
/// <param name="zipFileResourcePath">The zip file resource path.</param>
/// <param name="targetPath">The target path.</param>
- private void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
+ private async void ExtractTools(Assembly assembly, string zipFileResourcePath, string targetPath)
{
using (var resourceStream = assembly.GetManifestResourceStream(zipFileResourcePath))
{
_zipClient.ExtractAll(resourceStream, targetPath, false);
}
- ExtractFonts(assembly, targetPath);
+ try
+ {
+ await DownloadFonts(targetPath).ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error getting ffmpeg font files", ex);
+ }
}
+ private const string FontUrl = "https://www.dropbox.com/s/9nb76tybcsw5xrk/ARIALUNI.zip?dl=1";
+
/// <summary>
/// Extracts the fonts.
/// </summary>
- /// <param name="assembly">The assembly.</param>
/// <param name="targetPath">The target path.</param>
- private async void ExtractFonts(Assembly assembly, string targetPath)
+ private async Task DownloadFonts(string targetPath)
{
var fontsDirectory = Path.Combine(targetPath, "fonts");
@@ -224,54 +236,67 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
if (!File.Exists(fontFile))
{
- using (var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontFilename))
+ await DownloadFontFile(fontsDirectory, fontFilename).ConfigureAwait(false);
+ }
+
+ await WriteFontConfigFile(fontsDirectory).ConfigureAwait(false);
+ }
+
+ private async Task DownloadFontFile(string fontsDirectory, string fontFilename)
+ {
+ var existingFile = Directory
+ .EnumerateFiles(_appPaths.ProgramDataPath, fontFilename, SearchOption.AllDirectories)
+ .FirstOrDefault();
+
+ if (existingFile != null)
+ {
+ try
{
- using (
- var fileStream = new FileStream(fontFile, FileMode.Create, FileAccess.Write, FileShare.Read,
- StreamDefaults.DefaultFileStreamBufferSize,
- FileOptions.Asynchronous))
- {
- await stream.CopyToAsync(fileStream).ConfigureAwait(false);
- }
+ File.Copy(existingFile, Path.Combine(fontsDirectory, fontFilename), true);
+ return;
+ }
+ catch (IOException ex)
+ {
+ // Log this, but don't let it fail the operation
+ _logger.ErrorException("Error copying file", ex);
}
}
- await ExtractFontConfigFile(assembly, fontsDirectory).ConfigureAwait(false);
+ var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
+ {
+ Url = FontUrl,
+ Progress = new Progress<double>()
+ });
+
+ _zipClient.ExtractAll(tempFile, fontsDirectory, true);
+
+ try
+ {
+ File.Delete(tempFile);
+ }
+ catch (IOException ex)
+ {
+ // Log this, but don't let it fail the operation
+ _logger.ErrorException("Error deleting temp file {0}", ex, tempFile);
+ }
}
- /// <summary>
- /// Extracts the font config file.
- /// </summary>
- /// <param name="assembly">The assembly.</param>
- /// <param name="fontsDirectory">The fonts directory.</param>
- /// <returns>Task.</returns>
- private async Task ExtractFontConfigFile(Assembly assembly, string fontsDirectory)
+ private async Task WriteFontConfigFile(string fontsDirectory)
{
const string fontConfigFilename = "fonts.conf";
var fontConfigFile = Path.Combine(fontsDirectory, fontConfigFilename);
if (!File.Exists(fontConfigFile))
{
- using (
- var stream = assembly.GetManifestResourceStream(GetType().Namespace + ".fonts." + fontConfigFilename)
- )
- {
- using (var streamReader = new StreamReader(stream))
- {
- var contents = await streamReader.ReadToEndAsync().ConfigureAwait(false);
+ var contents = string.Format("<?xml version=\"1.0\"?><fontconfig><dir>{0}</dir><alias><family>Arial</family><prefer>Arial Unicode MS</prefer></alias></fontconfig>", fontsDirectory);
- contents = contents.Replace("<dir></dir>", "<dir>" + fontsDirectory + "</dir>");
+ var bytes = Encoding.UTF8.GetBytes(contents);
- var bytes = Encoding.UTF8.GetBytes(contents);
-
- using (
- var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
- FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize,
- FileOptions.Asynchronous))
- {
- await fileStream.WriteAsync(bytes, 0, bytes.Length);
- }
- }
+ using (var fileStream = new FileStream(fontConfigFile, FileMode.Create, FileAccess.Write,
+ FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize,
+ FileOptions.Asynchronous))
+ {
+ await fileStream.WriteAsync(bytes, 0, bytes.Length);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf b/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf
deleted file mode 100644
index 648bdb7b2..000000000
--- a/MediaBrowser.Server.Implementations/MediaEncoder/fonts/fonts.conf
+++ /dev/null
@@ -1,9 +0,0 @@
-<?xml version="1.0"?>
-<fontconfig>
-
-<dir></dir>
- <alias>
- <family>Arial</family>
- <prefer>Arial Unicode MS</prefer>
- </alias>
-</fontconfig> \ No newline at end of file
diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs
index 099b9a3df..eb8a73049 100644
--- a/MediaBrowser.ServerApplication/ApplicationHost.cs
+++ b/MediaBrowser.ServerApplication/ApplicationHost.cs
@@ -272,7 +272,7 @@ namespace MediaBrowser.ServerApplication
RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine(ApplicationPaths, LogManager, LibraryManager));
- MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ZipClient, ApplicationPaths, JsonSerializer);
+ MediaEncoder = new MediaEncoder(LogManager.GetLogger("MediaEncoder"), ZipClient, ApplicationPaths, JsonSerializer, HttpClient);
RegisterSingleInstance(MediaEncoder);
var clientConnectionManager = new SessionManager(UserDataRepository, ServerConfigurationManager, Logger, UserRepository);
diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js
index a0d4b399d..6262d760e 100644
--- a/MediaBrowser.WebDashboard/ApiClient.js
+++ b/MediaBrowser.WebDashboard/ApiClient.js
@@ -2695,7 +2695,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2721,7 +2722,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2747,7 +2749,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "POST",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2773,7 +2776,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2793,7 +2797,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2813,7 +2818,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2833,7 +2839,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2853,7 +2860,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2873,7 +2881,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: method,
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2899,7 +2908,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "POST",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2919,7 +2929,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "POST",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2939,7 +2950,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "POST",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2959,7 +2971,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "POST",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2979,7 +2992,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "POST",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -2999,7 +3013,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "POST",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3022,7 +3037,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "DELETE",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3040,7 +3056,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "DELETE",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3058,7 +3075,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "DELETE",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3076,7 +3094,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "DELETE",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3094,7 +3113,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "DELETE",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3112,7 +3132,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "DELETE",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3290,7 +3311,8 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
return self.ajax({
type: "DELETE",
- url: url
+ url: url,
+ dataType: "json"
});
};
@@ -3443,6 +3465,42 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout) {
});
};
+ self.sendSystemCommand = function (sessionId, command) {
+
+ if (!sessionId) {
+ throw new Error("null sessionId");
+ }
+
+ if (!command) {
+ throw new Error("null command");
+ }
+
+ var url = self.getUrl("Sessions/" + sessionId + "/System/" + command);
+
+ return self.ajax({
+ type: "POST",
+ url: url
+ });
+ };
+
+ self.sendMessageCommand = function (sessionId, options) {
+
+ if (!sessionId) {
+ throw new Error("null sessionId");
+ }
+
+ if (!options) {
+ throw new Error("null options");
+ }
+
+ var url = self.getUrl("Sessions/" + sessionId + "/Message", options);
+
+ return self.ajax({
+ type: "POST",
+ url: url
+ });
+ };
+
self.sendPlayStateCommand = function (sessionId, command, options) {
if (!sessionId) {
diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config
index 517b8c1d2..9f7b690af 100644
--- a/MediaBrowser.WebDashboard/packages.config
+++ b/MediaBrowser.WebDashboard/packages.config
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="MediaBrowser.ApiClient.Javascript" version="3.0.160" targetFramework="net45" />
+ <package id="MediaBrowser.ApiClient.Javascript" version="3.0.165" targetFramework="net45" />
<package id="ServiceStack.Common" version="3.9.58" targetFramework="net45" />
<package id="ServiceStack.Text" version="3.9.58" targetFramework="net45" />
</packages> \ No newline at end of file
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index 0c5360b49..744debbcd 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -237,7 +237,4 @@ Global
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(Performance) = preSolution
- HasPerformanceSessions = true
- EndGlobalSection
EndGlobal
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index f1c8251b2..9efed44dd 100644
--- a/Nuget/MediaBrowser.Common.Internal.nuspec
+++ b/Nuget/MediaBrowser.Common.Internal.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common.Internal</id>
- <version>3.0.183</version>
+ <version>3.0.186</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.183" />
+ <dependency id="MediaBrowser.Common" version="3.0.186" />
<dependency id="NLog" version="2.0.1.2" />
<dependency id="ServiceStack.Text" version="3.9.55" />
<dependency id="SimpleInjector" version="2.3.0" />
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index 0f334c9c1..cecd42cb7 100644
--- a/Nuget/MediaBrowser.Common.nuspec
+++ b/Nuget/MediaBrowser.Common.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
<metadata>
<id>MediaBrowser.Common</id>
- <version>3.0.183</version>
+ <version>3.0.186</version>
<title>MediaBrowser.Common</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index bcf772e8f..1cb8a8944 100644
--- a/Nuget/MediaBrowser.Server.Core.nuspec
+++ b/Nuget/MediaBrowser.Server.Core.nuspec
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>MediaBrowser.Server.Core</id>
- <version>3.0.183</version>
+ <version>3.0.186</version>
<title>Media Browser.Server.Core</title>
<authors>Media Browser Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Media Browser Server.</description>
<copyright>Copyright © Media Browser 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.183" />
+ <dependency id="MediaBrowser.Common" version="3.0.186" />
</dependencies>
</metadata>
<files>
diff --git a/README.md b/README.md
index 130151f20..532c8c5ad 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
Media Browser
============
-Media Browser Server is a media cataloging and transcoding server built on top of other popular open source technologies such as **Service Stack**, **jQuery** and **jQuery mobile**.
+Media Browser Server is a home media server built on top of other popular open source technologies such as **Service Stack**, **jQuery**, **jQuery mobile** and **Lucene .NET**.
It features a REST-based api with built-in documention to facilitate plugin development.