diff --git a/MediaBrowser.Api/AppThemeService.cs b/MediaBrowser.Api/AppThemeService.cs index 54141b3e22..4d8eed7ddb 100644 --- a/MediaBrowser.Api/AppThemeService.cs +++ b/MediaBrowser.Api/AppThemeService.cs @@ -9,16 +9,14 @@ using System.Linq; namespace MediaBrowser.Api { - [Route("/Themes", "GET")] - [Api(Description = "Gets a list of available themes for an app")] + [Route("/Themes", "GET", Summary = "Gets a list of available themes for an app")] public class GetAppThemes : IReturn> { [ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] public string App { get; set; } } - [Route("/Themes/Info", "GET")] - [Api(Description = "Gets an app theme")] + [Route("/Themes/Info", "GET", Summary = "Gets an app theme")] public class GetAppTheme : IReturn { [ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -28,8 +26,7 @@ namespace MediaBrowser.Api public string Name { get; set; } } - [Route("/Themes/Images", "GET")] - [Api(Description = "Gets an app theme")] + [Route("/Themes/Images", "GET", Summary = "Gets an app theme")] public class GetAppThemeImage { [ApiMember(Name = "App", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] @@ -45,12 +42,11 @@ namespace MediaBrowser.Api public string CacheTag { get; set; } } - [Route("/Themes", "POST")] - [Api(Description = "Saves a theme")] + [Route("/Themes", "POST", Summary = "Saves a theme")] public class SaveTheme : AppTheme, IReturnVoid { } - + public class AppThemeService : BaseApiService { private readonly IAppThemeManager _themeManager; diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 08686b43a8..707cfb4572 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; using ServiceStack.Web; using System; @@ -78,6 +79,20 @@ namespace MediaBrowser.Api return ToOptimizedResult(result); } + /// + /// Gets the session. + /// + /// The session manager. + /// SessionInfo. + protected SessionInfo GetSession(ISessionManager sessionManager) + { + var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); + + return sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && + string.Equals(i.Client, auth.Client) && + string.Equals(i.ApplicationVersion, auth.Version)); + } + /// /// To the cached result. /// diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 6007043503..b3191cd4b9 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -17,8 +17,7 @@ namespace MediaBrowser.Api /// /// Class GetConfiguration /// - [Route("/System/Configuration", "GET")] - [Api(("Gets application configuration"))] + [Route("/System/Configuration", "GET", Summary = "Gets application configuration")] public class GetConfiguration : IReturn { @@ -27,28 +26,24 @@ namespace MediaBrowser.Api /// /// Class UpdateConfiguration /// - [Route("/System/Configuration", "POST")] - [Api(("Updates application configuration"))] + [Route("/System/Configuration", "POST", Summary = "Updates application configuration")] public class UpdateConfiguration : ServerConfiguration, IReturnVoid { } - [Route("/System/Configuration/MetadataOptions/Default", "GET")] - [Api(("Gets a default MetadataOptions object"))] + [Route("/System/Configuration/MetadataOptions/Default", "GET", Summary = "Gets a default MetadataOptions object")] public class GetDefaultMetadataOptions : IReturn { } - [Route("/System/Configuration/MetadataPlugins", "GET")] - [Api(("Gets all available metadata plugins"))] + [Route("/System/Configuration/MetadataPlugins", "GET", Summary = "Gets all available metadata plugins")] public class GetMetadataPlugins : IReturn> { } - [Route("/System/Configuration/VideoImageExtraction", "POST")] - [Api(("Updates image extraction for all types"))] + [Route("/System/Configuration/VideoImageExtraction", "POST", Summary = "Updates image extraction for all types")] public class UpdateVideoImageExtraction : IReturnVoid { public bool Enabled { get; set; } diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index dba256418e..533a92fba8 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.Session; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -245,12 +246,13 @@ namespace MediaBrowser.Api.Library private readonly IDtoService _dtoService; private readonly IChannelManager _channelManager; + private readonly ISessionManager _sessionManager; /// /// Initializes a new instance of the class. /// public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, - IDtoService dtoService, IUserDataManager userDataManager, IChannelManager channelManager) + IDtoService dtoService, IUserDataManager userDataManager, IChannelManager channelManager, ISessionManager sessionManager) { _itemRepo = itemRepo; _libraryManager = libraryManager; @@ -258,6 +260,7 @@ namespace MediaBrowser.Api.Library _dtoService = dtoService; _userDataManager = userDataManager; _channelManager = channelManager; + _sessionManager = sessionManager; } public object Get(GetMediaFolders request) @@ -504,6 +507,13 @@ namespace MediaBrowser.Api.Library { var item = _dtoService.GetItemByDtoId(request.Id); + var session = GetSession(_sessionManager); + + if (!session.UserId.HasValue || !_userManager.GetUserById(session.UserId.Value).Configuration.EnableContentDeletion) + { + throw new UnauthorizedAccessException("This operation requires a logged in user with delete access."); + } + return _libraryManager.DeleteItem(item); } diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 204a7aab45..228dc378b5 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -17,8 +17,7 @@ namespace MediaBrowser.Api.Movies /// /// Class GetSimilarMovies /// - [Route("/Movies/{Id}/Similar", "GET")] - [Api(Description = "Finds movies and trailers similar to a given movie.")] + [Route("/Movies/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given movie.")] public class GetSimilarMovies : BaseGetSimilarItemsFromItem { [ApiMember(Name = "IncludeTrailers", Description = "Whether or not to include trailers within the results. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -30,8 +29,7 @@ namespace MediaBrowser.Api.Movies } } - [Route("/Movies/Recommendations", "GET")] - [Api(Description = "Gets movie recommendations")] + [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")] public class GetMovieRecommendations : IReturn, IHasItemFields { [ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index 0575066350..05e6a95774 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -10,8 +10,7 @@ namespace MediaBrowser.Api.Movies /// /// Class GetSimilarTrailers /// - [Route("/Trailers/{Id}/Similar", "GET")] - [Api(Description = "Finds movies and trailers similar to a given trailer.")] + [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] public class GetSimilarTrailers : BaseGetSimilarItemsFromItem { } diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index a8446a7ef2..9b9df3a92c 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -9,28 +9,24 @@ using System.Linq; namespace MediaBrowser.Api.Music { - [Route("/Songs/{Id}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a given song")] + [Route("/Songs/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given song")] public class GetInstantMixFromSong : BaseGetSimilarItemsFromItem { } - [Route("/Albums/{Id}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a given album")] + [Route("/Albums/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given album")] public class GetInstantMixFromAlbum : BaseGetSimilarItemsFromItem { } - [Route("/Artists/{Name}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a given artist")] + [Route("/Artists/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")] public class GetInstantMixFromArtist : BaseGetSimilarItems { [ApiMember(Name = "Name", Description = "The artist name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] public string Name { get; set; } } - [Route("/MusicGenres/{Name}/InstantMix", "GET")] - [Api(Description = "Creates an instant playlist based on a music genre")] + [Route("/MusicGenres/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a music genre")] public class GetInstantMixFromMusicGenre : BaseGetSimilarItems { [ApiMember(Name = "Name", Description = "The genre name", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] diff --git a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs index daa735fe53..80080fbb31 100644 --- a/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs +++ b/MediaBrowser.Api/ScheduledTasks/ScheduledTaskService.cs @@ -2,18 +2,17 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Model.Tasks; using ServiceStack; +using ServiceStack.Text.Controller; using System; using System.Collections.Generic; using System.Linq; -using ServiceStack.Text.Controller; namespace MediaBrowser.Api.ScheduledTasks { /// /// Class GetScheduledTask /// - [Route("/ScheduledTasks/{Id}", "GET")] - [Api(Description = "Gets a scheduled task, by Id")] + [Route("/ScheduledTasks/{Id}", "GET", Summary = "Gets a scheduled task, by Id")] public class GetScheduledTask : IReturn { /// @@ -27,8 +26,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class GetScheduledTasks /// - [Route("/ScheduledTasks", "GET")] - [Api(Description = "Gets scheduled tasks")] + [Route("/ScheduledTasks", "GET", Summary = "Gets scheduled tasks")] public class GetScheduledTasks : IReturn> { [ApiMember(Name = "IsHidden", Description = "Optional filter tasks that are hidden, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] @@ -38,8 +36,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class StartScheduledTask /// - [Route("/ScheduledTasks/Running/{Id}", "POST")] - [Api(Description = "Starts a scheduled task")] + [Route("/ScheduledTasks/Running/{Id}", "POST", Summary = "Starts a scheduled task")] public class StartScheduledTask : IReturnVoid { /// @@ -53,8 +50,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class StopScheduledTask /// - [Route("/ScheduledTasks/Running/{Id}", "DELETE")] - [Api(Description = "Stops a scheduled task")] + [Route("/ScheduledTasks/Running/{Id}", "DELETE", Summary = "Stops a scheduled task")] public class StopScheduledTask : IReturnVoid { /// @@ -68,8 +64,7 @@ namespace MediaBrowser.Api.ScheduledTasks /// /// Class UpdateScheduledTaskTriggers /// - [Route("/ScheduledTasks/{Id}/Triggers", "POST")] - [Api(Description = "Updates the triggers for a scheduled task")] + [Route("/ScheduledTasks/{Id}/Triggers", "POST", Summary = "Updates the triggers for a scheduled task")] public class UpdateScheduledTaskTriggers : List, IReturnVoid { /// diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index fc7fdd1600..7eff5054be 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -8,15 +8,13 @@ using ServiceStack; using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; namespace MediaBrowser.Api.UserLibrary { /// /// Class GetStudios /// - [Route("/Studios", "GET")] - [Api(Description = "Gets all studios from a given item, folder, or the entire library")] + [Route("/Studios", "GET", Summary = "Gets all studios from a given item, folder, or the entire library")] public class GetStudios : GetItemsByName { } @@ -24,8 +22,7 @@ namespace MediaBrowser.Api.UserLibrary /// /// Class GetStudio /// - [Route("/Studios/{Name}", "GET")] - [Api(Description = "Gets a studio, by name")] + [Route("/Studios/{Name}", "GET", Summary = "Gets a studio, by name")] public class GetStudio : IReturn { /// diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index c6051c02cc..a49f957f60 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -705,7 +705,7 @@ namespace MediaBrowser.Api.UserLibrary datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); } - var session = GetSession(); + var session = GetSession(_sessionManager); var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); @@ -719,15 +719,6 @@ namespace MediaBrowser.Api.UserLibrary return dto; } - private SessionInfo GetSession() - { - var auth = AuthorizationRequestFilterAttribute.GetAuthorization(Request); - - return _sessionManager.Sessions.First(i => string.Equals(i.DeviceId, auth.DeviceId) && - string.Equals(i.Client, auth.Client) && - string.Equals(i.ApplicationVersion, auth.Version)); - } - /// /// Posts the specified request. /// @@ -744,7 +735,7 @@ namespace MediaBrowser.Api.UserLibrary { CanSeek = request.CanSeek, Item = item, - SessionId = GetSession().Id, + SessionId = GetSession(_sessionManager).Id, QueueableMediaTypes = queueableMediaTypes.Split(',').ToList(), MediaSourceId = request.MediaSourceId }; @@ -768,7 +759,7 @@ namespace MediaBrowser.Api.UserLibrary PositionTicks = request.PositionTicks, IsMuted = request.IsMuted, IsPaused = request.IsPaused, - SessionId = GetSession().Id, + SessionId = GetSession(_sessionManager).Id, MediaSourceId = request.MediaSourceId }; @@ -787,7 +778,7 @@ namespace MediaBrowser.Api.UserLibrary var item = _dtoService.GetItemByDtoId(request.Id, user.Id); - var session = GetSession(); + var session = GetSession(_sessionManager); var info = new PlaybackStopInfo { @@ -817,7 +808,7 @@ namespace MediaBrowser.Api.UserLibrary { var user = _userManager.GetUserById(request.UserId); - var session = GetSession(); + var session = GetSession(_sessionManager); var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); diff --git a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs b/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs index 38139645eb..25acd613ca 100644 --- a/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs +++ b/MediaBrowser.Api/WebSocket/SessionInfoWebSocketListener.cs @@ -49,7 +49,7 @@ namespace MediaBrowser.Api.WebSocket /// Task{SystemInfo}. protected override Task> GetDataToSend(object state) { - return Task.FromResult(_sessionManager.Sessions.Select(_dtoService.GetSessionInfoDto)); + return Task.FromResult(_sessionManager.Sessions.Where(i => i.IsActive).Select(_dtoService.GetSessionInfoDto)); } } } diff --git a/MediaBrowser.Controller/Dlna/CodecProfile.cs b/MediaBrowser.Controller/Dlna/CodecProfile.cs index 2b9a40ea06..0f61cad98d 100644 --- a/MediaBrowser.Controller/Dlna/CodecProfile.cs +++ b/MediaBrowser.Controller/Dlna/CodecProfile.cs @@ -69,8 +69,6 @@ namespace MediaBrowser.Controller.Dlna VideoBitrate, VideoFramerate, VideoLevel, - VideoPacketLength, - VideoProfile, - VideoTimestamp + VideoProfile } } diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index e6a62c1812..0a34b80168 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -121,10 +121,7 @@ namespace MediaBrowser.Controller.Entities { _configuration = value; - if (value == null) - { - _configurationInitialized = false; - } + _configurationInitialized = value != null; } } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index 297f7a696b..2f4f09ad66 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -241,7 +241,13 @@ namespace MediaBrowser.Dlna.PlayTo _sessionManager.ReportCapabilities(sessionInfo.Id, new SessionCapabilities { - PlayableMediaTypes = new[] { MediaType.Audio, MediaType.Video, MediaType.Photo }, + PlayableMediaTypes = new[] + { + MediaType.Audio, + MediaType.Video, + MediaType.Photo + }, + SupportsFullscreenToggle = false }); diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs index 0dec9bbf3f..f79dc1e5fc 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItemFactory.cs @@ -191,9 +191,7 @@ namespace MediaBrowser.Dlna.PlayTo case ProfileConditionValue.AudioProfile: case ProfileConditionValue.Has64BitOffsets: case ProfileConditionValue.VideoBitDepth: - case ProfileConditionValue.VideoPacketLength: case ProfileConditionValue.VideoProfile: - case ProfileConditionValue.VideoTimestamp: { // Not supported yet break; @@ -461,12 +459,6 @@ namespace MediaBrowser.Dlna.PlayTo return videoStream == null ? null : videoStream.Width; case ProfileConditionValue.VideoLevel: return videoStream == null ? null : ConvertToLong(videoStream.Level); - case ProfileConditionValue.VideoPacketLength: - // TODO: Determine how to get this - return null; - case ProfileConditionValue.VideoTimestamp: - // TODO: Determine how to get this - return null; default: throw new InvalidOperationException("Unexpected Property"); } diff --git a/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs b/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs index b1ae21a437..42c788d381 100644 --- a/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs +++ b/MediaBrowser.Dlna/PlayTo/SsdpHttpClient.cs @@ -22,7 +22,11 @@ namespace MediaBrowser.Dlna.PlayTo _config = config; } - public async Task SendCommandAsync(string baseUrl, DeviceService service, string command, string postData, string header = null) + public async Task SendCommandAsync(string baseUrl, + DeviceService service, + string command, + string postData, + string header = null) { var serviceUrl = service.ControlUrl; if (!serviceUrl.StartsWith("/")) @@ -40,7 +44,12 @@ namespace MediaBrowser.Dlna.PlayTo } } - public async Task SubscribeAsync(string url, string ip, int port, string localIp, int eventport, int timeOut = 3600) + public async Task SubscribeAsync(string url, + string ip, + int port, + string localIp, + int eventport, + int timeOut = 3600) { var options = new HttpRequestOptions { @@ -59,7 +68,11 @@ namespace MediaBrowser.Dlna.PlayTo } } - public async Task RespondAsync(Uri url, string ip, int port, string localIp, int eventport, int timeOut = 20000) + public async Task RespondAsync(Uri url, + string ip, + int port, + string localIp, + int eventport) { var options = new HttpRequestOptions { @@ -97,7 +110,10 @@ namespace MediaBrowser.Dlna.PlayTo } } - private Task PostSoapDataAsync(string url, string soapAction, string postData, string header = null) + private Task PostSoapDataAsync(string url, + string soapAction, + string postData, + string header = null) { if (!soapAction.StartsWith("\"")) soapAction = "\"" + soapAction + "\""; diff --git a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs index a4855c94f2..30d787cf8f 100644 --- a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs +++ b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs @@ -1,8 +1,6 @@ -using MediaBrowser.Controller.Dlna; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.Globalization; -using System.Linq; namespace MediaBrowser.Dlna.PlayTo { diff --git a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs index 214b6f814d..5eb364080f 100644 --- a/MediaBrowser.Dlna/Profiles/DefaultProfile.cs +++ b/MediaBrowser.Dlna/Profiles/DefaultProfile.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Dlna.Profiles { public DefaultProfile() { - Name = "Generic Device"; + Name = "Media Browser"; ProtocolInfo = "DLNA"; diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs index 042cc0a965..4cf108172d 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2010Profile.cs @@ -96,13 +96,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="192"}, - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoTimestamp, Value="1"} - } + Type = DlnaProfileType.Video }, new MediaProfile @@ -112,12 +106,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="188"} - } + Type = DlnaProfileType.Video }, new MediaProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs index 401c40c362..ec10f71aa3 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2011Profile.cs @@ -138,13 +138,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="192"}, - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoTimestamp, Value="1"} - } + Type = DlnaProfileType.Video }, new MediaProfile @@ -154,12 +148,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="188"} - } + Type = DlnaProfileType.Video }, new MediaProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs index 2d24c406e3..21efc3b457 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2012Profile.cs @@ -126,13 +126,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="192"}, - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoTimestamp, Value="1"} - } + Type = DlnaProfileType.Video }, new MediaProfile @@ -142,12 +136,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="188"} - } + Type = DlnaProfileType.Video }, new MediaProfile diff --git a/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs b/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs index 10f7129581..b247c39ac9 100644 --- a/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs +++ b/MediaBrowser.Dlna/Profiles/SonyBravia2013Profile.cs @@ -182,13 +182,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/vnd.dlna.mpeg-tts", OrgPn="AVC_TS_HD_24_AC3_T,AVC_TS_HD_50_AC3_T,AVC_TS_HD_60_AC3_T,AVC_TS_HD_EU_T", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="192"}, - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoTimestamp, Value="1"} - } + Type = DlnaProfileType.Video }, new MediaProfile @@ -198,12 +192,7 @@ namespace MediaBrowser.Dlna.Profiles AudioCodec="ac3,aac,mp3", MimeType = "video/mpeg", OrgPn="AVC_TS_HD_24_AC3_ISO,AVC_TS_HD_50_AC3_ISO,AVC_TS_HD_60_AC3_ISO,AVC_TS_HD_EU_ISO", - Type = DlnaProfileType.Video, - - Conditions = new [] - { - new ProfileCondition{ Condition= ProfileConditionType.Equals, Property= ProfileConditionValue.VideoPacketLength, Value="188"} - } + Type = DlnaProfileType.Video }, new MediaProfile diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 1f304112f5..d0caa3ad20 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -57,6 +57,12 @@ namespace MediaBrowser.Model.Configuration /// The item by name path. public string ItemsByNamePath { get; set; } + /// + /// Gets or sets the metadata path. + /// + /// The metadata path. + public string MetadataPath { get; set; } + /// /// Gets or sets the display name of the season zero. /// diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index 2145860c73..10f984f88e 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -52,6 +52,7 @@ namespace MediaBrowser.Model.Configuration public bool EnableLiveTvAccess { get; set; } public bool EnableMediaPlayback { get; set; } + public bool EnableContentDeletion { get; set; } public string[] BlockedMediaFolders { get; set; } @@ -63,8 +64,9 @@ namespace MediaBrowser.Model.Configuration public UserConfiguration() { IsAdministrator = true; - EnableRemoteControlOfOtherUsers = true; + EnableRemoteControlOfOtherUsers = true; + EnableContentDeletion = true; EnableLiveTvManagement = true; EnableMediaPlayback = true; EnableLiveTvAccess = true; diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs index 044973064e..a27fa057ca 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs @@ -148,7 +148,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrWhiteSpace(composer)) { - foreach (var person in Split(composer)) + foreach (var person in Split(composer, false)) { audio.AddPerson(new PersonInfo { Name = person, Type = PersonType.Composer }); } @@ -221,12 +221,15 @@ namespace MediaBrowser.Providers.MediaInfo /// Splits the specified val. /// /// The val. + /// if set to true [allow comma delimiter]. /// System.String[][]. - private IEnumerable Split(string val) + private IEnumerable Split(string val, bool allowCommaDelimiter) { // Only use the comma as a delimeter if there are no slashes or pipes. // We want to be careful not to split names that have commas in them - var delimeter = _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? _nameDelimiters : new[] { ',' }; + var delimeter = !allowCommaDelimiter || _nameDelimiters.Any(i => val.IndexOf(i) != -1) ? + _nameDelimiters : + new[] { ',' }; return val.Split(delimeter, StringSplitOptions.RemoveEmptyEntries) .Where(i => !string.IsNullOrWhiteSpace(i)) @@ -312,7 +315,7 @@ namespace MediaBrowser.Providers.MediaInfo if (!string.IsNullOrEmpty(val)) { // Sometimes the artist name is listed here, account for that - var studios = Split(val).Where(i => !audio.HasArtist(i)); + var studios = Split(val, true).Where(i => !audio.HasArtist(i)); foreach (var studio in studios) { @@ -334,7 +337,7 @@ namespace MediaBrowser.Providers.MediaInfo { audio.Genres.Clear(); - foreach (var genre in Split(val)) + foreach (var genre in Split(val, true)) { audio.AddGenre(genre); } diff --git a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs index 415205cb11..cb3621fd17 100644 --- a/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs +++ b/MediaBrowser.Server.Implementations/Configuration/ServerConfigurationManager.cs @@ -26,6 +26,7 @@ namespace MediaBrowser.Server.Implementations.Configuration { UpdateItemsByNamePath(); UpdateTranscodingTempPath(); + UpdateMetadataPath(); } /// @@ -76,6 +77,16 @@ namespace MediaBrowser.Server.Implementations.Configuration Configuration.ItemsByNamePath; } + /// + /// Updates the metadata path. + /// + private void UpdateMetadataPath() + { + ((ServerApplicationPaths)ApplicationPaths).InternalMetadataPath = string.IsNullOrEmpty(Configuration.MetadataPath) ? + null : + Configuration.MetadataPath; + } + /// /// Updates the transcoding temporary path. /// @@ -98,6 +109,7 @@ namespace MediaBrowser.Server.Implementations.Configuration ValidateItemByNamePath(newConfig); ValidateTranscodingTempPath(newConfig); ValidatePathSubstitutions(newConfig); + ValidateMetadataPath(newConfig); base.ReplaceConfiguration(newConfiguration); } @@ -166,5 +178,25 @@ namespace MediaBrowser.Server.Implementations.Configuration } } } + + /// + /// Validates the metadata path. + /// + /// The new configuration. + /// + private void ValidateMetadataPath(ServerConfiguration newConfig) + { + var newPath = newConfig.MetadataPath; + + if (!string.IsNullOrWhiteSpace(newPath) + && !string.Equals(Configuration.MetadataPath ?? string.Empty, newPath)) + { + // Validate + if (!Directory.Exists(newPath)) + { + throw new DirectoryNotFoundException(string.Format("{0} does not exist.", newPath)); + } + } + } } } diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 06028d37ec..2ee843f09b 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -260,6 +260,8 @@ namespace MediaBrowser.Server.Implementations.Library public event EventHandler> UserCreated; + private readonly SemaphoreSlim _userListLock = new SemaphoreSlim(1, 1); + /// /// Creates the user. /// @@ -279,19 +281,28 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentException(string.Format("A user with the name '{0}' already exists.", name)); } - var user = InstantiateNewUser(name); + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); - var list = Users.ToList(); - list.Add(user); - Users = list; + try + { + var user = InstantiateNewUser(name); - user.DateLastSaved = DateTime.UtcNow; + var list = Users.ToList(); + list.Add(user); + Users = list; - await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false); + user.DateLastSaved = DateTime.UtcNow; - EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + await UserRepository.SaveUser(user, CancellationToken.None).ConfigureAwait(false); - return user; + EventHelper.QueueEventIfNotNull(UserCreated, this, new GenericEventArgs { Argument = user }, _logger); + + return user; + } + finally + { + _userListLock.Release(); + } } /// @@ -325,23 +336,32 @@ namespace MediaBrowser.Server.Implementations.Library throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name)); } - await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false); - - var path = user.ConfigurationFilePath; + await _userListLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); try { - File.Delete(path); + await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false); + + var path = user.ConfigurationFilePath; + + try + { + File.Delete(path); + } + catch (IOException ex) + { + _logger.ErrorException("Error deleting file {0}", ex, path); + } + + // Force this to be lazy loaded again + Users = await LoadUsers().ConfigureAwait(false); + + OnUserDeleted(user); } - catch (IOException ex) + finally { - _logger.ErrorException("Error deleting file {0}", ex, path); + _userListLock.Release(); } - - // Force this to be lazy loaded again - Users = await LoadUsers().ConfigureAwait(false); - - OnUserDeleted(user); } /// diff --git a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs index c36c49df0e..df2a5f83c2 100644 --- a/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs +++ b/MediaBrowser.Server.Implementations/ServerApplicationPaths.cs @@ -1,6 +1,6 @@ -using System; -using MediaBrowser.Common.Implementations; +using MediaBrowser.Common.Implementations; using MediaBrowser.Controller; +using System; using System.IO; namespace MediaBrowser.Server.Implementations @@ -239,14 +239,20 @@ namespace MediaBrowser.Server.Implementations } } + private string _internalMetadataPath; public string InternalMetadataPath { get { - return Path.Combine(DataPath, "metadata"); + return _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata")); + } + set + { + _internalMetadataPath = value; } } + public string GetInternalMetadataPath(Guid id) { var idString = id.ToString("N"); diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index e49244edf0..351cbcfc86 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -795,7 +795,7 @@ namespace MediaBrowser.ServerApplication list.Add(typeof(ApiEntryPoint).Assembly); // Include composable parts in the Dashboard assembly - list.Add(typeof(DashboardInfo).Assembly); + list.Add(typeof(DashboardService).Assembly); // Include composable parts in the Model assembly list.Add(typeof(SystemInfo).Assembly); diff --git a/MediaBrowser.WebDashboard/Api/DashboardInfo.cs b/MediaBrowser.WebDashboard/Api/DashboardInfo.cs deleted file mode 100644 index 78cc5a758c..0000000000 --- a/MediaBrowser.WebDashboard/Api/DashboardInfo.cs +++ /dev/null @@ -1,39 +0,0 @@ -using MediaBrowser.Model.Session; -using MediaBrowser.Model.System; -using MediaBrowser.Model.Tasks; -using System; -using System.Collections.Generic; - -namespace MediaBrowser.WebDashboard.Api -{ - /// - /// Class DashboardInfo - /// - public class DashboardInfo - { - /// - /// Gets or sets the system info. - /// - /// The system info. - public SystemInfo SystemInfo { get; set; } - - /// - /// Gets or sets the running tasks. - /// - /// The running tasks. - public List RunningTasks { get; set; } - - /// - /// Gets or sets the application update task id. - /// - /// The application update task id. - public Guid ApplicationUpdateTaskId { get; set; } - - /// - /// Gets or sets the active connections. - /// - /// The active connections. - public List ActiveConnections { get; set; } - } - -} diff --git a/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs b/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs deleted file mode 100644 index af0f9e3a05..0000000000 --- a/MediaBrowser.WebDashboard/Api/DashboardInfoWebSocketListener.cs +++ /dev/null @@ -1,62 +0,0 @@ -using MediaBrowser.Common.Net; -using MediaBrowser.Common.ScheduledTasks; -using MediaBrowser.Controller; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Session; -using MediaBrowser.Model.Logging; -using System.Threading.Tasks; - -namespace MediaBrowser.WebDashboard.Api -{ - /// - /// Class DashboardInfoWebSocketListener - /// - class DashboardInfoWebSocketListener : BasePeriodicWebSocketListener - { - /// - /// Gets the name. - /// - /// The name. - protected override string Name - { - get { return "DashboardInfo"; } - } - - private readonly IServerApplicationHost _appHost; - - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private readonly ITaskManager _taskManager; - - private readonly ISessionManager _sessionManager; - private readonly IDtoService _dtoService; - - /// - /// Initializes a new instance of the class. - /// - /// The app host. - /// The logger. - /// The task manager. - /// The session manager. - public DashboardInfoWebSocketListener(IServerApplicationHost appHost, ILogger logger, ITaskManager taskManager, ISessionManager sessionManager, IDtoService dtoService) - : base(logger) - { - _appHost = appHost; - _taskManager = taskManager; - _sessionManager = sessionManager; - _dtoService = dtoService; - } - - /// - /// Gets the data to send. - /// - /// The state. - /// Task{IEnumerable{TaskInfo}}. - protected override Task GetDataToSend(object state) - { - return Task.FromResult(DashboardService.GetDashboardInfo(_appHost, _taskManager, _sessionManager, _dtoService)); - } - } -} diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 88f86632b7..2c42cced24 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -1,16 +1,13 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; -using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; -using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Plugins; -using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; -using MediaBrowser.Model.Tasks; using ServiceStack; +using ServiceStack.Web; using System; using System.Collections.Generic; using System.IO; @@ -18,7 +15,6 @@ using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; -using ServiceStack.Web; namespace MediaBrowser.WebDashboard.Api { @@ -66,14 +62,6 @@ namespace MediaBrowser.WebDashboard.Api public string V { get; set; } } - /// - /// Class GetDashboardInfo - /// - [Route("/dashboard/dashboardInfo", "GET")] - public class GetDashboardInfo : IReturn - { - } - /// /// Class DashboardService /// @@ -97,12 +85,6 @@ namespace MediaBrowser.WebDashboard.Api /// The request context. public IRequest Request { get; set; } - /// - /// Gets or sets the task manager. - /// - /// The task manager. - private readonly ITaskManager _taskManager; - /// /// The _app host /// @@ -113,24 +95,18 @@ namespace MediaBrowser.WebDashboard.Api /// private readonly IServerConfigurationManager _serverConfigurationManager; - private readonly ISessionManager _sessionManager; - private readonly IDtoService _dtoService; private readonly IFileSystem _fileSystem; /// /// Initializes a new instance of the class. /// - /// The task manager. /// The app host. /// The server configuration manager. - /// The session manager. - public DashboardService(ITaskManager taskManager, IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, ISessionManager sessionManager, IDtoService dtoService, IFileSystem fileSystem) + /// The file system. + public DashboardService(IServerApplicationHost appHost, IServerConfigurationManager serverConfigurationManager, IFileSystem fileSystem) { - _taskManager = taskManager; _appHost = appHost; _serverConfigurationManager = serverConfigurationManager; - _sessionManager = sessionManager; - _dtoService = dtoService; _fileSystem = fileSystem; } @@ -163,45 +139,6 @@ namespace MediaBrowser.WebDashboard.Api return Path.Combine(DashboardUIPath, virtualPath.Replace('/', Path.DirectorySeparatorChar)); } - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetDashboardInfo request) - { - var result = GetDashboardInfo(_appHost, _taskManager, _sessionManager, _dtoService); - - return ResultFactory.GetOptimizedResult(Request, result); - } - - /// - /// Gets the dashboard info. - /// - /// The app host. - /// The task manager. - /// The connection manager. - /// DashboardInfo. - public static DashboardInfo GetDashboardInfo(IServerApplicationHost appHost, - ITaskManager taskManager, - ISessionManager connectionManager, IDtoService dtoService) - { - var connections = connectionManager.Sessions.Where(i => i.IsActive).ToList(); - - return new DashboardInfo - { - SystemInfo = appHost.GetSystemInfo(), - - RunningTasks = taskManager.ScheduledTasks.Where(i => i.State == TaskState.Running || i.State == TaskState.Cancelling) - .Select(ScheduledTaskHelpers.GetTaskInfo) - .ToList(), - - ApplicationUpdateTaskId = taskManager.ScheduledTasks.First(t => t.ScheduledTask.GetType().Name.Equals("SystemUpdateTask", StringComparison.OrdinalIgnoreCase)).Id, - - ActiveConnections = connections.Select(dtoService.GetSessionInfoDto).ToList() - }; - } - /// /// Gets the specified request. /// @@ -473,6 +410,7 @@ namespace MediaBrowser.WebDashboard.Api "alphapicker.js", "addpluginpage.js", "advancedconfigurationpage.js", + "advancedpaths.js", "advancedserversettings.js", "metadataadvanced.js", "appsplayback.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index a0dbae3d4c..8351afbae4 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -63,9 +63,7 @@ Properties\SharedVersion.cs - - @@ -85,6 +83,9 @@ + + PreserveNewest + PreserveNewest @@ -483,6 +484,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest