aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MediaBrowser.Api/ConnectService.cs34
-rw-r--r--MediaBrowser.Api/ItemUpdateService.cs5
-rw-r--r--MediaBrowser.Api/Library/LibraryService.cs7
-rw-r--r--MediaBrowser.Api/LiveTv/LiveTvService.cs94
-rw-r--r--MediaBrowser.Api/Movies/MoviesService.cs75
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs139
-rw-r--r--MediaBrowser.Api/Playback/MediaInfoService.cs7
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs2
-rw-r--r--MediaBrowser.Api/TvShowsService.cs2
-rw-r--r--MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs1
-rw-r--r--MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs7
-rw-r--r--MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs4
-rw-r--r--MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs10
-rw-r--r--MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs16
-rw-r--r--MediaBrowser.Controller/Channels/IChannelManager.cs2
-rw-r--r--MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs (renamed from MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs)2
-rw-r--r--MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs14
-rw-r--r--MediaBrowser.Controller/Dlna/ISsdpHandler.cs1
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs5
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs12
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs31
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs57
-rw-r--r--MediaBrowser.Controller/Entities/Trailer.cs2
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs2
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs5
-rw-r--r--MediaBrowser.Controller/Library/NameExtensions.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs15
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs5
-rw-r--r--MediaBrowser.Controller/LiveTv/ProgramInfo.cs9
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs16
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj3
-rw-r--r--MediaBrowser.Controller/Power/IPowerManagement.cs13
-rw-r--r--MediaBrowser.Controller/Providers/BaseItemXmlParser.cs165
-rw-r--r--MediaBrowser.Controller/Session/AuthenticationRequest.cs1
-rw-r--r--MediaBrowser.Dlna/Main/DlnaEntryPoint.cs114
-rw-r--r--MediaBrowser.Dlna/MediaBrowser.Dlna.csproj14
-rw-r--r--MediaBrowser.Dlna/PlayTo/Device.cs2
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToController.cs13
-rw-r--r--MediaBrowser.Dlna/PlayTo/PlayToManager.cs26
-rw-r--r--MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs2
-rw-r--r--MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs6
-rw-r--r--MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs2
-rw-r--r--MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs2
-rw-r--r--MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs2
-rw-r--r--MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs2
-rw-r--r--MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs10
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml2
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml6
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml2
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml2
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml2
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml2
-rw-r--r--MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml10
-rw-r--r--MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs217
-rw-r--r--MediaBrowser.Dlna/Ssdp/SsdpHandler.cs383
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj2
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs10
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs48
-rw-r--r--MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs4
-rw-r--r--MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs44
-rw-r--r--MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs6
-rw-r--r--MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs121
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs86
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs8
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs5
-rw-r--r--MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj3
-rw-r--r--MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj11
-rw-r--r--MediaBrowser.Model/ApiClient/ServerCredentials.cs2
-rw-r--r--MediaBrowser.Model/Configuration/AutoOnOff.cs10
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs3
-rw-r--r--MediaBrowser.Model/Configuration/LibraryOptions.cs3
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs13
-rw-r--r--MediaBrowser.Model/Configuration/UserConfiguration.cs1
-rw-r--r--MediaBrowser.Model/Dto/BaseItemDto.cs1
-rw-r--r--MediaBrowser.Model/Dto/ItemCounts.cs1
-rw-r--r--MediaBrowser.Model/LiveTv/LiveTvOptions.cs2
-rw-r--r--MediaBrowser.Model/LiveTv/ProgramAudio.cs3
-rw-r--r--MediaBrowser.Model/LiveTv/RecordingQuery.cs4
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj1
-rw-r--r--MediaBrowser.Model/System/SystemInfo.cs2
-rw-r--r--MediaBrowser.Mono.sln18
-rw-r--r--MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs14
-rw-r--r--MediaBrowser.Providers/Manager/ItemImageProvider.cs24
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs50
-rw-r--r--MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs9
-rw-r--r--MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs30
-rw-r--r--MediaBrowser.Providers/Music/AudioMetadataService.cs3
-rw-r--r--MediaBrowser.Providers/TV/DummySeasonProvider.cs18
-rw-r--r--MediaBrowser.Server.Implementations/Channels/ChannelManager.cs31
-rw-r--r--MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs1
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs26
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs66
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs6
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs34
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs42
-rw-r--r--MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs9
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs59
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs15
-rw-r--r--MediaBrowser.Server.Implementations/Library/UserViewManager.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs4
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs35
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs153
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs93
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs26
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs26
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs125
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs188
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs10
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs9
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs18
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs79
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs68
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs59
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs203
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs37
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs68
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs105
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs57
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs65
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs60
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs160
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs116
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs96
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs25
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs90
-rw-r--r--MediaBrowser.Server.Implementations/Localization/Ratings/de.txt5
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj22
-rw-r--r--MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs65
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs15
-rw-r--r--MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs2
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncManager.cs5
-rw-r--r--MediaBrowser.Server.Implementations/Udp/UdpServer.cs65
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs62
-rw-r--r--MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs16
-rw-r--r--MediaBrowser.Server.Implementations/packages.config1
-rw-r--r--MediaBrowser.Server.Mac/Emby.Server.Mac.csproj59
-rw-r--r--MediaBrowser.Server.Mac/Native/BaseMonoApp.cs10
-rw-r--r--MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj5
-rw-r--r--MediaBrowser.Server.Mono/Native/BaseMonoApp.cs18
-rw-r--r--MediaBrowser.Server.Mono/app.config14
-rw-r--r--MediaBrowser.Server.Startup.Common/ApplicationHost.cs82
-rw-r--r--MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs5
-rw-r--r--MediaBrowser.Server.Startup.Common/INativeApp.cs11
-rw-r--r--MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj5
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs40
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs43
-rw-r--r--MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs52
-rw-r--r--MediaBrowser.ServerApplication/MainStartup.cs30
-rw-r--r--MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj2
-rw-r--r--MediaBrowser.ServerApplication/Native/LoopbackUtil.cs358
-rw-r--r--MediaBrowser.ServerApplication/Native/RegisterServer.bat2
-rw-r--r--MediaBrowser.ServerApplication/Native/WindowsApp.cs78
-rw-r--r--MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs94
-rw-r--r--MediaBrowser.ServerApplication/ServerNotifyIcon.cs15
-rw-r--r--MediaBrowser.WebDashboard/Api/DashboardService.cs38
-rw-r--r--MediaBrowser.WebDashboard/Api/PackageCreator.cs7
-rw-r--r--MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj21
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs152
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs4
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs7
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs6
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs40
-rw-r--r--MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs18
-rw-r--r--MediaBrowser.sln34
-rw-r--r--Mono.Nat/AbstractNatDevice.cs97
-rw-r--r--Mono.Nat/AsyncResults/AsyncResult.cs71
-rw-r--r--Mono.Nat/Enums/MapState.cs36
-rw-r--r--Mono.Nat/Enums/ProtocolType.cs36
-rw-r--r--Mono.Nat/EventArgs/DeviceEventArgs.cs45
-rw-r--r--Mono.Nat/Exceptions/MappingException.cs87
-rw-r--r--Mono.Nat/IMapper.cs50
-rw-r--r--Mono.Nat/INatDevice.cs62
-rw-r--r--Mono.Nat/ISearcher.cs51
-rw-r--r--Mono.Nat/Mapping.cs123
-rw-r--r--Mono.Nat/Mono.Nat.csproj104
-rw-r--r--Mono.Nat/NatProtocol.cs9
-rw-r--r--Mono.Nat/NatUtility.cs264
-rw-r--r--Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs52
-rw-r--r--Mono.Nat/Pmp/Mappers/PmpMapper.cs83
-rw-r--r--Mono.Nat/Pmp/Pmp.cs118
-rw-r--r--Mono.Nat/Pmp/PmpConstants.cs56
-rw-r--r--Mono.Nat/Pmp/PmpNatDevice.cs347
-rw-r--r--Mono.Nat/Pmp/Searchers/PmpSearcher.cs149
-rw-r--r--Mono.Nat/Properties/AssemblyInfo.cs31
-rw-r--r--Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs56
-rw-r--r--Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs75
-rw-r--r--Mono.Nat/Upnp/Mappers/UpnpMapper.cs110
-rw-r--r--Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/ErrorMessage.cs63
-rw-r--r--Mono.Nat/Upnp/Messages/GetServicesMessage.cs62
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs75
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs57
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs51
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs55
-rw-r--r--Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs60
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs46
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs44
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs53
-rw-r--r--Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs108
-rw-r--r--Mono.Nat/Upnp/Messages/UpnpMessage.cs132
-rw-r--r--Mono.Nat/Upnp/Searchers/UpnpSearcher.cs287
-rw-r--r--Mono.Nat/Upnp/Upnp.cs83
-rw-r--r--Mono.Nat/Upnp/UpnpNatDevice.cs651
-rw-r--r--Nuget/MediaBrowser.Common.Internal.nuspec4
-rw-r--r--Nuget/MediaBrowser.Common.nuspec2
-rw-r--r--Nuget/MediaBrowser.Server.Core.nuspec4
233 files changed, 7818 insertions, 2241 deletions
diff --git a/MediaBrowser.Api/ConnectService.cs b/MediaBrowser.Api/ConnectService.cs
index 4bcd33d9e..494a6e756 100644
--- a/MediaBrowser.Api/ConnectService.cs
+++ b/MediaBrowser.Api/ConnectService.cs
@@ -7,6 +7,7 @@ using ServiceStack;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Session;
namespace MediaBrowser.Api
{
@@ -76,12 +77,12 @@ namespace MediaBrowser.Api
public class ConnectService : BaseApiService
{
private readonly IConnectManager _connectManager;
- private readonly IUserManager _userManager;
+ private readonly ISessionManager _sessionManager;
- public ConnectService(IConnectManager connectManager, IUserManager userManager)
+ public ConnectService(IConnectManager connectManager, ISessionManager sessionManager)
{
_connectManager = connectManager;
- _userManager = userManager;
+ _sessionManager = sessionManager;
}
public object Post(CreateConnectLink request)
@@ -141,10 +142,33 @@ namespace MediaBrowser.Api
throw new ResourceNotFoundException();
}
+ var auth = AuthorizationContext.GetAuthorizationInfo(Request);
+
+ if (string.IsNullOrWhiteSpace(auth.Client))
+ {
+ return ToOptimizedResult(new ConnectAuthenticationExchangeResult
+ {
+ AccessToken = user.ConnectAccessKey,
+ LocalUserId = user.Id.ToString("N")
+ });
+ }
+
+ var session = await _sessionManager.CreateNewSession(new AuthenticationRequest
+ {
+ App = auth.Client,
+ AppVersion = auth.Version,
+ DeviceId = auth.DeviceId,
+ DeviceName = auth.Device,
+ RemoteEndPoint = Request.RemoteIp,
+ Username = user.Name,
+ UserId = user.Id.ToString("N")
+
+ }).ConfigureAwait(false);
+
return ToOptimizedResult(new ConnectAuthenticationExchangeResult
{
- AccessToken = user.ConnectAccessKey,
- LocalUserId = user.Id.ToString("N")
+ AccessToken = session.AccessToken,
+ LocalUserId = session.User.Id
});
}
}
diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs
index 2778cfe29..cda7ce0c6 100644
--- a/MediaBrowser.Api/ItemUpdateService.cs
+++ b/MediaBrowser.Api/ItemUpdateService.cs
@@ -70,12 +70,13 @@ namespace MediaBrowser.Api
Cultures = _localizationManager.GetCultures().ToList()
};
- if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
+ if (!item.IsVirtualItem && !(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName) &&
+ item.SourceType == SourceType.Library)
{
var inheritedContentType = _libraryManager.GetInheritedContentType(item);
var configuredContentType = _libraryManager.GetConfiguredContentType(item);
- if (string.IsNullOrWhiteSpace(inheritedContentType) || string.Equals(inheritedContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || !string.IsNullOrWhiteSpace(configuredContentType))
+ if (string.IsNullOrWhiteSpace(inheritedContentType) || !string.IsNullOrWhiteSpace(configuredContentType))
{
info.ContentTypeOptions = GetContentTypeOptions(true);
info.ContentType = configuredContentType;
diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs
index 14a771db0..c6b637f01 100644
--- a/MediaBrowser.Api/Library/LibraryService.cs
+++ b/MediaBrowser.Api/Library/LibraryService.cs
@@ -25,6 +25,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Configuration;
namespace MediaBrowser.Api.Library
{
@@ -288,12 +289,13 @@ namespace MediaBrowser.Api.Library
private readonly ITVSeriesManager _tvManager;
private readonly ILibraryMonitor _libraryMonitor;
private readonly IFileSystem _fileSystem;
+ private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="LibraryService" /> class.
/// </summary>
public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager,
- IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem)
+ IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor, IFileSystem fileSystem, IServerConfigurationManager config)
{
_itemRepo = itemRepo;
_libraryManager = libraryManager;
@@ -307,6 +309,7 @@ namespace MediaBrowser.Api.Library
_tvManager = tvManager;
_libraryMonitor = libraryMonitor;
_fileSystem = fileSystem;
+ _config = config;
}
public object Get(GetSimilarItems request)
@@ -377,7 +380,7 @@ namespace MediaBrowser.Api.Library
if (item is Movie || (program != null && program.IsMovie) || item is Trailer)
{
- return new MoviesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService)
+ return new MoviesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _config)
{
AuthorizationContext = AuthorizationContext,
Logger = Logger,
diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs
index 545ac162f..3ad0ec1ba 100644
--- a/MediaBrowser.Api/LiveTv/LiveTvService.cs
+++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs
@@ -155,12 +155,72 @@ namespace MediaBrowser.Api.LiveTv
[ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
public bool? EnableUserData { get; set; }
+ public bool? IsMovie { get; set; }
+ public bool? IsSeries { get; set; }
+ public bool? IsKids { get; set; }
+ public bool? IsSports { get; set; }
+
public GetRecordings()
{
EnableTotalRecordCount = true;
}
}
+ [Route("/LiveTv/Recordings/Series", "GET", Summary = "Gets live tv recordings")]
+ [Authenticated]
+ public class GetRecordingSeries : IReturn<QueryResult<BaseItemDto>>, IHasDtoOptions
+ {
+ [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string ChannelId { get; set; }
+
+ [ApiMember(Name = "UserId", Description = "Optional filter by user and attach user data.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string UserId { get; set; }
+
+ [ApiMember(Name = "GroupId", Description = "Optional filter by recording group.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string GroupId { get; set; }
+
+ [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+ public int? StartIndex { get; set; }
+
+ [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+ public int? Limit { get; set; }
+
+ [ApiMember(Name = "Status", Description = "Optional filter by recording status.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public RecordingStatus? Status { get; set; }
+
+ [ApiMember(Name = "Status", Description = "Optional filter by recordings that are in progress, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+ public bool? IsInProgress { get; set; }
+
+ [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string SeriesTimerId { get; set; }
+
+ [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+ public bool? EnableImages { get; set; }
+
+ [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
+ public int? ImageTypeLimit { get; set; }
+
+ [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+ public string EnableImageTypes { get; set; }
+
+ /// <summary>
+ /// Fields to return within the items, in addition to basic information
+ /// </summary>
+ /// <value>The fields.</value>
+ [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+ public string Fields { get; set; }
+
+ public bool EnableTotalRecordCount { get; set; }
+
+ [ApiMember(Name = "EnableUserData", Description = "Optional, include user data", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")]
+ public bool? EnableUserData { get; set; }
+
+ public GetRecordingSeries()
+ {
+ EnableTotalRecordCount = true;
+ }
+ }
+
[Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")]
[Authenticated]
public class GetRecordingGroups : IReturn<QueryResult<BaseItemDto>>
@@ -535,12 +595,6 @@ namespace MediaBrowser.Api.LiveTv
[Authenticated]
public class GetLiveTvRegistrationInfo : IReturn<MBRegistrationRecord>
{
- [ApiMember(Name = "ChannelId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ChannelId { get; set; }
-
- [ApiMember(Name = "ProgramId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
- public string ProgramId { get; set; }
-
[ApiMember(Name = "Feature", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
public string Feature { get; set; }
}
@@ -592,7 +646,7 @@ namespace MediaBrowser.Api.LiveTv
public async Task<object> Get(GetLiveTvRegistrationInfo request)
{
- var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false);
+ var result = await _liveTvManager.GetRegistrationInfo(request.Feature).ConfigureAwait(false);
return ToOptimizedResult(result);
}
@@ -863,6 +917,32 @@ namespace MediaBrowser.Api.LiveTv
Status = request.Status,
SeriesTimerId = request.SeriesTimerId,
IsInProgress = request.IsInProgress,
+ EnableTotalRecordCount = request.EnableTotalRecordCount,
+ IsMovie = request.IsMovie,
+ IsSeries = request.IsSeries,
+ IsKids = request.IsKids,
+ IsSports = request.IsSports
+
+ }, options, CancellationToken.None).ConfigureAwait(false);
+
+ return ToOptimizedResult(result);
+ }
+
+ public async Task<object> Get(GetRecordingSeries request)
+ {
+ var options = GetDtoOptions(request);
+ options.DeviceId = AuthorizationContext.GetAuthorizationInfo(Request).DeviceId;
+
+ var result = await _liveTvManager.GetRecordingSeries(new RecordingQuery
+ {
+ ChannelId = request.ChannelId,
+ UserId = request.UserId,
+ GroupId = request.GroupId,
+ StartIndex = request.StartIndex,
+ Limit = request.Limit,
+ Status = request.Status,
+ SeriesTimerId = request.SeriesTimerId,
+ IsInProgress = request.IsInProgress,
EnableTotalRecordCount = request.EnableTotalRecordCount
}, options, CancellationToken.None).ConfigureAwait(false);
diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs
index 66f2fac81..f153a0475 100644
--- a/MediaBrowser.Api/Movies/MoviesService.cs
+++ b/MediaBrowser.Api/Movies/MoviesService.cs
@@ -14,6 +14,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.LiveTv;
namespace MediaBrowser.Api.Movies
@@ -88,6 +89,7 @@ namespace MediaBrowser.Api.Movies
private readonly IItemRepository _itemRepo;
private readonly IDtoService _dtoService;
+ private readonly IServerConfigurationManager _config;
/// <summary>
/// Initializes a new instance of the <see cref="MoviesService" /> class.
@@ -97,13 +99,14 @@ namespace MediaBrowser.Api.Movies
/// <param name="libraryManager">The library manager.</param>
/// <param name="itemRepo">The item repo.</param>
/// <param name="dtoService">The dto service.</param>
- public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService)
+ public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, IServerConfigurationManager config)
{
_userManager = userManager;
_userDataRepository = userDataRepository;
_libraryManager = libraryManager;
_itemRepo = itemRepo;
_dtoService = dtoService;
+ _config = config;
}
/// <summary>
@@ -146,15 +149,17 @@ namespace MediaBrowser.Api.Movies
(!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
_libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
+ var itemTypes = new List<string> { typeof(Movie).Name };
+ if (_config.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(typeof(Trailer).Name);
+ itemTypes.Add(typeof(LiveTvProgram).Name);
+ }
+
var itemsResult = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
Limit = request.Limit,
- IncludeItemTypes = new[]
- {
- typeof(Movie).Name,
- typeof(Trailer).Name,
- typeof(LiveTvProgram).Name
- },
+ IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
SimilarTo = item,
EnableGroupByMetadataKey = true
@@ -198,14 +203,16 @@ namespace MediaBrowser.Api.Movies
var recentlyPlayedMovies = _libraryManager.GetItemList(query).ToList();
+ var itemTypes = new List<string> { typeof(Movie).Name };
+ if (_config.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(typeof(Trailer).Name);
+ itemTypes.Add(typeof(LiveTvProgram).Name);
+ }
+
var likedMovies = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
- IncludeItemTypes = new[]
- {
- typeof(Movie).Name,
- typeof(Trailer).Name,
- typeof(LiveTvProgram).Name
- },
+ IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
SortBy = new[] { ItemSortBy.Random },
SortOrder = SortOrder.Descending,
@@ -278,6 +285,13 @@ namespace MediaBrowser.Api.Movies
private IEnumerable<RecommendationDto> GetWithDirector(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
+ var itemTypes = new List<string> { typeof(Movie).Name };
+ if (_config.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(typeof(Trailer).Name);
+ itemTypes.Add(typeof(LiveTvProgram).Name);
+ }
+
foreach (var name in names)
{
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
@@ -286,12 +300,7 @@ namespace MediaBrowser.Api.Movies
// Account for duplicates by imdb id, since the database doesn't support this yet
Limit = itemLimit + 2,
PersonTypes = new[] { PersonType.Director },
- IncludeItemTypes = new[]
- {
- typeof(Movie).Name,
- typeof(Trailer).Name,
- typeof(LiveTvProgram).Name
- },
+ IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
EnableGroupByMetadataKey = true
@@ -314,6 +323,13 @@ namespace MediaBrowser.Api.Movies
private IEnumerable<RecommendationDto> GetWithActor(User user, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
+ var itemTypes = new List<string> { typeof(Movie).Name };
+ if (_config.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(typeof(Trailer).Name);
+ itemTypes.Add(typeof(LiveTvProgram).Name);
+ }
+
foreach (var name in names)
{
var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
@@ -321,12 +337,7 @@ namespace MediaBrowser.Api.Movies
Person = name,
// Account for duplicates by imdb id, since the database doesn't support this yet
Limit = itemLimit + 2,
- IncludeItemTypes = new[]
- {
- typeof(Movie).Name,
- typeof(Trailer).Name,
- typeof(LiveTvProgram).Name
- },
+ IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
EnableGroupByMetadataKey = true
@@ -349,17 +360,19 @@ namespace MediaBrowser.Api.Movies
private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
{
+ var itemTypes = new List<string> { typeof(Movie).Name };
+ if (_config.Configuration.EnableExternalContentInSuggestions)
+ {
+ itemTypes.Add(typeof(Trailer).Name);
+ itemTypes.Add(typeof(LiveTvProgram).Name);
+ }
+
foreach (var item in baselineItems)
{
var similar = _libraryManager.GetItemList(new InternalItemsQuery(user)
{
Limit = itemLimit,
- IncludeItemTypes = new[]
- {
- typeof(Movie).Name,
- typeof(Trailer).Name,
- typeof(LiveTvProgram).Name
- },
+ IncludeItemTypes = itemTypes.ToArray(),
IsMovie = true,
SimilarTo = item,
EnableGroupByMetadataKey = true
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index 4af564a5a..4a62da6f6 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -337,44 +337,60 @@ namespace MediaBrowser.Api.Playback
/// Gets the video bitrate to specify on the command line
/// </summary>
/// <param name="state">The state.</param>
- /// <param name="videoCodec">The video codec.</param>
+ /// <param name="videoEncoder">The video codec.</param>
/// <returns>System.String.</returns>
- protected string GetVideoQualityParam(StreamState state, string videoCodec)
+ protected string GetVideoQualityParam(StreamState state, string videoEncoder)
{
var param = string.Empty;
var isVc1 = state.VideoStream != null &&
string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
- if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
+
+ if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
- param = "-preset superfast";
+ if (!string.IsNullOrWhiteSpace(encodingOptions.H264Preset))
+ {
+ param += "-preset " + encodingOptions.H264Preset;
+ }
+ else
+ {
+ param += "-preset superfast";
+ }
- param += " -crf 23";
+ if (encodingOptions.H264Crf >= 0 && encodingOptions.H264Crf <= 51)
+ {
+ param += " -crf " + encodingOptions.H264Crf.ToString(CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ param += " -crf 23";
+ }
}
- else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
- param = "-preset fast";
+ param += "-preset fast";
param += " -crf 28";
}
// h264 (h264_qsv)
- else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
- param = "-preset 7 -look_ahead 0";
+ param += "-preset 7 -look_ahead 0";
}
// h264 (h264_nvenc)
- else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
- param = "-preset default";
+ param += "-preset default";
}
// webm
- else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase))
{
// Values 0-3, 0 being highest quality but slower
var profileScore = 0;
@@ -394,30 +410,30 @@ namespace MediaBrowser.Api.Playback
profileScore = Math.Min(profileScore, 2);
// http://www.webmproject.org/docs/encoder-parameters/
- param = string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
+ param += string.Format("-speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
profileScore.ToString(UsCulture),
crf,
qmin,
qmax);
}
- else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
- param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
+ param += "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
}
// asf/wmv
- else if (string.Equals(videoCodec, "wmv2", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase))
{
- param = "-qmin 2";
+ param += "-qmin 2";
}
- else if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
- param = "-mbd 2";
+ param += "-mbd 2";
}
- param += GetVideoBitrateParam(state, videoCodec);
+ param += GetVideoBitrateParam(state, videoEncoder);
var framerate = GetFramerateParam(state);
if (framerate.HasValue)
@@ -432,8 +448,8 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.VideoRequest.Profile))
{
- if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
// not supported by h264_omx
param += " -profile:v " + state.VideoRequest.Profile;
@@ -442,11 +458,13 @@ namespace MediaBrowser.Api.Playback
if (!string.IsNullOrEmpty(state.VideoRequest.Level))
{
+ var level = NormalizeTranscodingLevel(state.OutputVideoCodec, state.VideoRequest.Level);
+
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
- if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
- switch (state.VideoRequest.Level)
+ switch (level)
{
case "30":
param += " -level 3";
@@ -476,20 +494,20 @@ namespace MediaBrowser.Api.Playback
param += " -level 5.2";
break;
default:
- param += " -level " + state.VideoRequest.Level;
+ param += " -level " + level;
break;
}
}
- else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
- param += " -level " + state.VideoRequest.Level;
+ param += " -level " + level;
}
}
- if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt yuv420p " + param;
}
@@ -497,6 +515,25 @@ namespace MediaBrowser.Api.Playback
return param;
}
+ private string NormalizeTranscodingLevel(string videoCodec, string level)
+ {
+ double requestLevel;
+
+ // Clients may direct play higher than level 41, but there's no reason to transcode higher
+ if (double.TryParse(level, NumberStyles.Any, UsCulture, out requestLevel))
+ {
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ if (requestLevel > 41)
+ {
+ return "41";
+ }
+ }
+ }
+
+ return level;
+ }
+
protected string GetAudioFilterParam(StreamState state, bool isHls)
{
var volParam = string.Empty;
@@ -730,7 +767,20 @@ namespace MediaBrowser.Api.Playback
if (request.Width.HasValue || request.Height.HasValue || request.MaxHeight.HasValue || request.MaxWidth.HasValue)
{
outputSizeParam = GetOutputSizeParam(state, outputVideoCodec).TrimEnd('"');
- outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase));
+ }
+ else
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0)
+ {
+ outputSizeParam = ",format=nv12|vaapi,hwupload";
}
var videoSizeParam = string.Empty;
@@ -985,7 +1035,7 @@ namespace MediaBrowser.Api.Playback
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
if (GetVideoEncoder(state).IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
{
- arg = "-hwaccel vaapi -hwaccel_output_format vaapi -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
+ arg = "-hwaccel vaapi -hwaccel_output_format yuv420p -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
}
}
@@ -1160,17 +1210,21 @@ namespace MediaBrowser.Api.Playback
await Task.Delay(100, cancellationTokenSource.Token).ConfigureAwait(false);
}
- if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive)
+ if (state.IsInputVideo && transcodingJob.Type == TranscodingJobType.Progressive && !transcodingJob.HasExited)
{
await Task.Delay(1000, cancellationTokenSource.Token).ConfigureAwait(false);
- if (state.ReadInputAtNativeFramerate)
+ if (state.ReadInputAtNativeFramerate && !transcodingJob.HasExited)
{
await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
}
}
- StartThrottler(state, transcodingJob);
+ if (!transcodingJob.HasExited)
+ {
+ StartThrottler(state, transcodingJob);
+ }
+
ReportUsage(state);
return transcodingJob;
@@ -1770,6 +1824,15 @@ namespace MediaBrowser.Api.Playback
// state.SegmentLength = 6;
//}
+ if (state.VideoRequest != null)
+ {
+ if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
+ {
+ state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+ state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
+ }
+ }
+
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
{
state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
@@ -2012,7 +2075,7 @@ namespace MediaBrowser.Api.Playback
}
// Source and target codecs must match
- if (!string.Equals(request.VideoCodec, videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ if (string.IsNullOrEmpty(videoStream.Codec) || !state.SupportedVideoCodecs.Contains(videoStream.Codec, StringComparer.OrdinalIgnoreCase))
{
return false;
}
diff --git a/MediaBrowser.Api/Playback/MediaInfoService.cs b/MediaBrowser.Api/Playback/MediaInfoService.cs
index 91e62b4e3..656b2ee08 100644
--- a/MediaBrowser.Api/Playback/MediaInfoService.cs
+++ b/MediaBrowser.Api/Playback/MediaInfoService.cs
@@ -17,6 +17,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Api.Playback
{
@@ -70,8 +71,9 @@ namespace MediaBrowser.Api.Playback
private readonly INetworkManager _networkManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IUserManager _userManager;
+ private readonly IJsonSerializer _json;
- public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager)
+ public MediaInfoService(IMediaSourceManager mediaSourceManager, IDeviceManager deviceManager, ILibraryManager libraryManager, IServerConfigurationManager config, INetworkManager networkManager, IMediaEncoder mediaEncoder, IUserManager userManager, IJsonSerializer json)
{
_mediaSourceManager = mediaSourceManager;
_deviceManager = deviceManager;
@@ -80,6 +82,7 @@ namespace MediaBrowser.Api.Playback
_networkManager = networkManager;
_mediaEncoder = mediaEncoder;
_userManager = userManager;
+ _json = json;
}
public object Get(GetBitrateTestBytes request)
@@ -147,6 +150,8 @@ namespace MediaBrowser.Api.Playback
var profile = request.DeviceProfile;
+ //Logger.Info("GetPostedPlaybackInfo profile: {0}", _json.SerializeToString(profile));
+
if (profile == null)
{
var caps = _deviceManager.GetCapabilities(authInfo.DeviceId);
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index d7d94c69b..2e92c4a49 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -112,6 +112,7 @@ namespace MediaBrowser.Api.Playback
public string OutputVideoSync = "-1";
public List<string> SupportedAudioCodecs { get; set; }
+ public List<string> SupportedVideoCodecs { get; set; }
public string UserAgent { get; set; }
public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger)
@@ -119,6 +120,7 @@ namespace MediaBrowser.Api.Playback
_mediaSourceManager = mediaSourceManager;
_logger = logger;
SupportedAudioCodecs = new List<string>();
+ SupportedVideoCodecs = new List<string>();
PlayableStreamFileNames = new List<string>();
RemoteHttpHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs
index daaa6343d..a0d69317c 100644
--- a/MediaBrowser.Api/TvShowsService.cs
+++ b/MediaBrowser.Api/TvShowsService.cs
@@ -478,7 +478,7 @@ namespace MediaBrowser.Api
}
else
{
- episodes = series.GetSeasonEpisodes(user, season);
+ episodes = series.GetSeasonEpisodes(season, user);
}
}
else
diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
index 5381f9004..182a92fc8 100644
--- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
+++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
@@ -205,6 +205,7 @@ namespace MediaBrowser.Api.UserLibrary
private void SetItemCounts(BaseItemDto dto, ItemCounts counts)
{
dto.ChildCount = counts.ItemCount;
+ dto.ProgramCount = counts.ProgramCount;
dto.SeriesCount = counts.SeriesCount;
dto.EpisodeCount = counts.EpisodeCount;
dto.MovieCount = counts.MovieCount;
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
index 371757f6c..ba5964be5 100644
--- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
+++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
@@ -293,12 +293,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
/// </exception>
public async Task<HttpResponseInfo> SendAsync(HttpRequestOptions options, string httpMethod)
{
- HttpResponseInfo response;
-
if (options.CacheMode == CacheMode.None)
{
- response = await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
- return response;
+ return await SendAsyncInternal(options, httpMethod).ConfigureAwait(false);
}
var url = options.Url;
@@ -306,7 +303,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
var responseCachePath = Path.Combine(_appPaths.CachePath, "httpclient", urlHash);
- response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
+ var response = await GetCachedResponse(responseCachePath, options.CacheLength, url).ConfigureAwait(false);
if (response != null)
{
return response;
diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index ab2aa761b..798e18ef5 100644
--- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -423,10 +423,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks
CurrentProgress = null;
OnTaskCompleted(startTime, endTime, status, failureException);
-
- // Bad practice, i know. But we keep a lot in memory, unfortunately.
- GC.Collect(2, GCCollectionMode.Forced, true);
- GC.Collect(2, GCCollectionMode.Forced, true);
}
/// <summary>
diff --git a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
index 4e01041bc..10c0f8fc9 100644
--- a/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
+++ b/MediaBrowser.Common.Implementations/Security/PluginSecurityManager.cs
@@ -142,9 +142,15 @@ namespace MediaBrowser.Common.Implementations.Security
}
set
{
- if (value != LicenseFile.RegKey)
+ var newValue = value;
+ if (newValue != null)
{
- LicenseFile.RegKey = value;
+ newValue = newValue.Trim();
+ }
+
+ if (newValue != LicenseFile.RegKey)
+ {
+ LicenseFile.RegKey = newValue;
LicenseFile.Save();
// re-load registration info
diff --git a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
index 6281ab3ed..84c08439e 100644
--- a/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
+++ b/MediaBrowser.Common.Implementations/Updates/GithubUpdater.cs
@@ -14,16 +14,14 @@ namespace MediaBrowser.Common.Implementations.Updates
{
private readonly IHttpClient _httpClient;
private readonly IJsonSerializer _jsonSerializer;
- private TimeSpan _cacheLength;
- public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer, TimeSpan cacheLength)
+ public GithubUpdater(IHttpClient httpClient, IJsonSerializer jsonSerializer)
{
_httpClient = httpClient;
_jsonSerializer = jsonSerializer;
- _cacheLength = cacheLength;
}
- public async Task<CheckForUpdateResult> CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, CancellationToken cancellationToken)
+ public async Task<CheckForUpdateResult> CheckForUpdateResult(string organzation, string repository, Version minVersion, PackageVersionClass updateLevel, string assetFilename, string packageName, string targetFilename, TimeSpan cacheLength, CancellationToken cancellationToken)
{
var url = string.Format("https://api.github.com/repos/{0}/{1}/releases", organzation, repository);
@@ -35,10 +33,10 @@ namespace MediaBrowser.Common.Implementations.Updates
UserAgent = "Emby/3.0"
};
- if (_cacheLength.Ticks > 0)
+ if (cacheLength.Ticks > 0)
{
options.CacheMode = CacheMode.Unconditional;
- options.CacheLength = _cacheLength;
+ options.CacheLength = cacheLength;
}
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
@@ -110,12 +108,6 @@ namespace MediaBrowser.Common.Implementations.Updates
UserAgent = "Emby/3.0"
};
- if (_cacheLength.Ticks > 0)
- {
- options.CacheMode = CacheMode.Unconditional;
- options.CacheLength = _cacheLength;
- }
-
using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
{
var obj = _jsonSerializer.DeserializeFromStream<RootObject[]>(stream);
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs
index e3d2d0440..3c46247a7 100644
--- a/MediaBrowser.Controller/Channels/IChannelManager.cs
+++ b/MediaBrowser.Controller/Channels/IChannelManager.cs
@@ -31,6 +31,8 @@ namespace MediaBrowser.Controller.Channels
/// <returns>ChannelFeatures.</returns>
ChannelFeatures GetChannelFeatures(string id);
+ bool SupportsSync(string channelId);
+
/// <summary>
/// Gets all channel features.
/// </summary>
diff --git a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs b/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs
index 3e33066ae..d2d28e504 100644
--- a/MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs
+++ b/MediaBrowser.Controller/Collections/ManualCollectionsFolder.cs
@@ -1,6 +1,6 @@
using MediaBrowser.Controller.Entities;
-namespace MediaBrowser.Server.Implementations.Collections
+namespace MediaBrowser.Controller.Collections
{
public class ManualCollectionsFolder : BasePluginFolder, IHiddenFromDisplay
{
diff --git a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
index e8083b363..d2c5b9e4e 100644
--- a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
+++ b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs
@@ -1,10 +1,20 @@
using System;
+using System.Collections.Generic;
+using System.Net;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Controller.Dlna
{
public interface IDeviceDiscovery
{
- event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
- event EventHandler<SsdpMessageEventArgs> DeviceLeft;
+ event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+ event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
+ }
+
+ public class UpnpDeviceInfo
+ {
+ public Uri Location { get; set; }
+ public Dictionary<string, string> Headers { get; set; }
+ public IPEndPoint LocalEndPoint { get; set; }
}
}
diff --git a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
index e4126ddcf..ec3a00aad 100644
--- a/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
+++ b/MediaBrowser.Controller/Dlna/ISsdpHandler.cs
@@ -4,6 +4,5 @@ namespace MediaBrowser.Controller.Dlna
{
public interface ISsdpHandler
{
- event EventHandler<SsdpMessageEventArgs> MessageReceived;
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 55aaf04ff..2a49168ed 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -2111,10 +2111,7 @@ namespace MediaBrowser.Controller.Entities
{
if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
- foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
- {
- path = LibraryManager.SubstitutePath(path, map.From, map.To);
- }
+ return LibraryManager.GetPathAfterNetworkSubstitution(path);
}
return path;
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 597ecf973..30ea26eb6 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -106,7 +106,7 @@ namespace MediaBrowser.Controller.Entities
{
LibraryOptions[path] = options;
- options.SchemaVersion = 1;
+ options.SchemaVersion = 2;
XmlSerializer.SerializeToFile(options, GetLibraryOptionsPath(path));
}
}
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index bf47ada0d..f1d8def4b 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -1057,11 +1057,21 @@ namespace MediaBrowser.Controller.Entities
/// <returns>IList{BaseItem}.</returns>
public IList<BaseItem> GetRecursiveChildren()
{
- return GetRecursiveChildren(i => true);
+ return GetRecursiveChildren(true);
+ }
+
+ public IList<BaseItem> GetRecursiveChildren(bool includeLinkedChildren)
+ {
+ return GetRecursiveChildren(i => true, includeLinkedChildren);
}
public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter)
{
+ return GetRecursiveChildren(filter, true);
+ }
+
+ public IList<BaseItem> GetRecursiveChildren(Func<BaseItem, bool> filter, bool includeLinkedChildren)
+ {
var result = new Dictionary<Guid, BaseItem>();
AddChildrenToList(result, true, true, filter);
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 65b7c9955..ff3e53b69 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -85,9 +85,7 @@ namespace MediaBrowser.Controller.Entities.TV
public override int GetChildCount(User user)
{
- Logger.Debug("Season {0} getting child cound", (Path ?? Name));
var result = GetChildren(user, true).Count();
- Logger.Debug("Season {0} child cound: ", result);
return result;
}
@@ -158,13 +156,10 @@ namespace MediaBrowser.Controller.Entities.TV
var id = Guid.NewGuid().ToString("N");
- Logger.Debug("Season.GetItemsInternal entering GetEpisodes. Request id: " + id);
var items = GetEpisodes(user).Where(filter);
- Logger.Debug("Season.GetItemsInternal entering PostFilterAndSort. Request id: " + id);
var result = PostFilterAndSort(items, query, false, false);
- Logger.Debug("Season.GetItemsInternal complete. Request id: " + id);
return Task.FromResult(result);
}
@@ -185,34 +180,12 @@ namespace MediaBrowser.Controller.Entities.TV
public IEnumerable<Episode> GetEpisodes(Series series, User user, IEnumerable<Episode> allSeriesEpisodes)
{
- return series.GetSeasonEpisodes(user, this, allSeriesEpisodes);
+ return series.GetSeasonEpisodes(this, user, allSeriesEpisodes);
}
public IEnumerable<Episode> GetEpisodes()
{
- var episodes = GetRecursiveChildren().OfType<Episode>();
- var series = Series;
-
- if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
- {
- var seasonNumber = IndexNumber;
- var list = episodes.ToList();
-
- if (seasonNumber.HasValue)
- {
- list.AddRange(series.GetRecursiveChildren().OfType<Episode>()
- .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
- }
- else
- {
- list.AddRange(series.GetRecursiveChildren().OfType<Episode>()
- .Where(i => !i.ParentIndexNumber.HasValue));
- }
-
- episodes = list.DistinctBy(i => i.Id);
- }
-
- return episodes;
+ return Series.GetSeasonEpisodes(this, null, null);
}
public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index 4915cfedc..7e8ba0516 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -209,7 +209,6 @@ namespace MediaBrowser.Controller.Entities.TV
var seriesKey = GetUniqueSeriesKey(this);
- Logger.Debug("GetSeasons SeriesKey: {0}", seriesKey);
var query = new InternalItemsQuery(user)
{
AncestorWithPresentationUniqueKey = seriesKey,
@@ -267,7 +266,6 @@ namespace MediaBrowser.Controller.Entities.TV
public IEnumerable<Episode> GetEpisodes(User user)
{
var seriesKey = GetUniqueSeriesKey(this);
- Logger.Debug("GetEpisodes seriesKey: {0}", seriesKey);
var query = new InternalItemsQuery(user)
{
@@ -291,8 +289,6 @@ namespace MediaBrowser.Controller.Entities.TV
var allItems = LibraryManager.GetItemList(query).ToList();
- Logger.Debug("GetEpisodes return {0} items from database", allItems.Count);
-
var allSeriesEpisodes = allItems.OfType<Episode>().ToList();
var allEpisodes = allItems.OfType<Season>()
@@ -373,27 +369,9 @@ namespace MediaBrowser.Controller.Entities.TV
progress.Report(100);
}
- private IEnumerable<Episode> GetAllEpisodes(User user)
- {
- Logger.Debug("Series.GetAllEpisodes entering GetItemList");
-
- var result = LibraryManager.GetItemList(new InternalItemsQuery(user)
- {
- AncestorWithPresentationUniqueKey = GetUniqueSeriesKey(this),
- IncludeItemTypes = new[] { typeof(Episode).Name },
- SortBy = new[] { ItemSortBy.SortName }
-
- }).Cast<Episode>().ToList();
-
- Logger.Debug("Series.GetAllEpisodes returning {0} episodes", result.Count);
-
- return result;
- }
-
- public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason)
+ public IEnumerable<Episode> GetSeasonEpisodes(Season parentSeason, User user)
{
var seriesKey = GetUniqueSeriesKey(this);
- Logger.Debug("GetSeasonEpisodes seriesKey: {0}", seriesKey);
var query = new InternalItemsQuery(user)
{
@@ -401,34 +379,35 @@ namespace MediaBrowser.Controller.Entities.TV
IncludeItemTypes = new[] { typeof(Episode).Name },
SortBy = new[] { ItemSortBy.SortName }
};
- var config = user.Configuration;
- if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
+ if (user != null)
{
- query.IsVirtualItem = false;
- }
- else if (!config.DisplayMissingEpisodes)
- {
- query.IsMissing = false;
- }
- else if (!config.DisplayUnairedEpisodes)
- {
- query.IsVirtualUnaired = false;
+ var config = user.Configuration;
+ if (!config.DisplayMissingEpisodes && !config.DisplayUnairedEpisodes)
+ {
+ query.IsVirtualItem = false;
+ }
+ else if (!config.DisplayMissingEpisodes)
+ {
+ query.IsMissing = false;
+ }
+ else if (!config.DisplayUnairedEpisodes)
+ {
+ query.IsVirtualUnaired = false;
+ }
}
var allItems = LibraryManager.GetItemList(query).OfType<Episode>();
- return GetSeasonEpisodes(user, parentSeason, allItems);
+ return GetSeasonEpisodes(parentSeason, user, allItems);
}
- public IEnumerable<Episode> GetSeasonEpisodes(User user, Season parentSeason, IEnumerable<Episode> allSeriesEpisodes)
+ public IEnumerable<Episode> GetSeasonEpisodes(Season parentSeason, User user, IEnumerable<Episode> allSeriesEpisodes)
{
if (allSeriesEpisodes == null)
{
- Logger.Debug("GetSeasonEpisodes allSeriesEpisodes is null");
- return GetSeasonEpisodes(user, parentSeason);
+ return GetSeasonEpisodes(parentSeason, user);
}
- Logger.Debug("GetSeasonEpisodes FilterEpisodesBySeason");
var episodes = FilterEpisodesBySeason(allSeriesEpisodes, parentSeason, ConfigurationManager.Configuration.DisplaySpecialsWithinSeasons);
var sortBy = (parentSeason.IndexNumber ?? -1) == 0 ? ItemSortBy.SortName : ItemSortBy.AiredEpisodeOrder;
diff --git a/MediaBrowser.Controller/Entities/Trailer.cs b/MediaBrowser.Controller/Entities/Trailer.cs
index 306ce35ec..7a987a68e 100644
--- a/MediaBrowser.Controller/Entities/Trailer.cs
+++ b/MediaBrowser.Controller/Entities/Trailer.cs
@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities
info.IsLocalTrailer = TrailerTypes.Contains(TrailerType.LocalTrailer);
- if (!IsInMixedFolder)
+ if (!IsInMixedFolder && LocationType == LocationType.FileSystem)
{
info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
}
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index f4c0e7658..d6744e804 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -89,5 +89,7 @@ namespace MediaBrowser.Controller
string GetLocalApiUrl(IPAddress ipAddress);
void LaunchUrl(string url);
+
+ void EnableLoopback(string appName);
}
}
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 0862e3eaf..d5c2fcd20 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -506,6 +506,8 @@ namespace MediaBrowser.Controller.Library
/// <returns>QueryResult&lt;BaseItem&gt;.</returns>
QueryResult<BaseItem> QueryItems(InternalItemsQuery query);
+ string GetPathAfterNetworkSubstitution(string path);
+
/// <summary>
/// Substitutes the path.
/// </summary>
@@ -566,5 +568,8 @@ namespace MediaBrowser.Controller.Library
QueryResult<Tuple<BaseItem, ItemCounts>> GetArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAlbumArtists(InternalItemsQuery query);
QueryResult<Tuple<BaseItem, ItemCounts>> GetAllArtists(InternalItemsQuery query);
+
+ void RegisterIgnoredPath(string path);
+ void UnRegisterIgnoredPath(string path);
}
} \ No newline at end of file
diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs
index 6973dce64..72f036b0a 100644
--- a/MediaBrowser.Controller/Library/NameExtensions.cs
+++ b/MediaBrowser.Controller/Library/NameExtensions.cs
@@ -54,7 +54,7 @@ namespace MediaBrowser.Controller.Library
}
}
- class TextComparer : IComparer<string>, IEqualityComparer<string>
+ public class DistinctNameComparer : IComparer<string>, IEqualityComparer<string>
{
public int Compare(string x, string y)
{
@@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.Library
return 0;
}
- return string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace);
+ return string.Compare(x.RemoveDiacritics(), y.RemoveDiacritics(), StringComparison.OrdinalIgnoreCase);
}
public bool Equals(string x, string y)
diff --git a/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs b/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs
deleted file mode 100644
index 3626c18e5..000000000
--- a/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using MediaBrowser.Model.Entities;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.LiveTv
-{
- public interface IHasRegistrationInfo
- {
- /// <summary>
- /// Gets the registration information.
- /// </summary>
- /// <param name="feature">The feature.</param>
- /// <returns>Task&lt;MBRegistrationRecord&gt;.</returns>
- Task<MBRegistrationRecord> GetRegistrationInfo(string feature);
- }
-}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index ed64127c3..a8e42749b 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -108,6 +108,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>QueryResult{RecordingInfoDto}.</returns>
Task<QueryResult<BaseItemDto>> GetRecordings(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken);
+ Task<QueryResult<BaseItemDto>> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken);
/// <summary>
/// Gets the timers.
@@ -364,11 +365,9 @@ namespace MediaBrowser.Controller.LiveTv
/// <summary>
/// Gets the registration information.
/// </summary>
- /// <param name="channelId">The channel identifier.</param>
- /// <param name="programId">The program identifier.</param>
/// <param name="feature">The feature.</param>
/// <returns>Task&lt;MBRegistrationRecord&gt;.</returns>
- Task<MBRegistrationRecord> GetRegistrationInfo(string channelId, string programId, string feature);
+ Task<MBRegistrationRecord> GetRegistrationInfo(string feature);
/// <summary>
/// Adds the channel information.
diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
index a6a3e6108..d0377fbfd 100644
--- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs
@@ -1,6 +1,7 @@
using MediaBrowser.Model.LiveTv;
using System;
using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.LiveTv
{
@@ -66,6 +67,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
public bool? IsHD { get; set; }
+ public bool? Is3D { get; set; }
+
/// <summary>
/// Gets or sets the audio.
/// </summary>
@@ -84,6 +87,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <value><c>true</c> if this instance is repeat; otherwise, <c>false</c>.</value>
public bool IsRepeat { get; set; }
+ public bool IsSubjectToBlackout { get; set; }
+
/// <summary>
/// Gets or sets the episode title.
/// </summary>
@@ -102,6 +107,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <value>The image URL.</value>
public string ImageUrl { get; set; }
+ public string LogoImageUrl { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance has image.
/// </summary>
@@ -144,6 +151,8 @@ namespace MediaBrowser.Controller.LiveTv
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
public bool IsKids { get; set; }
+ public bool IsEducational { get; set; }
+
/// <summary>
/// Gets or sets a value indicating whether this instance is premiere.
/// </summary>
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index 5d92a212f..94bc24b9e 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -82,5 +82,21 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value>The priority.</value>
public int Priority { get; set; }
+
+
+ // Program properties
+ public int? SeasonNumber { get; set; }
+ /// <summary>
+ /// Gets or sets the episode number.
+ /// </summary>
+ /// <value>The episode number.</value>
+ public int? EpisodeNumber { get; set; }
+ public bool IsMovie { get; set; }
+ public bool IsKids { get; set; }
+ public bool IsSports { get; set; }
+ public int? ProductionYear { get; set; }
+ public string EpisodeTitle { get; set; }
+ public DateTime? OriginalAirDate { get; set; }
+ public bool IsProgramSeries { get; set; }
}
}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index e7eaa1dc0..8fae46906 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -98,6 +98,7 @@
<Compile Include="Collections\CollectionCreationOptions.cs" />
<Compile Include="Collections\CollectionEvents.cs" />
<Compile Include="Collections\ICollectionManager.cs" />
+ <Compile Include="Collections\ManualCollectionsFolder.cs" />
<Compile Include="Connect\ConnectSupporterSummary.cs" />
<Compile Include="Connect\IConnectManager.cs" />
<Compile Include="Connect\UserLinkResult.cs" />
@@ -198,7 +199,6 @@
<Compile Include="Library\NameExtensions.cs" />
<Compile Include="Library\PlaybackStopEventArgs.cs" />
<Compile Include="Library\UserDataSaveEventArgs.cs" />
- <Compile Include="LiveTv\IHasRegistrationInfo.cs" />
<Compile Include="LiveTv\IListingsProvider.cs" />
<Compile Include="LiveTv\ITunerHost.cs" />
<Compile Include="LiveTv\RecordingGroup.cs" />
@@ -266,7 +266,6 @@
<Compile Include="Playlists\IPlaylistManager.cs" />
<Compile Include="Playlists\Playlist.cs" />
<Compile Include="Plugins\ILocalizablePlugin.cs" />
- <Compile Include="Power\IPowerManagement.cs" />
<Compile Include="Providers\AlbumInfo.cs" />
<Compile Include="Providers\ArtistInfo.cs" />
<Compile Include="Providers\BookInfo.cs" />
diff --git a/MediaBrowser.Controller/Power/IPowerManagement.cs b/MediaBrowser.Controller/Power/IPowerManagement.cs
deleted file mode 100644
index faa289695..000000000
--- a/MediaBrowser.Controller/Power/IPowerManagement.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-
-namespace MediaBrowser.Controller.Power
-{
- public interface IPowerManagement
- {
- /// <summary>
- /// Schedules the wake.
- /// </summary>
- /// <param name="utcTime">The UTC time.</param>
- void ScheduleWake(DateTime utcTime);
- }
-}
diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
index c0912708c..fccbd9211 100644
--- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
+++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
@@ -23,14 +23,18 @@ namespace MediaBrowser.Controller.Providers
/// The logger
/// </summary>
protected ILogger Logger { get; private set; }
+ protected IProviderManager ProviderManager { get; private set; }
+
+ private Dictionary<string, string> _validProviderIds;
/// <summary>
/// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
- public BaseItemXmlParser(ILogger logger)
+ public BaseItemXmlParser(ILogger logger, IProviderManager providerManager)
{
Logger = logger;
+ ProviderManager = providerManager;
}
/// <summary>
@@ -60,6 +64,22 @@ namespace MediaBrowser.Controller.Providers
ValidationType = ValidationType.None
};
+ _validProviderIds = _validProviderIds = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
+
+ var idInfos = ProviderManager.GetExternalIdInfos(item.Item);
+
+ foreach (var info in idInfos)
+ {
+ var id = info.Key + "Id";
+ if (!_validProviderIds.ContainsKey(id))
+ {
+ _validProviderIds.Add(id, info.Key);
+ }
+ }
+
+ //Additional Mappings
+ _validProviderIds.Add("IMDB", "Imdb");
+
//Fetch(item, metadataFile, settings, Encoding.GetEncoding("ISO-8859-1"), cancellationToken);
Fetch(item, metadataFile, settings, Encoding.UTF8, cancellationToken);
}
@@ -657,14 +677,6 @@ namespace MediaBrowser.Controller.Providers
break;
}
- case "TvDbId":
- var tvdbId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(tvdbId))
- {
- item.SetProviderId(MetadataProviders.Tvdb, tvdbId);
- }
- break;
-
case "VoteCount":
{
var val = reader.ReadElementContentAsString();
@@ -679,95 +691,7 @@ namespace MediaBrowser.Controller.Providers
}
break;
}
- case "MusicBrainzAlbumId":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbz);
- }
- break;
- }
- case "MusicBrainzAlbumArtistId":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, mbz);
- }
- break;
- }
- case "MusicBrainzArtistId":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzArtist, mbz);
- }
- break;
- }
- case "MusicBrainzReleaseGroupId":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, mbz);
- }
- break;
- }
- case "TVRageId":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.TvRage, id);
- }
- break;
- }
- case "TvMazeId":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.TvMaze, id);
- }
- break;
- }
- case "AudioDbArtistId":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.AudioDbArtist, id);
- }
- break;
- }
- case "AudioDbAlbumId":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.AudioDbAlbum, id);
- }
- break;
- }
- case "RottenTomatoesId":
- var rtId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(rtId))
- {
- item.SetProviderId(MetadataProviders.RottenTomatoes, rtId);
- }
- break;
-
- case "TMDbId":
- var tmdb = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(tmdb))
- {
- item.SetProviderId(MetadataProviders.Tmdb, tmdb);
- }
- break;
-
- case "TMDbCollectionId":
+ case "CollectionNumber":
var tmdbCollection = reader.ReadElementContentAsString();
if (!string.IsNullOrWhiteSpace(tmdbCollection))
{
@@ -775,30 +699,6 @@ namespace MediaBrowser.Controller.Providers
}
break;
- case "TVcomId":
- var TVcomId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(TVcomId))
- {
- item.SetProviderId(MetadataProviders.Tvcom, TVcomId);
- }
- break;
-
- case "Zap2ItId":
- var zap2ItId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(zap2ItId))
- {
- item.SetProviderId(MetadataProviders.Zap2It, zap2ItId);
- }
- break;
-
- case "IMDB":
- var imDbId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(imDbId))
- {
- item.SetProviderId(MetadataProviders.Imdb, imDbId);
- }
- break;
-
case "Genres":
{
using (var subtree = reader.ReadSubtree())
@@ -890,8 +790,25 @@ namespace MediaBrowser.Controller.Providers
}
default:
- reader.Skip();
- break;
+ {
+ string readerName = reader.Name;
+ string providerIdValue;
+ if (_validProviderIds.TryGetValue(readerName, out providerIdValue))
+ {
+ var id = reader.ReadElementContentAsString();
+ if (!string.IsNullOrWhiteSpace(id))
+ {
+ item.SetProviderId(providerIdValue, id);
+ }
+ }
+ else
+ {
+ reader.Skip();
+ }
+
+ break;
+
+ }
}
}
diff --git a/MediaBrowser.Controller/Session/AuthenticationRequest.cs b/MediaBrowser.Controller/Session/AuthenticationRequest.cs
index bfd7f928b..362f5b2b9 100644
--- a/MediaBrowser.Controller/Session/AuthenticationRequest.cs
+++ b/MediaBrowser.Controller/Session/AuthenticationRequest.cs
@@ -4,6 +4,7 @@ namespace MediaBrowser.Controller.Session
public class AuthenticationRequest
{
public string Username { get; set; }
+ public string UserId { get; set; }
public string PasswordSha1 { get; set; }
public string PasswordMd5 { get; set; }
public string App { get; set; }
diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
index 9f2726b31..b1205710a 100644
--- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
+++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs
@@ -14,8 +14,10 @@ using MediaBrowser.Dlna.Ssdp;
using MediaBrowser.Model.Logging;
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
+using Rssdp;
namespace MediaBrowser.Dlna.Main
{
@@ -38,12 +40,11 @@ namespace MediaBrowser.Dlna.Main
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
- private readonly SsdpHandler _ssdpHandler;
private readonly IDeviceDiscovery _deviceDiscovery;
- private readonly List<string> _registeredServerIds = new List<string>();
private bool _ssdpHandlerStarted;
private bool _dlnaServerStarted;
+ private SsdpDevicePublisher _Publisher;
public DlnaEntryPoint(IServerConfigurationManager config,
ILogManager logManager,
@@ -58,7 +59,7 @@ namespace MediaBrowser.Dlna.Main
IUserDataManager userDataManager,
ILocalizationManager localization,
IMediaSourceManager mediaSourceManager,
- ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
+ IDeviceDiscovery deviceDiscovery, IMediaEncoder mediaEncoder)
{
_config = config;
_appHost = appHost;
@@ -74,7 +75,6 @@ namespace MediaBrowser.Dlna.Main
_mediaSourceManager = mediaSourceManager;
_deviceDiscovery = deviceDiscovery;
_mediaEncoder = mediaEncoder;
- _ssdpHandler = (SsdpHandler)ssdpHandler;
_logger = logManager.GetLogger("Dlna");
}
@@ -110,18 +110,6 @@ namespace MediaBrowser.Dlna.Main
{
var options = _config.GetDlnaConfiguration();
- if (!options.EnableServer && !options.EnablePlayTo && !_config.Configuration.EnableUPnP)
- {
- if (_ssdpHandlerStarted)
- {
- // Sat/ip live tv depends on device discovery, as well as hd homerun detection
- // In order to allow this to be disabled, we need a modular way of knowing if there are
- // any parts of the system that are dependant on it
- // DisposeSsdpHandler();
- }
- return;
- }
-
if (!_ssdpHandlerStarted)
{
StartSsdpHandler();
@@ -154,7 +142,7 @@ namespace MediaBrowser.Dlna.Main
{
try
{
- _ssdpHandler.Start();
+ StartPublishing();
_ssdpHandlerStarted = true;
StartDeviceDiscovery();
@@ -165,13 +153,16 @@ namespace MediaBrowser.Dlna.Main
}
}
+ private void StartPublishing()
+ {
+ _Publisher = new SsdpDevicePublisher();
+ }
+
private void StartDeviceDiscovery()
{
try
{
- ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler);
-
- //DlnaChannel.Current.Start(() => _registeredServerIds.ToList());
+ ((DeviceDiscovery)_deviceDiscovery).Start();
}
catch (Exception ex)
{
@@ -199,8 +190,6 @@ namespace MediaBrowser.Dlna.Main
{
((DeviceDiscovery)_deviceDiscovery).Dispose();
- _ssdpHandler.Dispose();
-
_ssdpHandlerStarted = false;
}
catch (Exception ex)
@@ -225,6 +214,14 @@ namespace MediaBrowser.Dlna.Main
private async Task RegisterServerEndpoints()
{
+ if (!_config.GetDlnaConfiguration().BlastAliveMessages)
+ {
+ return;
+ }
+
+ var cacheLength = _config.GetDlnaConfiguration().BlastAliveMessageIntervalSeconds * 2;
+ _Publisher.SupportPnpRootDevice = true;
+
foreach (var address in await _appHost.GetLocalIpAddresses().ConfigureAwait(false))
{
//if (IPAddress.IsLoopback(address))
@@ -234,25 +231,43 @@ namespace MediaBrowser.Dlna.Main
//}
var addressString = address.ToString();
- var udn = addressString.GetMD5().ToString("N");
-
- var descriptorURI = "/dlna/" + udn + "/description.xml";
-
- var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
var services = new List<string>
{
- "upnp:rootdevice",
"urn:schemas-upnp-org:device:MediaServer:1",
"urn:schemas-upnp-org:service:ContentDirectory:1",
"urn:schemas-upnp-org:service:ConnectionManager:1",
- "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
- "uuid:" + udn
+ "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1"
};
- _ssdpHandler.RegisterNotification(udn, uri, address, services);
+ var udn = (addressString).GetMD5().ToString("N");
+
+ foreach (var fullService in services)
+ {
+ _logger.Info("Registering publisher for {0} on {1}", fullService, addressString);
+
+ var descriptorURI = "/dlna/" + udn + "/description.xml";
+ var uri = new Uri(_appHost.GetLocalApiUrl(address) + descriptorURI);
- _registeredServerIds.Add(udn);
+ var service = fullService.Replace("urn:", string.Empty).Replace(":1", string.Empty);
+
+ var serviceParts = service.Split(':');
+
+ var deviceTypeNamespace = serviceParts[0].Replace('.', '-');
+
+ _Publisher.AddDevice(new SsdpRootDevice
+ {
+ CacheLifetime = TimeSpan.FromSeconds(cacheLength), //How long SSDP clients can cache this info.
+ Location = uri, // Must point to the URL that serves your devices UPnP description document.
+ DeviceTypeNamespace = deviceTypeNamespace,
+ DeviceClass = serviceParts[1],
+ DeviceType = serviceParts[2],
+ FriendlyName = "Emby Server",
+ Manufacturer = "Emby",
+ ModelName = "Emby Server",
+ Uuid = udn // This must be a globally unique value that survives reboots etc. Get from storage or embedded hardware etc.
+ });
+ }
}
}
@@ -315,20 +330,35 @@ namespace MediaBrowser.Dlna.Main
public void DisposeDlnaServer()
{
- foreach (var id in _registeredServerIds)
+ if (_Publisher != null)
{
- try
- {
- _ssdpHandler.UnregisterNotification(id);
- }
- catch (Exception ex)
+ var devices = _Publisher.Devices.ToList();
+
+ Parallel.ForEach(devices, device =>
{
- _logger.ErrorException("Error unregistering server", ex);
- }
+ try
+ {
+ _Publisher.RemoveDevice(device);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error sending bye bye", ex);
+ }
+ });
+ //foreach (var device in devices)
+ //{
+ // try
+ // {
+ // _Publisher.RemoveDevice(device);
+ // }
+ // catch (Exception ex)
+ // {
+ // _logger.ErrorException("Error sending bye bye", ex);
+ // }
+ //}
+ _Publisher.Dispose();
}
- _registeredServerIds.Clear();
-
_dlnaServerStarted = false;
}
}
diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
index d10a5f7b5..ae2e43a4f 100644
--- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
+++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -14,6 +14,7 @@
<SchemaVersion>2.0</SchemaVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -23,7 +24,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
@@ -32,7 +33,7 @@
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release Mono|AnyCPU' ">
<Optimize>false</Optimize>
@@ -50,8 +51,15 @@
<Reference Include="Patterns.Logging">
<HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
</Reference>
+ <Reference Include="Rssdp.NetFx40">
+ <HintPath>..\ThirdParty\rssdp\Rssdp.NetFx40.dll</HintPath>
+ </Reference>
+ <Reference Include="Rssdp.Portable">
+ <HintPath>..\ThirdParty\rssdp\Rssdp.Portable.dll</HintPath>
+ </Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
+ <Reference Include="System.Net.Http" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs
index 174ca871a..d1802b3ad 100644
--- a/MediaBrowser.Dlna/PlayTo/Device.cs
+++ b/MediaBrowser.Dlna/PlayTo/Device.cs
@@ -483,7 +483,9 @@ namespace MediaBrowser.Dlna.PlayTo
{
if (OnDeviceUnavailable != null)
{
+ _logger.Debug("Disposing device due to loss of connection");
OnDeviceUnavailable();
+ return;
}
}
if (_successiveStopCount >= maxSuccessiveStopReturns)
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
index 5622885fc..d958d0e37 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs
@@ -19,6 +19,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -122,16 +123,18 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- void _deviceDiscovery_DeviceLeft(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceLeft(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string nts;
- e.Headers.TryGetValue("NTS", out nts);
+ info.Headers.TryGetValue("NTS", out nts);
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = String.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = String.Empty;
if (usn.IndexOf(_device.Properties.UUID, StringComparison.OrdinalIgnoreCase) != -1 &&
!_disposed)
@@ -653,7 +656,7 @@ namespace MediaBrowser.Dlna.PlayTo
_device.PlaybackProgress -= _device_PlaybackProgress;
_device.PlaybackStopped -= _device_PlaybackStopped;
_device.MediaChanged -= _device_MediaChanged;
- _deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
+ //_deviceDiscovery.DeviceLeft -= _deviceDiscovery_DeviceLeft;
_device.OnDeviceUnavailable = null;
_device.Dispose();
diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
index cd9a7b1f0..6d6986f01 100644
--- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
+++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs
@@ -12,7 +12,9 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
+using System.Threading.Tasks;
using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Dlna.PlayTo
{
@@ -61,16 +63,17 @@ namespace MediaBrowser.Dlna.PlayTo
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- async void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
- string location;
- if (!e.Headers.TryGetValue("Location", out location)) location = string.Empty;
+ string location = info.Location.ToString();
// It has to report that it's a media renderer
if (usn.IndexOf("MediaRenderer:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -100,7 +103,7 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- var uri = new Uri(location);
+ var uri = info.Location;
_logger.Debug("Attempting to create PlayToController from location {0}", location);
var device = await Device.CreateuPnpDeviceAsync(uri, _httpClient, _config, _logger).ConfigureAwait(false);
@@ -121,7 +124,7 @@ namespace MediaBrowser.Dlna.PlayTo
if (controller == null)
{
- var serverAddress = GetServerAddress(e.LocalEndPoint.Address);
+ var serverAddress = await GetServerAddress(info.LocalEndPoint == null ? null : info.LocalEndPoint.Address).ConfigureAwait(false);
string accessToken = null;
sessionInfo.SessionController = controller = new PlayToController(sessionInfo,
@@ -173,9 +176,14 @@ namespace MediaBrowser.Dlna.PlayTo
}
}
- private string GetServerAddress(IPAddress localIp)
+ private Task<string> GetServerAddress(IPAddress localIp)
{
- return _appHost.GetLocalApiUrl(localIp);
+ if (localIp == null)
+ {
+ return _appHost.GetLocalApiUrl();
+ }
+
+ return Task.FromResult(_appHost.GetLocalApiUrl(localIp));
}
public void Dispose()
diff --git a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs
index 7b0f18fa6..5edf3afbf 100644
--- a/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/PanasonicVieraProfile.cs
@@ -66,7 +66,7 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "mkv",
VideoCodec = "h264,mpeg2video",
- AudioCodec = "aac,ac3,dca,mp3,mp2,pcm",
+ AudioCodec = "aac,ac3,dca,mp3,mp2,pcm,dts",
Type = DlnaProfileType.Video
},
diff --git a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs
index 63fcf60ec..aae520d6f 100644
--- a/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/SamsungSmartTvProfile.cs
@@ -65,14 +65,14 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "avi",
VideoCodec = "h264,mpeg4,mjpeg",
- AudioCodec = "mp3,ac3,dca",
+ AudioCodec = "mp3,ac3,dca,dts",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
{
Container = "mkv",
VideoCodec = "h264,mpeg4,mjpeg4",
- AudioCodec = "mp3,ac3,dca,aac",
+ AudioCodec = "mp3,ac3,dca,aac,dts",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
@@ -300,7 +300,7 @@ namespace MediaBrowser.Dlna.Profiles
new CodecProfile
{
Type = CodecType.VideoAudio,
- Codec = "ac3,wmav2,dca,aac,mp3",
+ Codec = "ac3,wmav2,dca,aac,mp3,dts",
Conditions = new[]
{
diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs
index bbdf370b8..fefb96117 100644
--- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs
+++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2013.cs
@@ -115,7 +115,7 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "mkv",
VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm",
+ AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs
index 1eed398ed..4f2ff3ad1 100644
--- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs
+++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2014.cs
@@ -115,7 +115,7 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "mkv",
VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm",
+ AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs
index 563c2db7b..57cd5dad6 100644
--- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs
+++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2015.cs
@@ -103,7 +103,7 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "mkv",
VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm",
+ AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
diff --git a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs
index 21e0c092c..f504820d1 100644
--- a/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs
+++ b/MediaBrowser.Dlna/Profiles/SonyBlurayPlayer2016.cs
@@ -103,7 +103,7 @@ namespace MediaBrowser.Dlna.Profiles
{
Container = "mkv",
VideoCodec = "mpeg4,h264",
- AudioCodec = "ac3,dca,aac,mp3,pcm",
+ AudioCodec = "ac3,dca,aac,mp3,pcm,dts",
Type = DlnaProfileType.Video
},
new DirectPlayProfile
diff --git a/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs b/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs
index 1c0c7d297..5f9e30318 100644
--- a/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs
+++ b/MediaBrowser.Dlna/Profiles/WdtvLiveProfile.cs
@@ -58,7 +58,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "avi",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
- AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
+ AudioCodec = "ac3,dca,mp2,mp3,pcm,dts"
},
new DirectPlayProfile
@@ -66,7 +66,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mpeg",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video",
- AudioCodec = "ac3,dca,mp2,mp3,pcm,dca"
+ AudioCodec = "ac3,dca,mp2,mp3,pcm,dts"
},
new DirectPlayProfile
@@ -74,7 +74,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mkv",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,mpeg4,h264,vc1",
- AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dca"
+ AudioCodec = "ac3,dca,aac,mp2,mp3,pcm,dts"
},
new DirectPlayProfile
@@ -82,7 +82,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "ts,m2ts",
Type = DlnaProfileType.Video,
VideoCodec = "mpeg1video,mpeg2video,h264,vc1",
- AudioCodec = "ac3,dca,mp2,mp3,aac,dca"
+ AudioCodec = "ac3,dca,mp2,mp3,aac,dts"
},
new DirectPlayProfile
@@ -90,7 +90,7 @@ namespace MediaBrowser.Dlna.Profiles
Container = "mp4,mov",
Type = DlnaProfileType.Video,
VideoCodec = "h264,mpeg4",
- AudioCodec = "ac3,aac,mp2,mp3,dca"
+ AudioCodec = "ac3,aac,mp2,mp3,dca,dts"
},
new DirectPlayProfile
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml
index 865449cb6..d26346ff6 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Panasonic Viera.xml
@@ -39,7 +39,7 @@
</XmlRootAttributes>
<DirectPlayProfiles>
<DirectPlayProfile container="mpeg,mpg" audioCodec="ac3,mp3,pcm_dvd" videoCodec="mpeg2video,mpeg4" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="aac,ac3,dca,mp3,mp2,pcm" videoCodec="h264,mpeg2video" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="aac,ac3,dca,mp3,mp2,pcm,dts" videoCodec="h264,mpeg2video" type="Video" />
<DirectPlayProfile container="ts" audioCodec="aac,mp3,mp2" videoCodec="h264,mpeg2video" type="Video" />
<DirectPlayProfile container="mp4" audioCodec="aac,ac3,mp3,pcm" videoCodec="h264" type="Video" />
<DirectPlayProfile container="mov" audioCodec="aac,pcm" videoCodec="h264" type="Video" />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
index 77fa928cc..1918c0297 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Samsung Smart TV.xml
@@ -38,8 +38,8 @@
</XmlRootAttributes>
<DirectPlayProfiles>
<DirectPlayProfile container="asf" audioCodec="mp3,ac3,wmav2,wmapro,wmavoice" videoCodec="h264,mpeg4,mjpeg" type="Video" />
- <DirectPlayProfile container="avi" audioCodec="mp3,ac3,dca" videoCodec="h264,mpeg4,mjpeg" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="mp3,ac3,dca,aac" videoCodec="h264,mpeg4,mjpeg4" type="Video" />
+ <DirectPlayProfile container="avi" audioCodec="mp3,ac3,dca,dts" videoCodec="h264,mpeg4,mjpeg" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="mp3,ac3,dca,aac,dts" videoCodec="h264,mpeg4,mjpeg4" type="Video" />
<DirectPlayProfile container="mp4" audioCodec="mp3,aac" videoCodec="h264,mpeg4" type="Video" />
<DirectPlayProfile container="3gp" audioCodec="aac,he-aac" videoCodec="h264,mpeg4" type="Video" />
<DirectPlayProfile container="mpg,mpeg" audioCodec="ac3,mp2,mp3,aac" videoCodec="mpeg1video,mpeg2video,h264" type="Video" />
@@ -100,7 +100,7 @@
</Conditions>
<ApplyConditions />
</CodecProfile>
- <CodecProfile type="VideoAudio" codec="ac3,wmav2,dca,aac,mp3">
+ <CodecProfile type="VideoAudio" codec="ac3,wmav2,dca,aac,mp3,dts">
<Conditions>
<ProfileCondition condition="LessThanEqual" property="AudioChannels" value="6" isRequired="true" />
</Conditions>
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml
index 1c626d3d5..f8b583b50 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2013.xml
@@ -45,7 +45,7 @@
<DirectPlayProfile container="mpeg,mpg" audioCodec="ac3,mp3,mp2,pcm" videoCodec="mpeg1video,mpeg2video" type="Video" />
<DirectPlayProfile container="mp4,m4v" audioCodec="ac3,aac,pcm,mp3" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="avi" audioCodec="ac3,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm,dts" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="m2ts,mts" audioCodec="aac,mp3,ac3,dca,dts" videoCodec="h264,mpeg4,vc1" type="Video" />
<DirectPlayProfile container="wmv,asf" type="Video" />
<DirectPlayProfile container="mp3,m4a,wma,wav" type="Audio" />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml
index bb55a1a80..eaa37c620 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2014.xml
@@ -45,7 +45,7 @@
<DirectPlayProfile container="mpeg,mpg" audioCodec="ac3,mp3,mp2,pcm" videoCodec="mpeg1video,mpeg2video" type="Video" />
<DirectPlayProfile container="mp4,m4v" audioCodec="ac3,aac,pcm,mp3" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="avi" audioCodec="ac3,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm,dts" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="m2ts,mts" audioCodec="aac,mp3,ac3,dca,dts" videoCodec="h264,mpeg4,vc1" type="Video" />
<DirectPlayProfile container="wmv,asf" type="Video" />
<DirectPlayProfile container="mp3,m4a,wma,wav" type="Audio" />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml
index 804770a59..368e892ff 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2015.xml
@@ -43,7 +43,7 @@
<DirectPlayProfile container="mpeg,mpg" audioCodec="ac3,mp3,mp2,pcm" videoCodec="mpeg1video,mpeg2video" type="Video" />
<DirectPlayProfile container="mp4,m4v" audioCodec="ac3,aac,pcm,mp3" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="avi" audioCodec="ac3,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm,dts" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="m2ts,mts" audioCodec="aac,mp3,ac3,dca,dts" videoCodec="h264,mpeg4,vc1" type="Video" />
<DirectPlayProfile container="wmv,asf" type="Video" />
<DirectPlayProfile container="mp3,m4a,wma,wav" type="Audio" />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml
index 920b6ccfa..9ec096b7f 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/Sony Blu-ray Player 2016.xml
@@ -43,7 +43,7 @@
<DirectPlayProfile container="mpeg,mpg" audioCodec="ac3,mp3,mp2,pcm" videoCodec="mpeg1video,mpeg2video" type="Video" />
<DirectPlayProfile container="mp4,m4v" audioCodec="ac3,aac,pcm,mp3" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="avi" audioCodec="ac3,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm" videoCodec="mpeg4,h264" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp3,pcm,dts" videoCodec="mpeg4,h264" type="Video" />
<DirectPlayProfile container="m2ts,mts" audioCodec="aac,mp3,ac3,dca,dts" videoCodec="h264,mpeg4,vc1" type="Video" />
<DirectPlayProfile container="wmv,asf" type="Video" />
<DirectPlayProfile container="mp3,m4a,wma,wav" type="Audio" />
diff --git a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
index ef8fa5757..ebd4eb9b5 100644
--- a/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
+++ b/MediaBrowser.Dlna/Profiles/Xml/WDTV Live.xml
@@ -36,11 +36,11 @@
<IgnoreTranscodeByteRangeRequests>true</IgnoreTranscodeByteRangeRequests>
<XmlRootAttributes />
<DirectPlayProfiles>
- <DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
- <DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video" type="Video" />
- <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp2,mp3,pcm,dca" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
- <DirectPlayProfile container="ts,m2ts" audioCodec="ac3,dca,mp2,mp3,aac,dca" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
- <DirectPlayProfile container="mp4,mov" audioCodec="ac3,aac,mp2,mp3,dca" videoCodec="h264,mpeg4" type="Video" />
+ <DirectPlayProfile container="avi" audioCodec="ac3,dca,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
+ <DirectPlayProfile container="mpeg" audioCodec="ac3,dca,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video" type="Video" />
+ <DirectPlayProfile container="mkv" audioCodec="ac3,dca,aac,mp2,mp3,pcm,dts" videoCodec="mpeg1video,mpeg2video,mpeg4,h264,vc1" type="Video" />
+ <DirectPlayProfile container="ts,m2ts" audioCodec="ac3,dca,mp2,mp3,aac,dts" videoCodec="mpeg1video,mpeg2video,h264,vc1" type="Video" />
+ <DirectPlayProfile container="mp4,mov" audioCodec="ac3,aac,mp2,mp3,dca,dts" videoCodec="h264,mpeg4" type="Video" />
<DirectPlayProfile container="asf" audioCodec="wmav2,wmapro" videoCodec="vc1" type="Video" />
<DirectPlayProfile container="asf" audioCodec="mp2,ac3" videoCodec="mpeg2video" type="Video" />
<DirectPlayProfile container="mp3" audioCodec="mp2,mp3" type="Audio" />
diff --git a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
index 68768745e..c9bba526a 100644
--- a/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
+++ b/MediaBrowser.Dlna/Ssdp/DeviceDiscovery.cs
@@ -11,6 +11,8 @@ using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
+using Rssdp;
namespace MediaBrowser.Dlna.Ssdp
{
@@ -20,211 +22,114 @@ namespace MediaBrowser.Dlna.Ssdp
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
- private SsdpHandler _ssdpHandler;
private readonly CancellationTokenSource _tokenSource;
- private readonly IServerApplicationHost _appHost;
- public event EventHandler<SsdpMessageEventArgs> DeviceDiscovered;
- public event EventHandler<SsdpMessageEventArgs> DeviceLeft;
- private readonly INetworkManager _networkManager;
+ public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceDiscovered;
+ public event EventHandler<GenericEventArgs<UpnpDeviceInfo>> DeviceLeft;
- public DeviceDiscovery(ILogger logger, IServerConfigurationManager config, IServerApplicationHost appHost, INetworkManager networkManager)
+ private SsdpDeviceLocator _DeviceLocator;
+
+ public DeviceDiscovery(ILogger logger, IServerConfigurationManager config)
{
_tokenSource = new CancellationTokenSource();
_logger = logger;
_config = config;
- _appHost = appHost;
- _networkManager = networkManager;
}
- private List<IPAddress> GetLocalIpAddresses()
- {
- return _networkManager.GetLocalIpAddresses().ToList();
- }
-
- public void Start(SsdpHandler ssdpHandler)
+ // Call this method from somewhere in your code to start the search.
+ public void BeginSearch()
{
- _ssdpHandler = ssdpHandler;
- _ssdpHandler.MessageReceived += _ssdpHandler_MessageReceived;
-
- foreach (var localIp in GetLocalIpAddresses())
- {
- try
- {
- CreateListener(localIp);
- }
- catch (Exception e)
- {
- _logger.ErrorException("Failed to Initilize Socket", e);
- }
- }
- }
+ _DeviceLocator = new SsdpDeviceLocator();
- async void _ssdpHandler_MessageReceived(object sender, SsdpMessageEventArgs e)
- {
- string nts;
- e.Headers.TryGetValue("NTS", out nts);
+ // (Optional) Set the filter so we only see notifications for devices we care about
+ // (can be any search target value i.e device type, uuid value etc - any value that appears in the
+ // DiscoverdSsdpDevice.NotificationType property or that is used with the searchTarget parameter of the Search method).
+ //_DeviceLocator.NotificationFilter = "upnp:rootdevice";
- if (String.Equals(e.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase) &&
- String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase) &&
- !_disposed)
- {
- EventHelper.FireEventIfNotNull(DeviceLeft, this, e, _logger);
- return;
- }
+ // Connect our event handler so we process devices as they are found
+ _DeviceLocator.DeviceAvailable += deviceLocator_DeviceAvailable;
+ _DeviceLocator.DeviceUnavailable += _DeviceLocator_DeviceUnavailable;
- try
- {
- if (e.LocalEndPoint == null)
- {
- var ip = (await _appHost.GetLocalIpAddresses().ConfigureAwait(false)).FirstOrDefault(i => !IPAddress.IsLoopback(i));
- if (ip != null)
- {
- e.LocalEndPoint = new IPEndPoint(ip, 0);
- }
- }
-
- if (e.LocalEndPoint != null)
- {
- TryCreateDevice(e);
- }
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating play to controller", ex);
- }
+ // Perform a search so we don't have to wait for devices to broadcast notifications
+ // again to get any results right away (notifications are broadcast periodically).
+ StartAsyncSearch();
}
- private void CreateListener(IPAddress localIp)
+ private void StartAsyncSearch()
{
Task.Factory.StartNew(async (o) =>
{
- try
+ while (!_tokenSource.IsCancellationRequested)
{
- _logger.Info("Creating SSDP listener on {0}", localIp);
-
- var endPoint = new IPEndPoint(localIp, 1900);
-
- using (var socket = GetMulticastSocket(localIp, endPoint))
+ try
{
- var receiveBuffer = new byte[64000];
-
- CreateNotifier(localIp);
-
- while (!_tokenSource.IsCancellationRequested)
- {
- var receivedBytes = await socket.ReceiveAsync(receiveBuffer, 0, 64000);
+ // Enable listening for notifications (optional)
+ _DeviceLocator.StartListeningForNotifications();
- if (receivedBytes > 0)
- {
- var args = SsdpHelper.ParseSsdpResponse(receiveBuffer);
- args.EndPoint = endPoint;
- args.LocalEndPoint = new IPEndPoint(localIp, 0);
-
- _ssdpHandler.LogMessageReceived(args, true);
-
- TryCreateDevice(args);
- }
- }
+ await _DeviceLocator.SearchAsync().ConfigureAwait(false);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error searching for devices", ex);
}
- _logger.Info("SSDP listener - Task completed");
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception e)
- {
- _logger.ErrorException("Error in listener", e);
+ var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
+
+ await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
}
- }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+ }, CancellationToken.None, TaskCreationOptions.LongRunning);
}
- private void CreateNotifier(IPAddress localIp)
+ // Process each found device in the event handler
+ void deviceLocator_DeviceAvailable(object sender, DeviceAvailableEventArgs e)
{
- Task.Factory.StartNew(async (o) =>
- {
- try
- {
- while (true)
- {
- _ssdpHandler.SendSearchMessage(new IPEndPoint(localIp, 1900));
+ var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
- var delay = _config.GetDlnaConfiguration().ClientDiscoveryIntervalSeconds * 1000;
+ var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
- await Task.Delay(delay, _tokenSource.Token).ConfigureAwait(false);
- }
- }
- catch (OperationCanceledException)
- {
- }
- catch (Exception ex)
+ var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
+
+ var args = new GenericEventArgs<UpnpDeviceInfo>
+ {
+ Argument = new UpnpDeviceInfo
{
- _logger.ErrorException("Error in notifier", ex);
+ Location = e.DiscoveredDevice.DescriptionLocation,
+ Headers = headers
}
+ };
- }, _tokenSource.Token, TaskCreationOptions.LongRunning);
+ EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
}
- private Socket GetMulticastSocket(IPAddress localIpAddress, EndPoint localEndpoint)
+ private void _DeviceLocator_DeviceUnavailable(object sender, DeviceUnavailableEventArgs e)
{
- var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(IPAddress.Parse("239.255.255.250"), localIpAddress));
- socket.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 4);
-
- socket.Bind(localEndpoint);
+ var originalHeaders = e.DiscoveredDevice.ResponseHeaders;
- return socket;
- }
+ var headerDict = originalHeaders == null ? new Dictionary<string, KeyValuePair<string, IEnumerable<string>>>() : originalHeaders.ToDictionary(i => i.Key, StringComparer.OrdinalIgnoreCase);
- private void TryCreateDevice(SsdpMessageEventArgs args)
- {
- string nts;
- args.Headers.TryGetValue("NTS", out nts);
+ var headers = headerDict.ToDictionary(i => i.Key, i => i.Value.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase);
- if (String.Equals(nts, "ssdp:byebye", StringComparison.OrdinalIgnoreCase))
+ var args = new GenericEventArgs<UpnpDeviceInfo>
{
- if (String.Equals(args.Method, "NOTIFY", StringComparison.OrdinalIgnoreCase))
+ Argument = new UpnpDeviceInfo
{
- if (!_disposed)
- {
- EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
- }
+ Location = e.DiscoveredDevice.DescriptionLocation,
+ Headers = headers
}
+ };
- return;
- }
-
- string usn;
- if (!args.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
-
- string nt;
- if (!args.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
-
- // Need to be able to download device description
- string location;
- if (!args.Headers.TryGetValue("Location", out location) ||
- string.IsNullOrEmpty(location))
- {
- return;
- }
+ EventHelper.FireEventIfNotNull(DeviceLeft, this, args, _logger);
+ }
- EventHelper.FireEventIfNotNull(DeviceDiscovered, this, args, _logger);
+ public void Start()
+ {
+ BeginSearch();
}
public void Dispose()
{
- if (_ssdpHandler != null)
- {
- _ssdpHandler.MessageReceived -= _ssdpHandler_MessageReceived;
- }
-
if (!_disposed)
{
_disposed = true;
diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
index 720ea71a0..0d0ca98a2 100644
--- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
+++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs
@@ -83,90 +83,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- public event EventHandler<SsdpMessageEventArgs> MessageReceived;
-
- private async void OnMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
- {
- if (IgnoreMessage(args, isMulticast))
- {
- return;
- }
-
- LogMessageReceived(args, isMulticast);
-
- var headers = args.Headers;
- string st;
-
- if (string.Equals(args.Method, "M-SEARCH", StringComparison.OrdinalIgnoreCase) && headers.TryGetValue("st", out st))
- {
- TimeSpan delay = GetSearchDelay(headers);
-
- if (_config.GetDlnaConfiguration().EnableDebugLog)
- {
- _logger.Debug("Delaying search response by {0} seconds", delay.TotalSeconds);
- }
-
- await Task.Delay(delay).ConfigureAwait(false);
-
- RespondToSearch(args.EndPoint, st);
- }
-
- EventHelper.FireEventIfNotNull(MessageReceived, this, args, _logger);
- }
-
- internal void LogMessageReceived(SsdpMessageEventArgs args, bool isMulticast)
- {
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- var headerText = string.Join(",", headerTexts.ToArray());
-
- var protocol = isMulticast ? "Multicast" : "Unicast";
- var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- _logger.Debug("{0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
- }
- }
-
- internal bool IgnoreMessage(SsdpMessageEventArgs args, bool isMulticast)
- {
- string usn;
- if (args.Headers.TryGetValue("USN", out usn))
- {
- // USN=uuid:b67df29b5c379445fde78c3774ab518d::urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
- if (RegisteredDevices.Any(i => string.Equals(i.USN, usn, StringComparison.OrdinalIgnoreCase)))
- {
- //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- //var headerText = string.Join(",", headerTexts.ToArray());
-
- //var protocol = isMulticast ? "Multicast" : "Unicast";
- //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
- return true;
- }
- }
-
- string serverId;
- if (args.Headers.TryGetValue("X-EMBY-SERVERID", out serverId))
- {
- if (string.Equals(serverId, _appHost.SystemId, StringComparison.OrdinalIgnoreCase))
- {
- //var headerTexts = args.Headers.Select(i => string.Format("{0}={1}", i.Key, i.Value));
- //var headerText = string.Join(",", headerTexts.ToArray());
-
- //var protocol = isMulticast ? "Multicast" : "Unicast";
- //var localEndPointString = args.LocalEndPoint == null ? "null" : args.LocalEndPoint.ToString();
- //_logger.Debug("IGNORING {0} message received from {1} on {3}. Protocol: {4} Headers: {2}", args.Method, args.EndPoint, headerText, localEndPointString, protocol);
-
- return true;
- }
- }
-
- return false;
- }
-
public IEnumerable<UpnpDevice> RegisteredDevices
{
get
@@ -188,8 +104,6 @@ namespace MediaBrowser.Dlna.Ssdp
RestartSocketListener();
ReloadAliveNotifier();
- CreateUnicastClient();
-
SystemEvents.PowerModeChanged -= SystemEvents_PowerModeChanged;
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
}
@@ -202,32 +116,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- public void SendSearchMessage(EndPoint localIp)
- {
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- values["HOST"] = "239.255.255.250:1900";
- values["USER-AGENT"] = "UPnP/1.0 DLNADOC/1.50 Platinum/1.0.4.2";
- values["X-EMBY-SERVERID"] = _appHost.SystemId;
-
- values["MAN"] = "\"ssdp:discover\"";
-
- // Search target
- values["ST"] = "ssdp:all";
-
- // Seconds to delay response
- values["MX"] = "3";
-
- var header = "M-SEARCH * HTTP/1.1";
-
- var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
- // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
- SendDatagram(msg, _ssdpEndp, localIp, true);
-
- SendUnicastRequest(msg);
- }
-
public async void SendDatagram(string msg,
EndPoint endpoint,
EndPoint localAddress,
@@ -248,75 +136,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- /// <summary>
- /// According to the spec: http://www.upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0-20080424.pdf
- /// Device responses should be delayed a random duration between 0 and this many seconds to balance
- /// load for the control point when it processes responses. In my testing kodi times out after mx
- /// so we will generate from mx - 1
- /// </summary>
- /// <param name="headers">The mx headers</param>
- /// <returns>A timepsan for the amount to delay before returning search result.</returns>
- private TimeSpan GetSearchDelay(Dictionary<string, string> headers)
- {
- string mx;
- headers.TryGetValue("mx", out mx);
- int delaySeconds = 0;
- if (!string.IsNullOrWhiteSpace(mx)
- && int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds)
- && delaySeconds > 1)
- {
- delaySeconds = new Random().Next(delaySeconds - 1);
- }
-
- return TimeSpan.FromSeconds(delaySeconds);
- }
-
- private void RespondToSearch(EndPoint endpoint, string deviceType)
- {
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- var isLogged = false;
-
- const string header = "HTTP/1.1 200 OK";
-
- foreach (var d in RegisteredDevices)
- {
- if (string.Equals(deviceType, "ssdp:all", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(deviceType, d.Type, StringComparison.OrdinalIgnoreCase))
- {
- if (!isLogged)
- {
- if (enableDebugLogging)
- {
- _logger.Debug("Responding to search from {0} for {1}", endpoint, deviceType);
- }
- isLogged = true;
- }
-
- var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- values["CACHE-CONTROL"] = "max-age = 600";
- values["DATE"] = DateTime.Now.ToString("R");
- values["EXT"] = "";
- values["LOCATION"] = d.Descriptor.ToString();
- values["SERVER"] = _serverSignature;
- values["ST"] = d.Type;
- values["USN"] = d.USN;
-
- var msg = new SsdpMessageBuilder().BuildMessage(header, values);
-
- SendDatagram(msg, endpoint, null, false, 2);
- SendDatagram(msg, endpoint, new IPEndPoint(d.Address, 0), false, 2);
- //SendDatagram(header, values, endpoint, null, true);
-
- if (enableDebugLogging)
- {
- _logger.Debug("{1} - Responded to a {0} request to {2}", d.Type, endpoint, d.Address.ToString());
- }
- }
- }
- }
-
private void RestartSocketListener()
{
if (_isDisposed)
@@ -329,8 +148,6 @@ namespace MediaBrowser.Dlna.Ssdp
_multicastSocket = CreateMulticastSocket();
_logger.Info("MultiCast socket created");
-
- Receive();
}
catch (Exception ex)
{
@@ -339,74 +156,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- private void Receive()
- {
- try
- {
- var buffer = new byte[1024];
-
- EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
- _multicastSocket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref endpoint, ReceiveCallback, buffer);
- }
- catch (ObjectDisposedException)
- {
- if (!_isDisposed)
- {
- //StartSocketRetryTimer();
- }
- }
- catch (Exception ex)
- {
- _logger.Debug("Error in BeginReceiveFrom", ex);
- }
- }
-
- private void ReceiveCallback(IAsyncResult result)
- {
- if (_isDisposed)
- {
- return;
- }
-
- try
- {
- EndPoint endpoint = new IPEndPoint(IPAddress.Any, SSDPPort);
-
- var length = _multicastSocket.EndReceiveFrom(result, ref endpoint);
-
- var received = (byte[])result.AsyncState;
-
- var enableDebugLogging = _config.GetDlnaConfiguration().EnableDebugLog;
-
- if (enableDebugLogging)
- {
- _logger.Debug(Encoding.ASCII.GetString(received));
- }
-
- var args = SsdpHelper.ParseSsdpResponse(received);
- args.EndPoint = endpoint;
-
- OnMessageReceived(args, true);
- }
- catch (ObjectDisposedException)
- {
- if (!_isDisposed)
- {
- //StartSocketRetryTimer();
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Failed to read SSDP message", ex);
- }
-
- if (_multicastSocket != null)
- {
- Receive();
- }
- }
-
public void Dispose()
{
_config.NamedConfigurationUpdated -= _config_ConfigurationUpdated;
@@ -414,7 +163,6 @@ namespace MediaBrowser.Dlna.Ssdp
_isDisposed = true;
- DisposeUnicastClient();
DisposeSocket();
StopAliveNotifier();
}
@@ -523,137 +271,6 @@ namespace MediaBrowser.Dlna.Ssdp
}
}
- private void CreateUnicastClient()
- {
- if (_unicastClient == null)
- {
- try
- {
- _unicastClient = new UdpClient(_unicastPort);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error creating unicast client", ex);
- }
-
- UnicastSetBeginReceive();
- }
- }
-
- private void DisposeUnicastClient()
- {
- if (_unicastClient != null)
- {
- try
- {
- _unicastClient.Close();
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error closing unicast client", ex);
- }
-
- _unicastClient = null;
- }
- }
-
- /// <summary>
- /// Listen for Unicast SSDP Responses
- /// </summary>
- private void UnicastSetBeginReceive()
- {
- try
- {
- var ipRxEnd = new IPEndPoint(IPAddress.Any, _unicastPort);
- var udpListener = new UdpState { EndPoint = ipRxEnd };
-
- udpListener.UdpClient = _unicastClient;
- _unicastClient.BeginReceive(UnicastReceiveCallback, udpListener);
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in UnicastSetBeginReceive", ex);
- }
- }
-
- /// <summary>
- /// The UnicastReceiveCallback receives Http Responses
- /// and Fired the SatIpDeviceFound Event for adding the SatIpDevice
- /// </summary>
- /// <param name="ar"></param>
- private void UnicastReceiveCallback(IAsyncResult ar)
- {
- var udpClient = ((UdpState)(ar.AsyncState)).UdpClient;
- var endpoint = ((UdpState)(ar.AsyncState)).EndPoint;
- if (udpClient.Client != null)
- {
- try
- {
- var responseBytes = udpClient.EndReceive(ar, ref endpoint);
- var args = SsdpHelper.ParseSsdpResponse(responseBytes);
-
- args.EndPoint = endpoint;
-
- OnMessageReceived(args, false);
-
- UnicastSetBeginReceive();
- }
- catch (ObjectDisposedException)
- {
-
- }
- catch (SocketException)
- {
-
- }
- catch (Exception)
- {
- // If called while shutting down, seeing a NullReferenceException inside EndReceive
- }
- }
- }
-
- private void SendUnicastRequest(string request, int sendCount = 3)
- {
- if (_unicastClient == null)
- {
- return;
- }
-
- var ipSsdp = IPAddress.Parse(SSDPAddr);
- var ipTxEnd = new IPEndPoint(ipSsdp, SSDPPort);
-
- SendUnicastRequest(request, ipTxEnd, sendCount);
- }
-
- private async void SendUnicastRequest(string request, IPEndPoint toEndPoint, int sendCount = 3)
- {
- if (_unicastClient == null)
- {
- return;
- }
-
- //_logger.Debug("Sending unicast request");
-
- byte[] req = Encoding.ASCII.GetBytes(request);
-
- try
- {
- for (var i = 0; i < sendCount; i++)
- {
- if (i > 0)
- {
- await Task.Delay(50).ConfigureAwait(false);
- }
- _unicastClient.Send(req, req.Length, toEndPoint);
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error in SendUnicastRequest", ex);
- }
- }
-
private readonly object _notificationTimerSyncLock = new object();
private int _aliveNotifierIntervalMs;
private void ReloadAliveNotifier()
diff --git a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index 134385765..ac127458e 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -63,7 +63,6 @@
<Compile Include="Parsers\MovieXmlParser.cs" />
<Compile Include="Parsers\MusicVideoXmlParser.cs" />
<Compile Include="Parsers\PlaylistXmlParser.cs" />
- <Compile Include="Parsers\SeasonXmlParser.cs" />
<Compile Include="Parsers\SeriesXmlParser.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\BoxSetXmlProvider.cs" />
@@ -75,7 +74,6 @@
<Compile Include="Providers\MusicVideoXmlProvider.cs" />
<Compile Include="Providers\PersonXmlProvider.cs" />
<Compile Include="Providers\PlaylistXmlProvider.cs" />
- <Compile Include="Providers\SeasonXmlProvider.cs" />
<Compile Include="Providers\SeriesXmlProvider.cs" />
<Compile Include="Providers\VideoXmlProvider.cs" />
<Compile Include="Savers\BoxSetXmlSaver.cs" />
diff --git a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
index 772af0673..9ebb357c6 100644
--- a/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/BoxSetXmlParser.cs
@@ -9,8 +9,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
public class BoxSetXmlParser : BaseItemXmlParser<BoxSet>
{
- public BoxSetXmlParser(ILogger logger)
- : base(logger)
+ public BoxSetXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs
index d2ef01465..71f6d3fe9 100644
--- a/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/EpisodeXmlParser.cs
@@ -20,8 +20,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
private List<LocalImageInfo> _imagesFound;
private readonly IFileSystem _fileSystem;
- public EpisodeXmlParser(ILogger logger, IFileSystem fileSystem)
- : base(logger)
+ public EpisodeXmlParser(ILogger logger, IFileSystem fileSystem, IProviderManager providerManager)
+ : base(logger, providerManager)
{
_fileSystem = fileSystem;
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs
index 09cc1fdd7..75df53958 100644
--- a/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/GameSystemXmlParser.cs
@@ -10,8 +10,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
public class GameSystemXmlParser : BaseItemXmlParser<GameSystem>
{
- public GameSystemXmlParser(ILogger logger)
- : base(logger)
+ public GameSystemXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs
index 4bfcae44f..956b8baef 100644
--- a/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/GameXmlParser.cs
@@ -16,8 +16,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
- public GameXmlParser(ILogger logger)
- : base(logger)
+ public GameXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs
index 1c1bbe71e..6e78d365e 100644
--- a/MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/MovieXmlParser.cs
@@ -12,8 +12,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
public class BaseVideoXmlParser<T> : BaseItemXmlParser<T>
where T : Video
{
- public BaseVideoXmlParser(ILogger logger)
- : base(logger)
+ public BaseVideoXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
@@ -50,15 +50,15 @@ namespace MediaBrowser.LocalMetadata.Parsers
public class MovieXmlParser : BaseVideoXmlParser<Movie>
{
- public MovieXmlParser(ILogger logger) : base(logger)
+ public MovieXmlParser(ILogger logger, IProviderManager providerManager) : base(logger, providerManager)
{
}
}
public class VideoXmlParser : BaseVideoXmlParser<Video>
{
- public VideoXmlParser(ILogger logger)
- : base(logger)
+ public VideoXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs
index d93746aa0..5f0b447e8 100644
--- a/MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/MusicVideoXmlParser.cs
@@ -12,8 +12,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
/// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
- public MusicVideoXmlParser(ILogger logger)
- : base(logger)
+ public MusicVideoXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
index d4552fe12..de46c0a86 100644
--- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs
@@ -11,8 +11,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
{
public class PlaylistXmlParser : BaseItemXmlParser<Playlist>
{
- public PlaylistXmlParser(ILogger logger)
- : base(logger)
+ public PlaylistXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
diff --git a/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs
deleted file mode 100644
index 7fd60d3f7..000000000
--- a/MediaBrowser.LocalMetadata/Parsers/SeasonXmlParser.cs
+++ /dev/null
@@ -1,48 +0,0 @@
-using System.Xml;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-
-namespace MediaBrowser.LocalMetadata.Parsers
-{
- public class SeasonXmlParser : BaseItemXmlParser<Season>
- {
- public SeasonXmlParser(ILogger logger)
- : base(logger)
- {
- }
-
- /// <summary>
- /// Fetches the data from XML node.
- /// </summary>
- /// <param name="reader">The reader.</param>
- /// <param name="result">The result.</param>
- protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult<Season> result)
- {
- var item = result.Item;
-
- switch (reader.Name)
- {
- case "SeasonNumber":
- {
- var number = reader.ReadElementContentAsString();
-
- if (!string.IsNullOrWhiteSpace(number))
- {
- int num;
-
- if (int.TryParse(number, out num))
- {
- item.IndexNumber = num;
- }
- }
- break;
- }
-
- default:
- base.FetchDataFromXmlNode(reader, result);
- break;
- }
- }
- }
-}
diff --git a/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs
index 8133bd9fe..7b7fb4751 100644
--- a/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs
+++ b/MediaBrowser.LocalMetadata/Parsers/SeriesXmlParser.cs
@@ -17,8 +17,8 @@ namespace MediaBrowser.LocalMetadata.Parsers
/// Initializes a new instance of the <see cref="BaseItemXmlParser{T}" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
- public SeriesXmlParser(ILogger logger)
- : base(logger)
+ public SeriesXmlParser(ILogger logger, IProviderManager providerManager)
+ : base(logger, providerManager)
{
}
diff --git a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
index 217a5f355..3acb2b74c 100644
--- a/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/BoxSetXmlProvider.cs
@@ -14,16 +14,18 @@ namespace MediaBrowser.LocalMetadata.Providers
public class BoxSetXmlProvider : BaseXmlProvider<BoxSet>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public BoxSetXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<BoxSet> result, string path, CancellationToken cancellationToken)
{
- new BoxSetXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new BoxSetXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs
index d3e365838..493df8c6a 100644
--- a/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/EpisodeXmlProvider.cs
@@ -13,11 +13,13 @@ namespace MediaBrowser.LocalMetadata.Providers
public class EpisodeXmlProvider : BaseXmlProvider<Episode>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public EpisodeXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public EpisodeXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Episode> result, string path, CancellationToken cancellationToken)
@@ -25,7 +27,7 @@ namespace MediaBrowser.LocalMetadata.Providers
var images = new List<LocalImageInfo>();
var chapters = new List<ChapterInfo>();
- new EpisodeXmlParser(_logger, FileSystem).Fetch(result, images, path, cancellationToken);
+ new EpisodeXmlParser(_logger, FileSystem, _providerManager).Fetch(result, images, path, cancellationToken);
result.Images = images;
}
diff --git a/MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs
index 248fad363..7ac41e5cc 100644
--- a/MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/FolderXmlProvider.cs
@@ -13,16 +13,18 @@ namespace MediaBrowser.LocalMetadata.Providers
public class FolderXmlProvider : BaseXmlProvider<Folder>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public FolderXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public FolderXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Folder> result, string path, CancellationToken cancellationToken)
{
- new BaseItemXmlParser<Folder>(_logger).Fetch(result, path, cancellationToken);
+ new BaseItemXmlParser<Folder>(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs
index 646fce805..942befb83 100644
--- a/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/GameSystemXmlProvider.cs
@@ -11,16 +11,18 @@ namespace MediaBrowser.LocalMetadata.Providers
public class GameSystemXmlProvider : BaseXmlProvider<GameSystem>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public GameSystemXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public GameSystemXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<GameSystem> result, string path, CancellationToken cancellationToken)
{
- new GameSystemXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new GameSystemXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs
index 28736eddd..c562df7fb 100644
--- a/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/GameXmlProvider.cs
@@ -11,16 +11,18 @@ namespace MediaBrowser.LocalMetadata.Providers
public class GameXmlProvider : BaseXmlProvider<Game>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public GameXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public GameXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Game> result, string path, CancellationToken cancellationToken)
{
- new GameXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new GameXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs
index e4f83dd1f..333ea2823 100644
--- a/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/MovieXmlProvider.cs
@@ -11,16 +11,18 @@ namespace MediaBrowser.LocalMetadata.Providers
public class MovieXmlProvider : BaseXmlProvider<Movie>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public MovieXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public MovieXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Movie> result, string path, CancellationToken cancellationToken)
{
- new MovieXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new MovieXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs
index 1060fe895..49d8c09cc 100644
--- a/MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/MusicVideoXmlProvider.cs
@@ -10,16 +10,18 @@ namespace MediaBrowser.LocalMetadata.Providers
class MusicVideoXmlProvider : BaseXmlProvider<MusicVideo>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public MusicVideoXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public MusicVideoXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<MusicVideo> result, string path, CancellationToken cancellationToken)
{
- new MusicVideoXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new MusicVideoXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs
index b65977c8e..2ccb8968b 100644
--- a/MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/PersonXmlProvider.cs
@@ -10,16 +10,18 @@ namespace MediaBrowser.LocalMetadata.Providers
public class PersonXmlProvider : BaseXmlProvider<Person>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public PersonXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public PersonXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Person> result, string path, CancellationToken cancellationToken)
{
- new BaseItemXmlParser<Person>(_logger).Fetch(result, path, cancellationToken);
+ new BaseItemXmlParser<Person>(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
index eb9e9a660..149a3142d 100644
--- a/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/PlaylistXmlProvider.cs
@@ -11,16 +11,18 @@ namespace MediaBrowser.LocalMetadata.Providers
class PlaylistXmlProvider : BaseXmlProvider<Playlist>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public PlaylistXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public PlaylistXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Playlist> result, string path, CancellationToken cancellationToken)
{
- new PlaylistXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new PlaylistXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs
deleted file mode 100644
index 7c82d9811..000000000
--- a/MediaBrowser.LocalMetadata/Providers/SeasonXmlProvider.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.IO;
-using System.Threading;
-using CommonIO;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.LocalMetadata.Parsers;
-using MediaBrowser.Model.Logging;
-
-namespace MediaBrowser.LocalMetadata.Providers
-{
- /// <summary>
- /// Class SeriesProviderFromXml
- /// </summary>
- public class SeasonXmlProvider : BaseXmlProvider<Season>, IHasOrder
- {
- private readonly ILogger _logger;
-
- public SeasonXmlProvider(IFileSystem fileSystem, ILogger logger)
- : base(fileSystem)
- {
- _logger = logger;
- }
-
- protected override void Fetch(MetadataResult<Season> result, string path, CancellationToken cancellationToken)
- {
- new SeasonXmlParser(_logger).Fetch(result, path, cancellationToken);
- }
-
- protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
- {
- return directoryService.GetFile(Path.Combine(info.Path, "season.xml"));
- }
-
- public int Order
- {
- get
- {
- // After Xbmc
- return 1;
- }
- }
- }
-}
-
diff --git a/MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs
index 0893f192f..26d3c7539 100644
--- a/MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/SeriesXmlProvider.cs
@@ -14,16 +14,18 @@ namespace MediaBrowser.LocalMetadata.Providers
public class SeriesXmlProvider : BaseXmlProvider<Series>, IHasOrder
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public SeriesXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public SeriesXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Series> result, string path, CancellationToken cancellationToken)
{
- new SeriesXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new SeriesXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs b/MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs
index c7bde4fa8..50f3bcda4 100644
--- a/MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs
+++ b/MediaBrowser.LocalMetadata/Providers/VideoXmlProvider.cs
@@ -10,16 +10,18 @@ namespace MediaBrowser.LocalMetadata.Providers
class VideoXmlProvider : BaseXmlProvider<Video>
{
private readonly ILogger _logger;
+ private readonly IProviderManager _providerManager;
- public VideoXmlProvider(IFileSystem fileSystem, ILogger logger)
+ public VideoXmlProvider(IFileSystem fileSystem, ILogger logger, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Video> result, string path, CancellationToken cancellationToken)
{
- new VideoXmlParser(_logger).Fetch(result, path, cancellationToken);
+ new VideoXmlParser(_logger, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
index d472d4812..ac4ebbefe 100644
--- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
+++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs
@@ -133,7 +133,7 @@ namespace MediaBrowser.LocalMetadata.Savers
/// <param name="xmlTagsUsed">The XML tags used.</param>
public static void Save(StringBuilder xml, string path, List<string> xmlTagsUsed, IServerConfigurationManager config, IFileSystem fileSystem)
{
- if (fileSystem.FileExists(path))
+ if (fileSystem.FileExists(path))
{
var position = xml.ToString().LastIndexOf("</", StringComparison.OrdinalIgnoreCase);
xml.Insert(position, GetCustomTags(path, xmlTagsUsed));
@@ -145,7 +145,7 @@ namespace MediaBrowser.LocalMetadata.Savers
//Add the new node to the document.
xmlDocument.InsertBefore(xmlDocument.CreateXmlDeclaration("1.0", "UTF-8", "yes"), xmlDocument.DocumentElement);
- fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ fileSystem.CreateDirectory(Path.GetDirectoryName(path));
var wasHidden = false;
@@ -445,121 +445,18 @@ namespace MediaBrowser.LocalMetadata.Savers
builder.Append("<RunningTime>" + Convert.ToInt32(timespan.TotalMinutes).ToString(UsCulture) + "</RunningTime>");
}
- var imdb = item.GetProviderId(MetadataProviders.Imdb);
-
- if (!string.IsNullOrEmpty(imdb))
- {
- builder.Append("<IMDB>" + SecurityElement.Escape(imdb) + "</IMDB>");
- }
-
- var tmdb = item.GetProviderId(MetadataProviders.Tmdb);
-
- if (!string.IsNullOrEmpty(tmdb))
- {
- builder.Append("<TMDbId>" + SecurityElement.Escape(tmdb) + "</TMDbId>");
- }
-
- if (!(item is Series))
+ if (item.ProviderIds != null)
{
- var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
-
- if (!string.IsNullOrEmpty(tvdb))
+ foreach (var providerKey in item.ProviderIds.Keys)
{
- builder.Append("<TvDbId>" + SecurityElement.Escape(tvdb) + "</TvDbId>");
+ var providerId = item.ProviderIds[providerKey];
+ if (!string.IsNullOrEmpty(providerId))
+ {
+ builder.Append(string.Format("<{0}>{1}</{0}>", providerKey + "Id", SecurityElement.Escape(providerId)));
+ }
}
}
- var externalId = item.GetProviderId(MetadataProviders.Tvcom);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<TVcomId>" + SecurityElement.Escape(externalId) + "</TVcomId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.RottenTomatoes);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<RottenTomatoesId>" + SecurityElement.Escape(externalId) + "</RottenTomatoesId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.Zap2It);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<Zap2ItId>" + SecurityElement.Escape(externalId) + "</Zap2ItId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<MusicBrainzAlbumId>" + SecurityElement.Escape(externalId) + "</MusicBrainzAlbumId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<MusicBrainzAlbumArtistId>" + SecurityElement.Escape(externalId) + "</MusicBrainzAlbumArtistId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<MusicBrainzArtistId>" + SecurityElement.Escape(externalId) + "</MusicBrainzArtistId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<MusicBrainzReleaseGroupId>" + SecurityElement.Escape(externalId) + "</MusicBrainzReleaseGroupId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.Gamesdb);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<GamesDbId>" + SecurityElement.Escape(externalId) + "</GamesDbId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.TmdbCollection);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<TMDbCollectionId>" + SecurityElement.Escape(externalId) + "</TMDbCollectionId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.AudioDbArtist);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<AudioDbArtistId>" + SecurityElement.Escape(externalId) + "</AudioDbArtistId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.AudioDbAlbum);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<AudioDbAlbumId>" + SecurityElement.Escape(externalId) + "</AudioDbAlbumId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.TvRage);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<TVRageId>" + SecurityElement.Escape(externalId) + "</TVRageId>");
- }
-
- externalId = item.GetProviderId(MetadataProviders.TvMaze);
-
- if (!string.IsNullOrEmpty(externalId))
- {
- builder.Append("<TvMazeId>" + SecurityElement.Escape(externalId) + "</TvMazeId>");
- }
-
var hasTagline = item as IHasTaglines;
if (hasTagline != null)
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
index 6bf414dfa..9d8b7fd63 100644
--- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs
@@ -481,6 +481,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
+ if (state.IsVideoRequest)
+ {
+ var encodingOptions = GetEncodingOptions();
+ var videoEncoder = EncodingJobFactory.GetVideoEncoder(MediaEncoder, state, encodingOptions);
+ if (videoEncoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ arg = "-hwaccel vaapi -hwaccel_output_format yuv420p -vaapi_device " + encodingOptions.VaapiDevice + " " + arg;
+ }
+ }
+
return arg.Trim();
}
@@ -555,7 +565,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
outputSizeParam = await GetOutputSizeParam(state, outputVideoCodec).ConfigureAwait(false);
outputSizeParam = outputSizeParam.TrimEnd('"');
- outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("format", StringComparison.OrdinalIgnoreCase));
+ }
+ else
+ {
+ outputSizeParam = "," + outputSizeParam.Substring(outputSizeParam.IndexOf("scale", StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase) && outputSizeParam.Length == 0)
+ {
+ outputSizeParam = ",format=nv12|vaapi,hwupload";
}
var videoSizeParam = string.Empty;
@@ -585,23 +608,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// Gets the video bitrate to specify on the command line
/// </summary>
/// <param name="state">The state.</param>
- /// <param name="videoCodec">The video codec.</param>
+ /// <param name="videoEncoder">The video codec.</param>
/// <returns>System.String.</returns>
- protected string GetVideoQualityParam(EncodingJob state, string videoCodec)
+ protected string GetVideoQualityParam(EncodingJob state, string videoEncoder)
{
var param = string.Empty;
var isVc1 = state.VideoStream != null &&
string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
- if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
param = "-preset superfast";
param += " -crf 23";
}
- else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
{
param = "-preset fast";
@@ -609,20 +632,20 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
// h264 (h264_qsv)
- else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
{
param = "-preset 7 -look_ahead 0";
}
// h264 (h264_nvenc)
- else if (string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
param = "-preset llhq";
}
// webm
- else if (string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "libvpx", StringComparison.OrdinalIgnoreCase))
{
// Values 0-3, 0 being highest quality but slower
var profileScore = 0;
@@ -649,23 +672,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
qmax);
}
- else if (string.Equals(videoCodec, "mpeg4", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
param = "-mbd rd -flags +mv4+aic -trellis 2 -cmp 2 -subcmp 2 -bf 2";
}
// asf/wmv
- else if (string.Equals(videoCodec, "wmv2", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "wmv2", StringComparison.OrdinalIgnoreCase))
{
param = "-qmin 2";
}
- else if (string.Equals(videoCodec, "msmpeg4", StringComparison.OrdinalIgnoreCase))
+ else if (string.Equals(videoEncoder, "msmpeg4", StringComparison.OrdinalIgnoreCase))
{
param = "-mbd 2";
}
- param += GetVideoBitrateParam(state, videoCodec);
+ param += GetVideoBitrateParam(state, videoEncoder);
var framerate = GetFramerateParam(state);
if (framerate.HasValue)
@@ -680,8 +703,8 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrEmpty(state.Options.Profile))
{
- if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
// not supported by h264_omx
param += " -profile:v " + state.Options.Profile;
@@ -692,9 +715,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!string.IsNullOrEmpty(levelString))
{
+ levelString = NormalizeTranscodingLevel(state.OutputVideoCodec, levelString);
+
// h264_qsv and h264_nvenc expect levels to be expressed as a decimal. libx264 supports decimal and non-decimal format
- if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
- string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) ||
+ string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase))
{
switch (levelString)
{
@@ -730,16 +755,16 @@ namespace MediaBrowser.MediaEncoding.Encoder
break;
}
}
- else if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase))
+ else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase))
{
param += " -level " + levelString;
}
}
- if (!string.Equals(videoCodec, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
- !string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase) &&
+ !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
{
param = "-pix_fmt yuv420p " + param;
}
@@ -747,6 +772,25 @@ namespace MediaBrowser.MediaEncoding.Encoder
return param;
}
+ private string NormalizeTranscodingLevel(string videoCodec, string level)
+ {
+ double requestLevel;
+
+ // Clients may direct play higher than level 41, but there's no reason to transcode higher
+ if (double.TryParse(level, NumberStyles.Any, UsCulture, out requestLevel))
+ {
+ if (string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ if (requestLevel > 41)
+ {
+ return "41";
+ }
+ }
+ }
+
+ return level;
+ }
+
protected string GetVideoBitrateParam(EncodingJob state, string videoCodec)
{
var bitrate = state.OutputVideoBitrate;
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index ad84ffee8..a2707aace 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -928,7 +928,13 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
StartProcess(processWrapper);
- ranToCompletion = process.WaitForExit(10000);
+ var timeoutMs = ConfigurationManager.Configuration.ImageExtractionTimeoutMs;
+ if (timeoutMs <= 0)
+ {
+ timeoutMs = Environment.Is64BitOperatingSystem ? (Environment.ProcessorCount > 2 ? 14000 : 20000) : 40000;
+ }
+
+ ranToCompletion = process.WaitForExit(timeoutMs);
if (!ranToCompletion)
{
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index a63aca11b..dc5e69283 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -818,14 +818,17 @@ namespace MediaBrowser.MediaEncoding.Subtitles
public string GetSubtitleFileCharacterSetFromLanguage(string language)
{
+ // https://developer.xamarin.com/api/type/System.Text.Encoding/
+
switch (language.ToLower())
{
+ case "hun":
+ return "windows-1252";
case "pol":
case "cze":
case "ces":
case "slo":
case "slk":
- case "hun":
case "slv":
case "srp":
case "hrv":
diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
index 351740e6e..ad7dea0a5 100644
--- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
+++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
@@ -175,9 +175,6 @@
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
<Link>Configuration\AccessSchedule.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
- <Link>Configuration\AutoOnOff.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
index 7df8f3126..61f2f3f13 100644
--- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
+++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
@@ -51,7 +51,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
- <Compile Include="..\mediabrowser.model\activity\ActivityLogEntry.cs">
+ <Compile Include="..\MediaBrowser.Model\Activity\ActivityLogEntry.cs">
<Link>Activity\ActivityLogEntry.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\ApiClient\ApiHelpers.cs">
@@ -147,9 +147,6 @@
<Compile Include="..\MediaBrowser.Model\Configuration\AccessSchedule.cs">
<Link>Configuration\AccessSchedule.cs</Link>
</Compile>
- <Compile Include="..\MediaBrowser.Model\Configuration\AutoOnOff.cs">
- <Link>Configuration\AutoOnOff.cs</Link>
- </Compile>
<Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs">
<Link>Configuration\BaseApplicationConfiguration.cs</Link>
</Compile>
@@ -249,7 +246,7 @@
<Compile Include="..\MediaBrowser.Model\Connect\PinStatusResult.cs">
<Link>Connect\PinStatusResult.cs</Link>
</Compile>
- <Compile Include="..\mediabrowser.model\connect\UserLinkType.cs">
+ <Compile Include="..\MediaBrowser.Model\Connect\UserLinkType.cs">
<Link>Connect\UserLinkType.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Devices\ContentUploadHistory.cs">
@@ -948,7 +945,7 @@
<Compile Include="..\MediaBrowser.Model\Querying\UserQuery.cs">
<Link>Querying\UserQuery.cs</Link>
</Compile>
- <Compile Include="..\mediabrowser.model\registration\RegistrationInfo.cs">
+ <Compile Include="..\MediaBrowser.Model\Registration\RegistrationInfo.cs">
<Link>Registration\RegistrationInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Search\SearchHint.cs">
@@ -1020,7 +1017,7 @@
<Compile Include="..\MediaBrowser.Model\Session\UserDataChangeInfo.cs">
<Link>Session\UserDataChangeInfo.cs</Link>
</Compile>
- <Compile Include="..\mediabrowser.model\social\SocialShareInfo.cs">
+ <Compile Include="..\MediaBrowser.Model\Social\SocialShareInfo.cs">
<Link>Social\SocialShareInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Sync\CompleteSyncJobInfo.cs">
diff --git a/MediaBrowser.Model/ApiClient/ServerCredentials.cs b/MediaBrowser.Model/ApiClient/ServerCredentials.cs
index 19f68445e..ddeb7e546 100644
--- a/MediaBrowser.Model/ApiClient/ServerCredentials.cs
+++ b/MediaBrowser.Model/ApiClient/ServerCredentials.cs
@@ -67,7 +67,7 @@ namespace MediaBrowser.Model.ApiClient
}
if (!string.IsNullOrEmpty(server.ManualAddress))
{
- existing.LocalAddress = server.ManualAddress;
+ existing.ManualAddress = server.ManualAddress;
}
if (!string.IsNullOrEmpty(server.Name))
{
diff --git a/MediaBrowser.Model/Configuration/AutoOnOff.cs b/MediaBrowser.Model/Configuration/AutoOnOff.cs
deleted file mode 100644
index e911a0ff1..000000000
--- a/MediaBrowser.Model/Configuration/AutoOnOff.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-
-namespace MediaBrowser.Model.Configuration
-{
- public enum AutoOnOff
- {
- Auto,
- Enabled,
- Disabled
- }
-}
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 3c03dc12a..9a0b2d35b 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -11,6 +11,8 @@ namespace MediaBrowser.Model.Configuration
public string HardwareAccelerationType { get; set; }
public string EncoderAppPath { get; set; }
public string VaapiDevice { get; set; }
+ public int H264Crf { get; set; }
+ public string H264Preset { get; set; }
public EncodingOptions()
{
@@ -19,6 +21,7 @@ namespace MediaBrowser.Model.Configuration
ThrottleDelaySeconds = 180;
EncodingThreadCount = -1;
VaapiDevice = "/dev/dri/card0";
+ H264Crf = 23;
}
}
}
diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs
index 3fe694553..770ad433d 100644
--- a/MediaBrowser.Model/Configuration/LibraryOptions.cs
+++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs
@@ -6,6 +6,9 @@
public bool EnablePhotos { get; set; }
public bool EnableRealtimeMonitor { get; set; }
public int SchemaVersion { get; set; }
+ public bool EnableChapterImageExtraction { get; set; }
+ public bool ExtractChapterImagesDuringLibraryScan { get; set; }
+ public bool DownloadImagesInAdvance { get; set; }
public LibraryOptions()
{
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index a891a422a..e45aa58c5 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -176,7 +176,6 @@ namespace MediaBrowser.Model.Configuration
public string UICulture { get; set; }
public PeopleMetadataOptions PeopleMetadataOptions { get; set; }
- public bool FindInternetTrailers { get; set; }
public bool SaveMetadataHidden { get; set; }
@@ -184,8 +183,6 @@ namespace MediaBrowser.Model.Configuration
public int RemoteClientBitrateLimit { get; set; }
- public AutoOnOff EnableLibraryMonitor { get; set; }
-
public int SharingExpirationDays { get; set; }
public string[] Migrations { get; set; }
@@ -194,8 +191,6 @@ namespace MediaBrowser.Model.Configuration
public int SchemaVersion { get; set; }
public int SqliteCacheSize { get; set; }
- public bool DownloadImagesInAdvance { get; set; }
-
public bool EnableAnonymousUsageReporting { get; set; }
public bool EnableStandaloneMusicKeys { get; set; }
public bool EnableLocalizedGuids { get; set; }
@@ -205,7 +200,10 @@ namespace MediaBrowser.Model.Configuration
public bool DisplayCollectionsView { get; set; }
public string[] LocalNetworkAddresses { get; set; }
public string[] CodecsUsed { get; set; }
+ public bool EnableChannelView { get; set; }
+ public bool EnableExternalContentInSuggestions { get; set; }
+ public int ImageExtractionTimeoutMs { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
/// </summary>
@@ -215,9 +213,11 @@ namespace MediaBrowser.Model.Configuration
Migrations = new string[] { };
CodecsUsed = new string[] { };
SqliteCacheSize = 0;
+ ImageExtractionTimeoutMs = 0;
EnableLocalizedGuids = true;
DisplaySpecialsWithinSeasons = true;
+ EnableExternalContentInSuggestions = true;
ImageSavingConvention = ImageSavingConvention.Compatible;
PublicPort = 8096;
@@ -230,6 +230,7 @@ namespace MediaBrowser.Model.Configuration
EnableAnonymousUsageReporting = true;
EnableAutomaticRestart = true;
+ EnableFolderView = true;
EnableUPnP = true;
SharingExpirationDays = 30;
@@ -239,11 +240,9 @@ namespace MediaBrowser.Model.Configuration
// 5 minutes
MinResumeDurationSeconds = 300;
- EnableLibraryMonitor = AutoOnOff.Auto;
LibraryMonitorDelay = 60;
EnableInternetProviders = true;
- FindInternetTrailers = true;
PathSubstitutions = new PathSubstitution[] { };
ContentTypes = new NameValuePair[] { };
diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs
index 313c5243c..a6785a06a 100644
--- a/MediaBrowser.Model/Configuration/UserConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs
@@ -41,7 +41,6 @@ namespace MediaBrowser.Model.Configuration
public string[] PlainFolderViews { get; set; }
public bool HidePlayedInLatest { get; set; }
- public bool EnableChannelView { get; set; }
public bool RememberAudioSelections { get; set; }
public bool RememberSubtitleSelections { get; set; }
diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs
index 348a781ae..9434ab03a 100644
--- a/MediaBrowser.Model/Dto/BaseItemDto.cs
+++ b/MediaBrowser.Model/Dto/BaseItemDto.cs
@@ -826,6 +826,7 @@ namespace MediaBrowser.Model.Dto
/// </summary>
/// <value>The series count.</value>
public int? SeriesCount { get; set; }
+ public int? ProgramCount { get; set; }
/// <summary>
/// Gets or sets the episode count.
/// </summary>
diff --git a/MediaBrowser.Model/Dto/ItemCounts.cs b/MediaBrowser.Model/Dto/ItemCounts.cs
index 66c3dfebc..8ceb3a86b 100644
--- a/MediaBrowser.Model/Dto/ItemCounts.cs
+++ b/MediaBrowser.Model/Dto/ItemCounts.cs
@@ -26,6 +26,7 @@
/// <value>The game count.</value>
public int GameCount { get; set; }
public int ArtistCount { get; set; }
+ public int ProgramCount { get; set; }
/// <summary>
/// Gets or sets the game system count.
/// </summary>
diff --git a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
index 242a2d24e..ee7dd8b98 100644
--- a/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
+++ b/MediaBrowser.Model/LiveTv/LiveTvOptions.cs
@@ -13,6 +13,7 @@ namespace MediaBrowser.Model.LiveTv
public string SeriesRecordingPath { get; set; }
public bool EnableAutoOrganize { get; set; }
public bool EnableRecordingEncoding { get; set; }
+ public string RecordingEncodingFormat { get; set; }
public bool EnableRecordingSubfolders { get; set; }
public bool EnableOriginalAudioWithEncodedRecordings { get; set; }
@@ -31,6 +32,7 @@ namespace MediaBrowser.Model.LiveTv
TunerHosts = new List<TunerHostInfo>();
ListingProviders = new List<ListingsProviderInfo>();
MediaLocationsCreated = new string[] { };
+ RecordingEncodingFormat = "mp4";
}
}
diff --git a/MediaBrowser.Model/LiveTv/ProgramAudio.cs b/MediaBrowser.Model/LiveTv/ProgramAudio.cs
index 902079b9a..9a272492c 100644
--- a/MediaBrowser.Model/LiveTv/ProgramAudio.cs
+++ b/MediaBrowser.Model/LiveTv/ProgramAudio.cs
@@ -6,6 +6,7 @@
Stereo,
Dolby,
DolbyDigital,
- Thx
+ Thx,
+ Atmos
}
} \ No newline at end of file
diff --git a/MediaBrowser.Model/LiveTv/RecordingQuery.cs b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
index 923d303f8..0ba5f1779 100644
--- a/MediaBrowser.Model/LiveTv/RecordingQuery.cs
+++ b/MediaBrowser.Model/LiveTv/RecordingQuery.cs
@@ -68,6 +68,10 @@ namespace MediaBrowser.Model.LiveTv
/// <value>The fields.</value>
public ItemFields[] Fields { get; set; }
public bool? EnableImages { get; set; }
+ public bool? IsMovie { get; set; }
+ public bool? IsSeries { get; set; }
+ public bool? IsKids { get; set; }
+ public bool? IsSports { get; set; }
public int? ImageTypeLimit { get; set; }
public ImageType[] EnableImageTypes { get; set; }
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index db70b8606..c1a01680d 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -89,7 +89,6 @@
<Compile Include="Chapters\RemoteChapterResult.cs" />
<Compile Include="Collections\CollectionCreationResult.cs" />
<Compile Include="Configuration\AccessSchedule.cs" />
- <Compile Include="Configuration\AutoOnOff.cs" />
<Compile Include="Configuration\ChannelOptions.cs" />
<Compile Include="Configuration\ChapterOptions.cs" />
<Compile Include="Configuration\CinemaModeConfiguration.cs" />
diff --git a/MediaBrowser.Model/System/SystemInfo.cs b/MediaBrowser.Model/System/SystemInfo.cs
index 3d1de5b37..c4d056a61 100644
--- a/MediaBrowser.Model/System/SystemInfo.cs
+++ b/MediaBrowser.Model/System/SystemInfo.cs
@@ -8,6 +8,8 @@ namespace MediaBrowser.Model.System
/// </summary>
public class SystemInfo : PublicSystemInfo
{
+ public PackageVersionClass SystemUpdateLevel { get; set; }
+
/// <summary>
/// Gets or sets the display name of the operating system.
/// </summary>
diff --git a/MediaBrowser.Mono.sln b/MediaBrowser.Mono.sln
index 3d9677fa8..6300a9559 100644
--- a/MediaBrowser.Mono.sln
+++ b/MediaBrowser.Mono.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2013
-VisualStudioVersion = 12.0.30723.0
+# Visual Studio 14
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}"
EndProject
@@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -203,6 +205,18 @@ Global
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Any CPU.Build.0 = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
index cdaa38366..2f534c12e 100644
--- a/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
+++ b/MediaBrowser.Providers/Folders/CollectionFolderMetadataService.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using CommonIO;
+using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -21,4 +22,17 @@ namespace MediaBrowser.Providers.Folders
{
}
}
+
+ public class ManualCollectionsFolderMetadataService : MetadataService<ManualCollectionsFolder, ItemLookupInfo>
+ {
+ protected override void MergeData(MetadataResult<ManualCollectionsFolder> source, MetadataResult<ManualCollectionsFolder> target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+ {
+ ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+ }
+
+ public ManualCollectionsFolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IFileSystem fileSystem, IUserDataManager userDataManager, ILibraryManager libraryManager) : base(serverConfigurationManager, logger, providerManager, fileSystem, userDataManager, libraryManager)
+ {
+ }
+ }
+
}
diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
index 97dd1ed4c..1d8ba85f8 100644
--- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs
+++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs
@@ -56,7 +56,7 @@ namespace MediaBrowser.Providers.Manager
return hasChanges;
}
- public async Task<RefreshResult> RefreshImages(IHasImages item, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
+ public async Task<RefreshResult> RefreshImages(IHasImages item, LibraryOptions libraryOptions, IEnumerable<IImageProvider> imageProviders, ImageRefreshOptions refreshOptions, MetadataOptions savedOptions, CancellationToken cancellationToken)
{
if (refreshOptions.IsReplacingImage(ImageType.Backdrop))
{
@@ -84,7 +84,7 @@ namespace MediaBrowser.Providers.Manager
if (remoteProvider != null)
{
- await RefreshFromProvider(item, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
+ await RefreshFromProvider(item, libraryOptions, remoteProvider, refreshOptions, savedOptions, backdropLimit, screenshotLimit, downloadedImages, result, cancellationToken).ConfigureAwait(false);
providerIds.Add(provider.GetType().FullName.GetMD5());
continue;
}
@@ -249,7 +249,7 @@ namespace MediaBrowser.Providers.Manager
/// <param name="result">The result.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>Task.</returns>
- private async Task RefreshFromProvider(IHasImages item,
+ private async Task RefreshFromProvider(IHasImages item, LibraryOptions libraryOptions,
IRemoteImageProvider provider,
ImageRefreshOptions refreshOptions,
MetadataOptions savedOptions,
@@ -293,7 +293,7 @@ namespace MediaBrowser.Providers.Manager
if (!HasImage(item, imageType) || (refreshOptions.IsReplacingImage(imageType) && !downloadedImages.Contains(imageType)))
{
minWidth = savedOptions.GetMinWidth(imageType);
- var downloaded = await DownloadImage(item, provider, result, list, minWidth, imageType, cancellationToken).ConfigureAwait(false);
+ var downloaded = await DownloadImage(item, libraryOptions, provider, result, list, minWidth, imageType, cancellationToken).ConfigureAwait(false);
if (downloaded)
{
@@ -305,7 +305,7 @@ namespace MediaBrowser.Providers.Manager
if (!item.LockedFields.Contains(MetadataFields.Backdrops))
{
minWidth = savedOptions.GetMinWidth(ImageType.Backdrop);
- await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
+ await DownloadBackdrops(item, libraryOptions, ImageType.Backdrop, backdropLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
}
if (!item.LockedFields.Contains(MetadataFields.Screenshots))
@@ -314,7 +314,7 @@ namespace MediaBrowser.Providers.Manager
if (hasScreenshots != null)
{
minWidth = savedOptions.GetMinWidth(ImageType.Screenshot);
- await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
+ await DownloadBackdrops(item, libraryOptions, ImageType.Screenshot, screenshotLimit, provider, result, list, minWidth, cancellationToken).ConfigureAwait(false);
}
}
}
@@ -472,7 +472,7 @@ namespace MediaBrowser.Providers.Manager
return changed;
}
- private async Task<bool> DownloadImage(IHasImages item,
+ private async Task<bool> DownloadImage(IHasImages item, LibraryOptions libraryOptions,
IRemoteImageProvider provider,
RefreshResult result,
IEnumerable<RemoteImageInfo> images,
@@ -484,7 +484,7 @@ namespace MediaBrowser.Providers.Manager
.Where(i => i.Type == type && !(i.Width.HasValue && i.Width.Value < minWidth))
.ToList();
- if (EnableImageStub(item, type) && eligibleImages.Count > 0)
+ if (EnableImageStub(item, type, libraryOptions) && eligibleImages.Count > 0)
{
SaveImageStub(item, type, eligibleImages.Select(i => i.Url));
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
@@ -518,14 +518,14 @@ namespace MediaBrowser.Providers.Manager
return false;
}
- private bool EnableImageStub(IHasImages item, ImageType type)
+ private bool EnableImageStub(IHasImages item, ImageType type, LibraryOptions libraryOptions)
{
if (item is LiveTvProgram)
{
return true;
}
- if (_config.Configuration.DownloadImagesInAdvance)
+ if (libraryOptions.DownloadImagesInAdvance)
{
return false;
}
@@ -585,7 +585,7 @@ namespace MediaBrowser.Providers.Manager
}, newIndex);
}
- private async Task DownloadBackdrops(IHasImages item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
+ private async Task DownloadBackdrops(IHasImages item, LibraryOptions libraryOptions, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, int minWidth, CancellationToken cancellationToken)
{
foreach (var image in images.Where(i => i.Type == imageType))
{
@@ -601,7 +601,7 @@ namespace MediaBrowser.Providers.Manager
var url = image.Url;
- if (EnableImageStub(item, imageType))
+ if (EnableImageStub(item, imageType, libraryOptions))
{
SaveImageStub(item, imageType, new[] { url });
result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index c535f33ba..e16e76bfc 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -11,6 +11,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Providers.Manager
@@ -120,6 +121,8 @@ namespace MediaBrowser.Providers.Manager
}
}
+ LibraryOptions libraryOptions = null;
+
// Next run remote image providers, but only if local image providers didn't throw an exception
if (!localImagesFailed && refreshOptions.ImageRefreshMode != ImageRefreshMode.ValidationOnly)
{
@@ -127,7 +130,12 @@ namespace MediaBrowser.Providers.Manager
if (providers.Count > 0)
{
- var result = await itemImageProvider.RefreshImages(itemOfType, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
+ if (libraryOptions == null)
+ {
+ libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
+ }
+
+ var result = await itemImageProvider.RefreshImages(itemOfType, libraryOptions, providers, refreshOptions, config, cancellationToken).ConfigureAwait(false);
updateType = updateType | result.UpdateType;
if (result.Failures == 0)
@@ -180,8 +188,13 @@ namespace MediaBrowser.Providers.Manager
item.DateLastRefreshed = default(DateTime);
}
+ if (libraryOptions == null)
+ {
+ libraryOptions = LibraryManager.GetLibraryOptions((BaseItem)item);
+ }
+
// Save to database
- await SaveItem(metadataResult, updateType, cancellationToken).ConfigureAwait(false);
+ await SaveItem(metadataResult, libraryOptions, updateType, cancellationToken).ConfigureAwait(false);
}
await AfterMetadataRefresh(itemOfType, refreshOptions, cancellationToken).ConfigureAwait(false);
@@ -196,17 +209,19 @@ namespace MediaBrowser.Providers.Manager
lookupInfo.Year = result.ProductionYear;
}
- protected async Task SaveItem(MetadataResult<TItemType> result, ItemUpdateType reason, CancellationToken cancellationToken)
+ protected async Task SaveItem(MetadataResult<TItemType> result, LibraryOptions libraryOptions, ItemUpdateType reason, CancellationToken cancellationToken)
{
if (result.Item.SupportsPeople && result.People != null)
{
- await LibraryManager.UpdatePeople(result.Item as BaseItem, result.People.ToList());
- await SavePeopleMetadata(result.People, cancellationToken).ConfigureAwait(false);
+ var baseItem = result.Item as BaseItem;
+
+ await LibraryManager.UpdatePeople(baseItem, result.People.ToList());
+ await SavePeopleMetadata(result.People, libraryOptions, cancellationToken).ConfigureAwait(false);
}
await result.Item.UpdateToRepository(reason, cancellationToken).ConfigureAwait(false);
}
- private async Task SavePeopleMetadata(List<PersonInfo> people, CancellationToken cancellationToken)
+ private async Task SavePeopleMetadata(List<PersonInfo> people, LibraryOptions libraryOptions, CancellationToken cancellationToken)
{
foreach (var person in people)
{
@@ -229,7 +244,7 @@ namespace MediaBrowser.Providers.Manager
if (!string.IsNullOrWhiteSpace(person.ImageUrl) && !personEntity.HasImage(ImageType.Primary))
{
- await AddPersonImage(personEntity, person.ImageUrl, cancellationToken).ConfigureAwait(false);
+ await AddPersonImage(personEntity, libraryOptions, person.ImageUrl, cancellationToken).ConfigureAwait(false);
saveEntity = true;
updateType = updateType | ItemUpdateType.ImageUpdate;
@@ -243,9 +258,9 @@ namespace MediaBrowser.Providers.Manager
}
}
- private async Task AddPersonImage(Person personEntity, string imageUrl, CancellationToken cancellationToken)
+ private async Task AddPersonImage(Person personEntity, LibraryOptions libraryOptions, string imageUrl, CancellationToken cancellationToken)
{
- if (ServerConfigurationManager.Configuration.DownloadImagesInAdvance)
+ if (libraryOptions.DownloadImagesInAdvance)
{
try
{
@@ -532,8 +547,6 @@ namespace MediaBrowser.Providers.Manager
}
catch (Exception ex)
{
- refreshResult.Failures++;
-
Logger.ErrorException("Error in {0}", ex, provider.Name);
// If a local provider fails, consider that a failure
@@ -647,8 +660,6 @@ namespace MediaBrowser.Providers.Manager
if (result.HasMetadata)
{
- NormalizeRemoteResult(result.Item);
-
MergeData(result, temp, new List<MetadataFields>(), false, false);
refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataDownload;
@@ -673,19 +684,6 @@ namespace MediaBrowser.Providers.Manager
return refreshResult;
}
- private void NormalizeRemoteResult(TItemType item)
- {
- if (!ServerConfigurationManager.Configuration.FindInternetTrailers)
- {
- var hasTrailers = item as IHasTrailers;
-
- if (hasTrailers != null)
- {
- hasTrailers.RemoteTrailers.Clear();
- }
- }
- }
-
private void MergeNewData(TItemType source, TIdType lookupInfo)
{
// Copy new provider id's that may have been obtained
diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
index c20823535..0f8cf93fb 100644
--- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
+++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
@@ -261,11 +261,18 @@ namespace MediaBrowser.Providers.MediaInfo
NormalizeChapterNames(chapters);
+ var libraryOptions = _libraryManager.GetLibraryOptions(video);
+ var extractDuringScan = chapterOptions.ExtractDuringLibraryScan;
+ if (libraryOptions != null && libraryOptions.SchemaVersion >= 2)
+ {
+ extractDuringScan = libraryOptions.ExtractChapterImagesDuringLibraryScan;
+ }
+
await _encodingManager.RefreshChapterImages(new ChapterImageRefreshOptions
{
Chapters = chapters,
Video = video,
- ExtractImages = chapterOptions.ExtractDuringLibraryScan,
+ ExtractImages = extractDuringScan,
SaveChapters = false
}, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
index 18f177932..2a40e4d85 100644
--- a/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
+++ b/MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
@@ -20,6 +20,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Providers.TV;
namespace MediaBrowser.Providers.Movies
@@ -59,30 +60,13 @@ namespace MediaBrowser.Providers.Movies
public bool Supports(IHasImages item)
{
- //var channelItem = item as IChannelMediaItem;
-
- //if (channelItem != null)
- //{
- // if (channelItem.ContentType == ChannelMediaContentType.Movie)
- // {
- // return true;
- // }
- // if (channelItem.ContentType == ChannelMediaContentType.MovieExtra)
- // {
- // if (channelItem.ExtraType == ExtraType.Trailer)
- // {
- // return true;
- // }
- // }
- //}
-
// Supports images for tv movies
- //var tvProgram = item as LiveTvProgram;
- //if (tvProgram != null && tvProgram.IsMovie)
- //{
- // return true;
- //}
-
+ var tvProgram = item as LiveTvProgram;
+ if (tvProgram != null && tvProgram.IsMovie)
+ {
+ return true;
+ }
+
return item is Movie || item is BoxSet || item is MusicVideo;
}
diff --git a/MediaBrowser.Providers/Music/AudioMetadataService.cs b/MediaBrowser.Providers/Music/AudioMetadataService.cs
index 532128186..67ddd8981 100644
--- a/MediaBrowser.Providers/Music/AudioMetadataService.cs
+++ b/MediaBrowser.Providers/Music/AudioMetadataService.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Logging;
using MediaBrowser.Providers.Manager;
using System.Collections.Generic;
+using System.Linq;
using CommonIO;
namespace MediaBrowser.Providers.Music
@@ -21,7 +22,7 @@ namespace MediaBrowser.Providers.Music
if (replaceData || targetItem.Artists.Count == 0)
{
- targetItem.Artists = sourceItem.Artists;
+ targetItem.Artists = sourceItem.Artists.ToList();
}
if (replaceData || string.IsNullOrEmpty(targetItem.Album))
diff --git a/MediaBrowser.Providers/TV/DummySeasonProvider.cs b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
index fe0ad78be..b74eac219 100644
--- a/MediaBrowser.Providers/TV/DummySeasonProvider.cs
+++ b/MediaBrowser.Providers/TV/DummySeasonProvider.cs
@@ -138,8 +138,6 @@ namespace MediaBrowser.Providers.TV
.Where(i => i.LocationType == LocationType.Virtual)
.ToList();
- var episodes = series.GetRecursiveChildren().OfType<Episode>().ToList();
-
var seasonsToRemove = virtualSeasons
.Where(i =>
{
@@ -152,19 +150,15 @@ namespace MediaBrowser.Providers.TV
{
return true;
}
+ }
- // If there are no episodes with this season number, delete it
- if (episodes.All(e => !e.ParentIndexNumber.HasValue || e.ParentIndexNumber.Value != seasonNumber))
- {
- return true;
- }
-
- return false;
+ // If there are no episodes with this season number, delete it
+ if (!i.GetEpisodes().Any())
+ {
+ return true;
}
- // Season does not have a number
- // Remove if there are no episodes directly in series without a season number
- return episodes.All(s => s.ParentIndexNumber.HasValue || s.IsInSeasonFolder);
+ return false;
})
.ToList();
diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
index bb7e142b6..b76cf46b0 100644
--- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
+++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
@@ -530,6 +530,19 @@ namespace MediaBrowser.Server.Implementations.Channels
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
}
+ public bool SupportsSync(string channelId)
+ {
+ if (string.IsNullOrWhiteSpace(channelId))
+ {
+ throw new ArgumentNullException("channelId");
+ }
+
+ //var channel = GetChannel(channelId);
+ var channelProvider = GetChannelProvider(channelId);
+
+ return channelProvider.GetChannelFeatures().SupportsContentDownloading;
+ }
+
public ChannelFeatures GetChannelFeaturesDto(Channel channel,
IChannel provider,
InternalChannelFeatures features)
@@ -1450,6 +1463,24 @@ namespace MediaBrowser.Server.Implementations.Channels
return result;
}
+ internal IChannel GetChannelProvider(string internalChannelId)
+ {
+ if (internalChannelId == null)
+ {
+ throw new ArgumentNullException("internalChannelId");
+ }
+
+ var result = GetAllChannels()
+ .FirstOrDefault(i => string.Equals(GetInternalChannelId(i.Name).ToString("N"), internalChannelId, StringComparison.OrdinalIgnoreCase));
+
+ if (result == null)
+ {
+ throw new ResourceNotFoundException("No channel provider found for channel id " + internalChannelId);
+ }
+
+ return result;
+ }
+
private IEnumerable<BaseItem> ApplyFilters(IEnumerable<BaseItem> items, IEnumerable<ItemFilter> filters, User user)
{
foreach (var filter in filters.OrderByDescending(f => (int)f))
diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs
index 6cd9e9620..50bb6c559 100644
--- a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs
+++ b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs
@@ -2,6 +2,7 @@
using MediaBrowser.Controller.Entities;
using System.IO;
using CommonIO;
+using MediaBrowser.Controller.Collections;
namespace MediaBrowser.Server.Implementations.Collections
{
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
index 28a62c012..2b2373a47 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectEntryPoint.cs
@@ -43,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.Connect
{
LoadCachedAddress();
- _timer = new PeriodicTimer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
+ _timer = new PeriodicTimer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(1));
((ConnectManager)_connectManager).Start();
}
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
index 45cb2f57d..e7e52a887 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -450,7 +450,7 @@ namespace MediaBrowser.Server.Implementations.Connect
if (!string.IsNullOrWhiteSpace(user.ConnectUserId))
{
- await RemoveConnect(user, connectUser.Id).ConfigureAwait(false);
+ await RemoveConnect(user, user.ConnectUserId).ConfigureAwait(false);
}
var url = GetConnectUrl("ServerAuthorizations");
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index be68162ca..ae676626d 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -437,6 +437,7 @@ namespace MediaBrowser.Server.Implementations.Dto
dto.TrailerCount = taggedItems.Count(i => i is Trailer);
dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo);
dto.SeriesCount = taggedItems.Count(i => i is Series);
+ dto.ProgramCount = taggedItems.Count(i => i is LiveTvProgram);
dto.SongCount = taggedItems.Count(i => i is Audio);
}
@@ -1168,6 +1169,26 @@ namespace MediaBrowser.Server.Implementations.Dto
};
})
.ToList();
+
+ // Include artists that are not in the database yet, e.g., just added via metadata editor
+ var foundArtists = artistItems.Items.Select(i => i.Item1.Name).ToList();
+ dto.ArtistItems.AddRange(hasArtist.Artists
+ .Except(foundArtists, new DistinctNameComparer())
+ .Select(i =>
+ {
+ var artist = _libraryManager.GetArtist(i);
+ if (artist != null)
+ {
+ return new NameIdPair
+ {
+ Name = artist.Name,
+ Id = artist.Id.ToString("N")
+ };
+ }
+
+ return null;
+
+ }).Where(i => i != null));
}
var hasAlbumArtist = item as IHasAlbumArtist;
@@ -1496,10 +1517,7 @@ namespace MediaBrowser.Server.Implementations.Dto
if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
{
- foreach (var map in _config.Configuration.PathSubstitutions)
- {
- path = _libraryManager.SubstitutePath(path, map.From, map.To);
- }
+ path = _libraryManager.GetPathAfterNetworkSubstitution(path);
}
return path;
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
index 280bec65b..1021d8823 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Net;
using MediaBrowser.Common.Threading;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.EntryPoints
{
@@ -17,17 +18,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
private readonly IServerApplicationHost _appHost;
private readonly ILogger _logger;
private readonly IServerConfigurationManager _config;
- private readonly ISsdpHandler _ssdp;
+ private readonly IDeviceDiscovery _deviceDiscovery;
private PeriodicTimer _timer;
private bool _isStarted;
- public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, ISsdpHandler ssdp)
+ public ExternalPortForwarding(ILogManager logmanager, IServerApplicationHost appHost, IServerConfigurationManager config, IDeviceDiscovery deviceDiscovery)
{
_logger = logmanager.GetLogger("PortMapper");
_appHost = appHost;
_config = config;
- _ssdp = ssdp;
+ _deviceDiscovery = deviceDiscovery;
}
private string _lastConfigIdentifier;
@@ -61,7 +62,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
public void Run()
{
- //NatUtility.Logger = new LogWriter(_logger);
+ NatUtility.Logger = _logger;
if (_config.Configuration.EnableUPnP)
{
@@ -93,33 +94,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = new PeriodicTimer(ClearCreatedRules, null, TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
- _ssdp.MessageReceived += _ssdp_MessageReceived;
+ _deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
_lastConfigIdentifier = GetConfigIdentifier();
_isStarted = true;
}
- private void ClearCreatedRules(object state)
- {
- _createdRules = new List<string>();
- _usnsHandled = new List<string>();
- }
-
- void _ssdp_MessageReceived(object sender, SsdpMessageEventArgs e)
+ private async void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
- var endpoint = e.EndPoint as IPEndPoint;
-
- if (endpoint == null || e.LocalEndPoint == null)
- {
- return;
- }
+ var info = e.Argument;
string usn;
- if (!e.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
+ if (!info.Headers.TryGetValue("USN", out usn)) usn = string.Empty;
string nt;
- if (!e.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
+ if (!info.Headers.TryGetValue("NT", out nt)) nt = string.Empty;
// Filter device type
if (usn.IndexOf("WANIPConnection:", StringComparison.OrdinalIgnoreCase) == -1 &&
@@ -132,15 +122,45 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
var identifier = string.IsNullOrWhiteSpace(usn) ? nt : usn;
- if (!_usnsHandled.Contains(identifier))
+ if (info.Location != null && !_usnsHandled.Contains(identifier))
{
_usnsHandled.Add(identifier);
_logger.Debug("Calling Nat.Handle on " + identifier);
- NatUtility.Handle(e.LocalEndPoint.Address, e.Message, endpoint, NatProtocol.Upnp);
+
+ IPAddress address;
+ if (IPAddress.TryParse(info.Location.Host, out address))
+ {
+ // The Handle method doesn't need the port
+ var endpoint = new IPEndPoint(address, info.Location.Port);
+
+ IPAddress localAddress = null;
+
+ try
+ {
+ var localAddressString = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
+
+ if (!IPAddress.TryParse(localAddressString, out localAddress))
+ {
+ return;
+ }
+ }
+ catch
+ {
+ return;
+ }
+
+ NatUtility.Handle(localAddress, info, endpoint, NatProtocol.Upnp);
+ }
}
}
+ private void ClearCreatedRules(object state)
+ {
+ _createdRules = new List<string>();
+ _usnsHandled = new List<string>();
+ }
+
void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
var ex = e.ExceptionObject as Exception;
@@ -228,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
_timer = null;
}
- _ssdp.MessageReceived -= _ssdp_MessageReceived;
+ _deviceDiscovery.DeviceDiscovered -= _deviceDiscovery_DeviceDiscovered;
try
{
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs
index f82bb01bb..d14bd4368 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs
@@ -92,11 +92,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
DeviceId = session.DeviceId
};
- // Report usage to remote server, except for web client, since we already have data on that
- if (!string.Equals(info.AppName, "Dashboard", StringComparison.OrdinalIgnoreCase))
- {
- ReportNewSession(info);
- }
+ ReportNewSession(info);
return info;
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index 51a53fe21..8e46f8f03 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -71,14 +71,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer
HostConfig.Instance.MapExceptionToStatusCode = new Dictionary<Type, int>
{
- {typeof (InvalidOperationException), 422},
+ {typeof (InvalidOperationException), 500},
+ {typeof (NotImplementedException), 500},
{typeof (ResourceNotFoundException), 404},
{typeof (FileNotFoundException), 404},
{typeof (DirectoryNotFoundException), 404},
{typeof (SecurityException), 401},
{typeof (PaymentRequiredException), 402},
{typeof (UnauthorizedAccessException), 500},
- {typeof (ApplicationException), 500}
+ {typeof (ApplicationException), 500},
+ {typeof (PlatformNotSupportedException), 500},
+ {typeof (NotSupportedException), 500}
};
HostConfig.Instance.GlobalResponseHeaders = new Dictionary<string, string>();
@@ -99,14 +102,14 @@ namespace MediaBrowser.Server.Implementations.HttpServer
// new SessionAuthProvider(_containerAdapter.Resolve<ISessionContext>()),
//}));
- PreRequestFilters.Add((httpReq, httpRes) =>
- {
- //Handles Request and closes Responses after emitting global HTTP Headers
- if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
- {
- httpRes.EndRequest(); //add a 'using ServiceStack;'
- }
- });
+ //PreRequestFilters.Add((httpReq, httpRes) =>
+ //{
+ // //Handles Request and closes Responses after emitting global HTTP Headers
+ // if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
+ // {
+ // httpRes.EndRequest(); //add a 'using ServiceStack;'
+ // }
+ //});
HostContext.GlobalResponseFilters.Add(new ResponseFilter(_logger).FilterResponse);
}
@@ -400,6 +403,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return;
}
+ if (string.Equals(httpReq.Verb, "OPTIONS", StringComparison.OrdinalIgnoreCase))
+ {
+ httpRes.StatusCode = 200;
+ httpRes.AddHeader("Access-Control-Allow-Origin", "*");
+ httpRes.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
+ httpRes.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Range, X-MediaBrowser-Token, X-Emby-Authorization");
+ httpRes.ContentType = "text/html";
+
+ httpRes.Close();
+ }
+
var operationName = httpReq.OperationName;
var localPath = url.LocalPath;
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index 7ed4dc71e..8bb40a00e 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -46,6 +46,14 @@ namespace MediaBrowser.Server.Implementations.IO
"TempSBE"
};
+ private readonly IReadOnlyList<string> _alwaysIgnoreSubstrings = new List<string>
+ {
+ // Synology
+ "@eaDir",
+ ".wd_tv",
+ ".actors"
+ };
+
private readonly IReadOnlyList<string> _alwaysIgnoreExtensions = new List<string>
{
// thumbs.db
@@ -98,7 +106,14 @@ namespace MediaBrowser.Server.Implementations.IO
if (refreshPath)
{
- ReportFileSystemChanged(path);
+ try
+ {
+ ReportFileSystemChanged(path);
+ }
+ catch (Exception ex)
+ {
+ Logger.ErrorException("Error in ReportFileSystemChanged for {0}", ex, path);
+ }
}
}
@@ -156,32 +171,16 @@ namespace MediaBrowser.Server.Implementations.IO
Start();
}
- private bool EnableLibraryMonitor
- {
- get
- {
- switch (ConfigurationManager.Configuration.EnableLibraryMonitor)
- {
- case AutoOnOff.Auto:
- return Environment.OSVersion.Platform == PlatformID.Win32NT;
- case AutoOnOff.Enabled:
- return true;
- default:
- return false;
- }
- }
- }
-
private bool IsLibraryMonitorEnabaled(BaseItem item)
{
var options = LibraryManager.GetLibraryOptions(item);
- if (options != null && options.SchemaVersion >= 1)
+ if (options != null)
{
return options.EnableRealtimeMonitor;
}
- return EnableLibraryMonitor;
+ return false;
}
public void Start()
@@ -421,10 +420,11 @@ namespace MediaBrowser.Server.Implementations.IO
}
var filename = Path.GetFileName(path);
-
+
var monitorPath = !string.IsNullOrEmpty(filename) &&
!_alwaysIgnoreFiles.Contains(filename, StringComparer.OrdinalIgnoreCase) &&
- !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ !_alwaysIgnoreExtensions.Contains(Path.GetExtension(path) ?? string.Empty, StringComparer.OrdinalIgnoreCase) &&
+ _alwaysIgnoreSubstrings.All(i => path.IndexOf(i, StringComparison.OrdinalIgnoreCase) == -1);
// Ignore certain files
var tempIgnorePaths = _tempIgnoredPaths.Keys.ToList();
diff --git a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
index 7c7a535cd..4a43befed 100644
--- a/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
+++ b/MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
@@ -216,7 +216,8 @@ namespace MediaBrowser.Server.Implementations.Intros
}
return allIntros
- .Where(i => IsMatch(i.Path, codec));
+ .Where(i => IsMatch(i.Path, codec))
+ .OrderBy(i => Guid.NewGuid());
}
private IEnumerable<IntroInfo> GetMediaInfoIntrosByAudioStream(List<IntroInfo> allIntros, MediaStream stream)
@@ -229,13 +230,15 @@ namespace MediaBrowser.Server.Implementations.Intros
}
return allIntros
- .Where(i => IsAudioMatch(i.Path, stream));
+ .Where(i => IsAudioMatch(i.Path, stream))
+ .OrderBy(i => Guid.NewGuid());
}
private IEnumerable<IntroInfo> GetMediaInfoIntrosByTags(List<IntroInfo> allIntros, List<string> tags)
{
return allIntros
- .Where(i => tags.Any(t => IsMatch(i.Path, t)));
+ .Where(i => tags.Any(t => IsMatch(i.Path, t)))
+ .OrderBy(i => Guid.NewGuid());
}
private bool IsMatch(string file, string attribute)
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index cc3a7e41f..1f8c77953 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -401,7 +401,7 @@ namespace MediaBrowser.Server.Implementations.Library
var locationType = item.LocationType;
var children = item.IsFolder
- ? ((Folder)item).GetRecursiveChildren().ToList()
+ ? ((Folder)item).GetRecursiveChildren(false).ToList()
: new List<BaseItem>();
foreach (var metadataPath in GetMetadataPaths(item, children))
@@ -621,9 +621,38 @@ namespace MediaBrowser.Server.Implementations.Library
return ResolveItem(args, resolvers);
}
+ private readonly List<string> _ignoredPaths = new List<string>();
+
+ public void RegisterIgnoredPath(string path)
+ {
+ lock (_ignoredPaths)
+ {
+ _ignoredPaths.Add(path);
+ }
+ }
+ public void UnRegisterIgnoredPath(string path)
+ {
+ lock (_ignoredPaths)
+ {
+ _ignoredPaths.Remove(path);
+ }
+ }
+
public bool IgnoreFile(FileSystemMetadata file, BaseItem parent)
{
- return EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent));
+ if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent)))
+ {
+ return true;
+ }
+
+ //lock (_ignoredPaths)
+ {
+ if (_ignoredPaths.Contains(file.FullName, StringComparer.OrdinalIgnoreCase))
+ {
+ return true;
+ }
+ }
+ return false;
}
public IEnumerable<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
@@ -666,7 +695,7 @@ namespace MediaBrowser.Server.Implementations.Library
public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files,
IDirectoryService directoryService,
- Folder parent,
+ Folder parent,
LibraryOptions libraryOptions,
string collectionType,
IItemResolver[] resolvers)
@@ -1088,10 +1117,6 @@ namespace MediaBrowser.Server.Implementations.Library
await RunPostScanTasks(innerProgress, cancellationToken).ConfigureAwait(false);
progress.Report(100);
-
- // Bad practice, i know. But we keep a lot in memory, unfortunately.
- GC.Collect(2, GCCollectionMode.Forced, true);
- GC.Collect(2, GCCollectionMode.Forced, true);
}
/// <summary>
@@ -1465,10 +1490,10 @@ namespace MediaBrowser.Server.Implementations.Library
private void AddUserToQuery(InternalItemsQuery query, User user)
{
- if (query.AncestorIds.Length == 0 &&
- !query.ParentId.HasValue &&
- query.ChannelIds.Length == 0 &&
- query.TopParentIds.Length == 0 &&
+ if (query.AncestorIds.Length == 0 &&
+ !query.ParentId.HasValue &&
+ query.ChannelIds.Length == 0 &&
+ query.TopParentIds.Length == 0 &&
string.IsNullOrWhiteSpace(query.AncestorWithPresentationUniqueKey)
&& query.ItemIds.Length == 0)
{
@@ -2502,6 +2527,16 @@ namespace MediaBrowser.Server.Implementations.Library
}).OrderBy(i => i.Path).ToList();
}
+ public string GetPathAfterNetworkSubstitution(string path)
+ {
+ foreach (var map in ConfigurationManager.Configuration.PathSubstitutions)
+ {
+ path = SubstitutePath(path, map.From, map.To);
+ }
+
+ return path;
+ }
+
public string SubstitutePath(string path, string from, string to)
{
if (string.IsNullOrWhiteSpace(path))
@@ -2517,7 +2552,7 @@ namespace MediaBrowser.Server.Implementations.Library
throw new ArgumentNullException("to");
}
- var newPath = path.Replace(from, to, StringComparison.OrdinalIgnoreCase);
+ var newPath = path.Replace(from.Trim(), to.Trim(), StringComparison.OrdinalIgnoreCase);
if (!string.Equals(newPath, path))
{
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
index aefb29f1a..4c6254330 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
@@ -54,14 +54,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
if (args.IsDirectory)
{
+ if (args.HasParent<Series>())
+ {
+ return null;
+ }
+
var collectionType = args.GetCollectionType();
if (string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
- if (args.HasParent<Series>())
- {
- return null;
- }
-
var configuredContentType = _libraryManager.GetConfiguredContentType(args.Path);
if (!string.Equals(configuredContentType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
{
@@ -76,11 +76,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
{
if (string.IsNullOrWhiteSpace(collectionType))
{
- if (args.HasParent<Series>())
- {
- return null;
- }
-
if (args.Parent.IsRoot)
{
return null;
diff --git a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs
index 5fffa3d1f..2cbee7c97 100644
--- a/MediaBrowser.Server.Implementations/Library/UserViewManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/UserViewManager.cs
@@ -120,8 +120,8 @@ namespace MediaBrowser.Server.Implementations.Library
}, cancellationToken).ConfigureAwait(false);
var channels = channelResult.Items;
-
- if (user.Configuration.EnableChannelView && channels.Length > 0)
+
+ if (_config.Configuration.EnableChannelView && channels.Length > 0)
{
list.Add(await _channelManager.GetInternalChannelFolder(cancellationToken).ConfigureAwait(false));
}
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs
index d90b9615b..93b9c0da1 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -165,10 +165,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
progress.Report(100);
_logger.Info("People validation complete");
-
- // Bad practice, i know. But we keep a lot in memory, unfortunately.
- GC.Collect(2, GCCollectionMode.Forced, true);
- GC.Collect(2, GCCollectionMode.Forced, true);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index b21aa904b..2e3edf3e9 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -61,11 +61,44 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
}
- await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+ await CopyUntilCancelled(response.Content, output, cancellationToken).ConfigureAwait(false);
}
}
_logger.Info("Recording completed to file {0}", targetFile);
}
+
+ private const int BufferSize = 81920;
+ public static async Task CopyUntilCancelled(Stream source, Stream target, CancellationToken cancellationToken)
+ {
+ while (!cancellationToken.IsCancellationRequested)
+ {
+ var bytesRead = await CopyToAsyncInternal(source, target, BufferSize, cancellationToken).ConfigureAwait(false);
+
+ //var position = fs.Position;
+ //_logger.Debug("Streamed {0} bytes to position {1} from file {2}", bytesRead, position, path);
+
+ if (bytesRead == 0)
+ {
+ await Task.Delay(100).ConfigureAwait(false);
+ }
+ }
+ }
+
+ private static async Task<int> CopyToAsyncInternal(Stream source, Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
+ {
+ byte[] buffer = new byte[bufferSize];
+ int bytesRead;
+ int totalBytesRead = 0;
+
+ while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ {
+ await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
+
+ totalBytesRead += bytesRead;
+ }
+
+ return totalBytesRead;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 6acb0783e..3eb032ba8 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -27,13 +27,12 @@ using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.Configuration;
using Microsoft.Win32;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
- public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IHasRegistrationInfo, IDisposable
+ public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IDisposable
{
private readonly IApplicationHost _appHpst;
private readonly ILogger _logger;
@@ -46,7 +45,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly LiveTvManager _liveTvManager;
private readonly IFileSystem _fileSystem;
- private readonly ISecurityManager _security;
private readonly ILibraryMonitor _libraryMonitor;
private readonly ILibraryManager _libraryManager;
@@ -62,7 +60,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
- public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement)
+ public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder)
{
Current = this;
@@ -71,7 +69,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_httpClient = httpClient;
_config = config;
_fileSystem = fileSystem;
- _security = security;
_libraryManager = libraryManager;
_libraryMonitor = libraryMonitor;
_providerManager = providerManager;
@@ -81,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_jsonSerializer = jsonSerializer;
_seriesTimerProvider = new SeriesTimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "seriestimers"));
- _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), powerManagement, _logger);
+ _timerProvider = new TimerManager(fileSystem, jsonSerializer, _logger, Path.Combine(DataPath, "timers"), _logger);
_timerProvider.TimerFired += _timerProvider_TimerFired;
_config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated;
@@ -464,11 +461,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return CreateSeriesTimer(info, cancellationToken);
}
- public Task<string> CreateTimer(TimerInfo info, CancellationToken cancellationToken)
+ public Task<string> CreateTimer(TimerInfo timer, CancellationToken cancellationToken)
{
- info.Id = Guid.NewGuid().ToString("N");
- _timerProvider.Add(info);
- return Task.FromResult(info.Id);
+ timer.Id = Guid.NewGuid().ToString("N");
+
+ ProgramInfo programInfo = null;
+
+ if (!string.IsNullOrWhiteSpace(timer.ProgramId))
+ {
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
+ }
+ if (programInfo == null)
+ {
+ _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+ }
+
+ if (programInfo != null)
+ {
+ RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer);
+ }
+
+ _timerProvider.Add(timer);
+ return Task.FromResult(timer.Id);
}
public async Task<string> CreateSeriesTimer(SeriesTimerInfo info, CancellationToken cancellationToken)
@@ -583,7 +598,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
PrePaddingSeconds = Math.Max(config.PrePaddingSeconds, 0),
RecordAnyChannel = true,
RecordAnyTime = true,
- RecordNewOnly = false,
+ RecordNewOnly = true,
Days = new List<DayOfWeek>
{
@@ -733,6 +748,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return result.Item1;
}
+ catch (FileNotFoundException)
+ {
+ }
catch (Exception e)
{
_logger.ErrorException("Error getting channel stream", e);
@@ -754,6 +772,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return new Tuple<MediaSourceInfo, ITunerHost, SemaphoreSlim>(result.Item1, hostInstance, result.Item2);
}
+ catch (FileNotFoundException)
+ {
+ }
catch (Exception e)
{
_logger.ErrorException("Error getting channel stream", e);
@@ -846,14 +867,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- private string GetRecordingPath(TimerInfo timer, ProgramInfo info)
+ private string GetRecordingPath(TimerInfo timer)
{
var recordPath = RecordingPath;
var config = GetConfiguration();
- if (info.IsMovie)
+ if (timer.IsProgramSeries)
{
- var customRecordingPath = config.MovieRecordingPath;
+ var customRecordingPath = config.SeriesRecordingPath;
var allowSubfolder = true;
if (!string.IsNullOrWhiteSpace(customRecordingPath))
{
@@ -863,19 +884,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (allowSubfolder && config.EnableRecordingSubfolders)
{
- recordPath = Path.Combine(recordPath, "Movies");
+ recordPath = Path.Combine(recordPath, "Series");
}
- var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
- if (info.ProductionYear.HasValue)
+ var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
+
+ // Can't use the year here in the folder name because it is the year of the episode, not the series.
+ recordPath = Path.Combine(recordPath, folderName);
+
+ if (timer.SeasonNumber.HasValue)
{
- folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ folderName = string.Format("Season {0}", timer.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
+ recordPath = Path.Combine(recordPath, folderName);
}
- recordPath = Path.Combine(recordPath, folderName);
}
- else if (info.IsSeries)
+ else if (timer.IsMovie)
{
- var customRecordingPath = config.SeriesRecordingPath;
+ var customRecordingPath = config.MovieRecordingPath;
var allowSubfolder = true;
if (!string.IsNullOrWhiteSpace(customRecordingPath))
{
@@ -885,52 +910,37 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (allowSubfolder && config.EnableRecordingSubfolders)
{
- recordPath = Path.Combine(recordPath, "Series");
- }
-
- var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
- var folderNameWithYear = folderName;
- if (info.ProductionYear.HasValue)
- {
- folderNameWithYear += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
- }
-
- if (Directory.Exists(Path.Combine(recordPath, folderName)))
- {
- recordPath = Path.Combine(recordPath, folderName);
- }
- else
- {
- recordPath = Path.Combine(recordPath, folderNameWithYear);
+ recordPath = Path.Combine(recordPath, "Movies");
}
- if (info.SeasonNumber.HasValue)
+ var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
+ if (timer.ProductionYear.HasValue)
{
- folderName = string.Format("Season {0}", info.SeasonNumber.Value.ToString(CultureInfo.InvariantCulture));
- recordPath = Path.Combine(recordPath, folderName);
+ folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
+ recordPath = Path.Combine(recordPath, folderName);
}
- else if (info.IsKids)
+ else if (timer.IsKids)
{
if (config.EnableRecordingSubfolders)
{
recordPath = Path.Combine(recordPath, "Kids");
}
- var folderName = _fileSystem.GetValidFilename(info.Name).Trim();
- if (info.ProductionYear.HasValue)
+ var folderName = _fileSystem.GetValidFilename(timer.Name).Trim();
+ if (timer.ProductionYear.HasValue)
{
- folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
+ folderName += " (" + timer.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")";
}
recordPath = Path.Combine(recordPath, folderName);
}
- else if (info.IsSports)
+ else if (timer.IsSports)
{
if (config.EnableRecordingSubfolders)
{
recordPath = Path.Combine(recordPath, "Sports");
}
- recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
+ recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
else
{
@@ -938,10 +948,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
recordPath = Path.Combine(recordPath, "Other");
}
- recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(info.Name).Trim());
+ recordPath = Path.Combine(recordPath, _fileSystem.GetValidFilename(timer.Name).Trim());
}
- var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
+ var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer)).Trim() + ".ts";
return Path.Combine(recordPath, recordingFileName);
}
@@ -953,29 +963,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
throw new ArgumentNullException("timer");
}
- ProgramInfo info = null;
+ ProgramInfo programInfo = null;
- if (string.IsNullOrWhiteSpace(timer.ProgramId))
+ if (!string.IsNullOrWhiteSpace(timer.ProgramId))
{
- _logger.Info("Timer {0} has null programId", timer.Id);
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
}
- else
- {
- info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId);
- }
-
- if (info == null)
+ if (programInfo == null)
{
_logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId);
- info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
+ programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate);
}
- if (info == null)
+ if (programInfo != null)
{
- throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId));
+ RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer);
}
- var recordPath = GetRecordingPath(timer, info);
+ var recordPath = GetRecordingPath(timer);
var recordingStatus = RecordingStatus.New;
var isResourceOpen = false;
SemaphoreSlim semaphore = null;
@@ -995,6 +1000,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath);
recordPath = EnsureFileUnique(recordPath, timer.Id);
+ _libraryManager.RegisterIgnoredPath(recordPath);
_libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
_fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
activeRecordingInfo.Path = recordPath;
@@ -1046,6 +1052,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
semaphore.Release();
}
+ _libraryManager.UnRegisterIgnoredPath(recordPath);
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, true);
ActiveRecordingInfo removed;
@@ -1057,7 +1064,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
timer.Status = RecordingStatus.Completed;
_timerProvider.Delete(timer);
- OnSuccessfulRecording(info.IsSeries, recordPath);
+ OnSuccessfulRecording(timer.IsProgramSeries, recordPath);
}
else if (DateTime.UtcNow < timer.EndDate)
{
@@ -1114,7 +1121,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if (config.EnableRecordingEncoding)
{
- var regInfo = await _security.GetRegistrationStatus("embytvrecordingconversion").ConfigureAwait(false);
+ var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false);
if (regInfo.IsValid)
{
@@ -1172,7 +1179,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList();
- var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
+ var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
if (registration.IsValid)
{
@@ -1214,7 +1221,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
// Exclude programs that have already ended
- allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow);
+ allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow);
allPrograms = GetProgramsForSeries(seriesTimer, allPrograms);
@@ -1349,20 +1356,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- public Task<MBRegistrationRecord> GetRegistrationInfo(string feature)
- {
- if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase))
- {
- return _security.GetRegistrationStatus("embytvseriesrecordings");
- }
-
- return Task.FromResult(new MBRegistrationRecord
- {
- IsValid = true,
- IsRegistered = true
- });
- }
-
public List<VirtualFolderInfo> GetRecordingFolders()
{
var list = new List<VirtualFolderInfo>();
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index fc3a507d1..560b0d5b4 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -46,34 +46,79 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_httpClient = httpClient;
}
+ private string OutputFormat
+ {
+ get
+ {
+ var format = _liveTvOptions.RecordingEncodingFormat;
+
+ if (string.Equals(format, "mkv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "mkv";
+ }
+
+ return "mp4";
+ }
+ }
+
public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile)
{
- return Path.ChangeExtension(targetFile, ".mp4");
+ return Path.ChangeExtension(targetFile, "." + OutputFormat);
}
public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
- var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts");
-
- try
+ if (mediaSource.Path.IndexOf("m3u8", StringComparison.OrdinalIgnoreCase) != -1)
{
- await RecordInternal(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken)
+ await RecordWithoutTempFile(mediaSource, targetFile, duration, onStarted, cancellationToken)
.ConfigureAwait(false);
+
+ return;
}
- finally
+
+ var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts");
+
+ await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken)
+ .ConfigureAwait(false);
+ }
+
+ private async void DeleteTempFile(string path)
+ {
+ for (var i = 0; i < 10; i++)
{
try
{
- File.Delete(tempfile);
+ File.Delete(path);
+ return;
+ }
+ catch (FileNotFoundException)
+ {
+ return;
+ }
+ catch (DirectoryNotFoundException)
+ {
+ return;
}
catch (Exception ex)
{
_logger.ErrorException("Error deleting recording temp file", ex);
}
+
+ await Task.Delay(1000).ConfigureAwait(false);
}
}
- public async Task RecordInternal(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ private async Task RecordWithoutTempFile(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ {
+ var durationToken = new CancellationTokenSource(duration);
+ cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+
+ await RecordFromFile(mediaSource, mediaSource.Path, targetFile, false, duration, onStarted, cancellationToken).ConfigureAwait(false);
+
+ _logger.Info("Recording completed to file {0}", targetFile);
+ }
+
+ private async Task RecordWithTempFile(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
var httpRequestOptions = new HttpRequestOptions()
{
@@ -110,14 +155,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
}
- var tempFileTask = response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken);
+ var tempFileTask = DirectRecorder.CopyUntilCancelled(response.Content, output, cancellationToken);
// Give the temp file a little time to build up
await Task.Delay(bufferMs, cancellationToken).ConfigureAwait(false);
- var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, duration, onStarted, cancellationToken), cancellationToken);
+ var recordTask = Task.Run(() => RecordFromFile(mediaSource, tempFile, targetFile, true, duration, onStarted, cancellationToken), CancellationToken.None);
- await tempFileTask.ConfigureAwait(false);
+ try
+ {
+ await tempFileTask.ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+
+ }
await recordTask.ConfigureAwait(false);
}
@@ -126,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Recording completed to file {0}", targetFile);
}
- private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ private Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, bool deleteInputFileAfterCompletion, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
_targetPath = targetFile;
_fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
@@ -167,7 +219,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(_json.SerializeToString(mediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
_logFileStream.Write(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length);
- process.Exited += (sender, args) => OnFfMpegProcessExited(process);
+ process.Exited += (sender, args) => OnFfMpegProcessExited(process, inputFile, deleteInputFileAfterCompletion);
process.Start();
@@ -215,15 +267,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
private string GetAudioArgs(MediaSourceInfo mediaSource)
{
- // do not copy aac because many players have difficulty with aac_latm
- var copyAudio = new[] { "mp3" };
var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>();
var inputAudioCodec = mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).FirstOrDefault() ?? string.Empty;
- if (copyAudio.Contains(inputAudioCodec, StringComparer.OrdinalIgnoreCase))
- {
- return "-codec:a:0 copy";
- }
+ // do not copy aac because many players have difficulty with aac_latm
if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings && !string.Equals(inputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase))
{
return "-codec:a:0 copy";
@@ -281,8 +328,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
/// <summary>
/// Processes the exited.
/// </summary>
- /// <param name="process">The process.</param>
- private void OnFfMpegProcessExited(Process process)
+ private void OnFfMpegProcessExited(Process process, string inputFile, bool deleteInputFileAfterCompletion)
{
_hasExited = true;
@@ -308,6 +354,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Error("FFMpeg recording exited with an error for {0}.", _targetPath);
_taskCompletionSource.TrySetException(new Exception(string.Format("Recording for {0} failed", _targetPath)));
}
+
+ if (deleteInputFileAfterCompletion)
+ {
+ DeleteTempFile(inputFile);
+ }
}
private void DisposeLogStream()
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index 37e10d925..236439bc5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -30,19 +30,29 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
timer.Overview = parent.Overview;
timer.SeriesTimerId = series.Id;
+ CopyProgramInfoToTimerInfo(parent, timer);
+
return timer;
}
- public static string GetRecordingName(TimerInfo timer, ProgramInfo info)
+ public static void CopyProgramInfoToTimerInfo(ProgramInfo programInfo, TimerInfo timerInfo)
{
- if (info == null)
- {
- return timer.ProgramId;
- }
+ timerInfo.SeasonNumber = programInfo.SeasonNumber;
+ timerInfo.EpisodeNumber = programInfo.EpisodeNumber;
+ timerInfo.IsMovie = programInfo.IsMovie;
+ timerInfo.IsKids = programInfo.IsKids;
+ timerInfo.IsSports = programInfo.IsSports;
+ timerInfo.ProductionYear = programInfo.ProductionYear;
+ timerInfo.EpisodeTitle = programInfo.EpisodeTitle;
+ timerInfo.OriginalAirDate = programInfo.OriginalAirDate;
+ timerInfo.IsProgramSeries = programInfo.IsSeries;
+ }
+ public static string GetRecordingName(TimerInfo info)
+ {
var name = info.Name;
- if (info.IsSeries)
+ if (info.IsProgramSeries)
{
var addHyphen = true;
@@ -55,6 +65,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
name += " " + info.OriginalAirDate.Value.ToString("yyyy-MM-dd");
}
+ else
+ {
+ name += " " + DateTime.Now.ToString("yyyy-MM-dd");
+ }
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
{
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
index 423358906..a7e34a373 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/TimerManager.cs
@@ -9,7 +9,6 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using CommonIO;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
@@ -17,15 +16,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public class TimerManager : ItemDataProvider<TimerInfo>
{
private readonly ConcurrentDictionary<string, Timer> _timers = new ConcurrentDictionary<string, Timer>(StringComparer.OrdinalIgnoreCase);
- private readonly IPowerManagement _powerManagement;
private readonly ILogger _logger;
public event EventHandler<GenericEventArgs<TimerInfo>> TimerFired;
- public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, IPowerManagement powerManagement, ILogger logger1)
+ public TimerManager(IFileSystem fileSystem, IJsonSerializer jsonSerializer, ILogger logger, string dataPath, ILogger logger1)
: base(fileSystem, jsonSerializer, logger, dataPath, (r1, r2) => string.Equals(r1.Id, r2.Id, StringComparison.OrdinalIgnoreCase))
{
- _powerManagement = powerManagement;
_logger = logger1;
}
@@ -64,7 +61,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow;
timer.Change(timespan, TimeSpan.Zero);
- ScheduleWake(item);
}
else
{
@@ -101,7 +97,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
base.Add(item);
AddTimer(item);
- ScheduleWake(item);
}
private void AddTimer(TimerInfo item)
@@ -124,25 +119,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
StartTimer(item, timerLength);
}
- private void ScheduleWake(TimerInfo info)
- {
- var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5);
-
- try
- {
- _powerManagement.ScheduleWake(startDate);
- _logger.Info("Scheduled system wake timer at {0} (UTC)", startDate);
- }
- catch (NotImplementedException)
- {
-
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error scheduling wake timer", ex);
- }
- }
-
public void StartTimer(TimerInfo item, TimeSpan dueTime)
{
StopTimer(item);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index e37109c14..d863c4587 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -166,7 +166,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
var imageIndex = images.FindIndex(i => i.programID == schedule.programID.Substring(0, 10));
if (imageIndex > -1)
{
- programDict[schedule.programID].images = GetProgramLogo(ApiUrl, images[imageIndex]);
+ var programEntry = programDict[schedule.programID];
+
+ var data = images[imageIndex].data ?? new List<ScheduleDirect.ImageData>();
+ data = data.OrderByDescending(GetSizeOrder).ToList();
+
+ programEntry.primaryImage = GetProgramImage(ApiUrl, data, "Logo", true);
+ //programEntry.thumbImage = GetProgramImage(ApiUrl, data, "Iconic", false);
+ //programEntry.bannerImage = GetProgramImage(ApiUrl, data, "Banner", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-L1", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-LO", false) ??
+ // GetProgramImage(ApiUrl, data, "Banner-LOT", false);
+
+ if (!string.IsNullOrWhiteSpace(programEntry.thumbImage))
+ {
+ var b = true;
+ }
+
+ if (!string.IsNullOrWhiteSpace(programEntry.bannerImage))
+ {
+ var b = true;
+ }
}
}
@@ -179,6 +199,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return programsInfo;
}
+ private int GetSizeOrder(ScheduleDirect.ImageData image)
+ {
+ if (!string.IsNullOrWhiteSpace(image.size))
+ {
+ int value;
+ if (int.TryParse(image.size, out value))
+ {
+ return value;
+ }
+ }
+
+ return 0;
+ }
+
private readonly object _channelCacheLock = new object();
private ScheduleDirect.Station GetStation(string listingsId, string channelNumber, string channelName)
{
@@ -194,14 +228,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return station;
}
- if (string.IsNullOrWhiteSpace(channelName))
+ if (!string.IsNullOrWhiteSpace(channelName))
{
- return null;
- }
+ channelName = NormalizeName(channelName);
- channelName = NormalizeName(channelName);
+ var result = channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
- return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase));
+ if (result != null)
+ {
+ return result;
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(channelNumber))
+ {
+ return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase));
+ }
}
return null;
@@ -307,9 +349,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
channelNumber = channelNumber.TrimStart('0');
_logger.Debug("Found channel: " + channelNumber + " in Schedules Direct");
- var schChannel = root.stations.FirstOrDefault(item => item.stationID == map.stationID);
-
- AddToChannelPairCache(listingsId, channelNumber, schChannel);
+ if (root.stations != null)
+ {
+ var schChannel = root.stations.FirstOrDefault(item => string.Equals(item.stationID, map.stationID, StringComparison.OrdinalIgnoreCase));
+ if (schChannel != null)
+ {
+ AddToChannelPairCache(listingsId, channelNumber, schChannel);
+ }
+ }
}
_logger.Info("Added " + GetChannelPairCacheCount(listingsId) + " channels to the dictionary");
@@ -348,7 +395,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
if (programInfo.audioProperties != null)
{
- if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
+ if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase)))
+ {
+ audioType = ProgramAudio.Atmos;
+ }
+ else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase)))
{
audioType = ProgramAudio.DolbyDigital;
}
@@ -372,13 +423,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
episodeTitle = details.episodeTitle150;
}
- string imageUrl = null;
-
- if (details.hasImageArtwork)
- {
- imageUrl = details.images;
- }
-
var showType = details.showType ?? string.Empty;
var info = new ProgramInfo
@@ -394,7 +438,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
Audio = audioType,
IsRepeat = repeat,
IsSeries = showType.IndexOf("series", StringComparison.OrdinalIgnoreCase) != -1,
- ImageUrl = imageUrl,
+ ImageUrl = details.primaryImage,
IsKids = string.Equals(details.audience, "children", StringComparison.OrdinalIgnoreCase),
IsSports = showType.IndexOf("sports", StringComparison.OrdinalIgnoreCase) != -1,
IsMovie = showType.IndexOf("movie", StringComparison.OrdinalIgnoreCase) != -1 || showType.IndexOf("film", StringComparison.OrdinalIgnoreCase) != -1,
@@ -405,6 +449,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
if (programInfo.videoProperties != null)
{
info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase);
+ info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase);
}
if (details.contentRating != null && details.contentRating.Count > 0)
@@ -472,36 +517,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
return date;
}
- private string GetProgramLogo(string apiUrl, ScheduleDirect.ShowImages images)
+ private string GetProgramImage(string apiUrl, List<ScheduleDirect.ImageData> images, string category, bool returnDefaultImage)
{
string url = null;
- if (images.data != null)
+
+ var logoIndex = images.FindIndex(i => string.Equals(i.category, category, StringComparison.OrdinalIgnoreCase));
+ if (logoIndex == -1)
{
- var smallImages = images.data.Where(i => i.size == "Sm").ToList();
- if (smallImages.Any())
+ if (!returnDefaultImage)
{
- images.data = smallImages;
+ return null;
}
- var logoIndex = images.data.FindIndex(i => i.category == "Logo");
- if (logoIndex == -1)
+ logoIndex = 0;
+ }
+ var uri = images[logoIndex].uri;
+
+ if (!string.IsNullOrWhiteSpace(uri))
+ {
+ if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
{
- logoIndex = 0;
+ url = uri;
}
- var uri = images.data[logoIndex].uri;
-
- if (!string.IsNullOrWhiteSpace(uri))
+ else
{
- if (uri.IndexOf("http", StringComparison.OrdinalIgnoreCase) != -1)
- {
- url = uri;
- }
- else
- {
- url = apiUrl + "/image/" + uri;
- }
+ url = apiUrl + "/image/" + uri;
}
- //_logger.Debug("URL for image is : " + url);
}
+ //_logger.Debug("URL for image is : " + url);
return url;
}
@@ -785,9 +827,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
get { return "Schedules Direct"; }
}
+ public static string TypeName = "SchedulesDirect";
public string Type
{
- get { return "SchedulesDirect"; }
+ get { return TypeName; }
}
private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken)
@@ -1190,7 +1233,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
public List<Crew> crew { get; set; }
public string showType { get; set; }
public bool hasImageArtwork { get; set; }
- public string images { get; set; }
+ public string primaryImage { get; set; }
+ public string thumbImage { get; set; }
+ public string bannerImage { get; set; }
public string imageID { get; set; }
public string md5 { get; set; }
public List<string> contentAdvisory { get; set; }
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 88017aa59..b3ced55a5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -31,7 +31,11 @@ using CommonIO;
using IniParser;
using IniParser.Model;
using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Security;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.Events;
+using MediaBrowser.Server.Implementations.LiveTv.Listings;
namespace MediaBrowser.Server.Implementations.LiveTv
{
@@ -49,6 +53,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
private readonly ITaskManager _taskManager;
private readonly IJsonSerializer _jsonSerializer;
private readonly IProviderManager _providerManager;
+ private readonly ISecurityManager _security;
private readonly IDtoService _dtoService;
private readonly ILocalizationManager _localization;
@@ -71,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated;
public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated;
- public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem)
+ public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem, ISecurityManager security)
{
_config = config;
_logger = logger;
@@ -83,6 +88,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
_jsonSerializer = jsonSerializer;
_providerManager = providerManager;
_fileSystem = fileSystem;
+ _security = security;
_dtoService = dtoService;
_userDataManager = userDataManager;
@@ -1423,6 +1429,49 @@ namespace MediaBrowser.Server.Implementations.LiveTv
return new QueryResult<BaseItem>();
}
+ var includeItemTypes = new List<string>();
+ var excludeItemTypes = new List<string>();
+ var genres = new List<string>();
+
+ if (query.IsMovie.HasValue)
+ {
+ if (query.IsMovie.Value)
+ {
+ includeItemTypes.Add(typeof(Movie).Name);
+ }
+ else
+ {
+ excludeItemTypes.Add(typeof(Movie).Name);
+ }
+ }
+ if (query.IsSeries.HasValue)
+ {
+ if (query.IsSeries.Value)
+ {
+ includeItemTypes.Add(typeof(Episode).Name);
+ }
+ else
+ {
+ excludeItemTypes.Add(typeof(Episode).Name);
+ }
+ }
+ if (query.IsSports.HasValue)
+ {
+ if (query.IsSports.Value)
+ {
+ genres.Add("Sports");
+ }
+ }
+ if (query.IsKids.HasValue)
+ {
+ if (query.IsKids.Value)
+ {
+ genres.Add("Kids");
+ genres.Add("Children");
+ genres.Add("Family");
+ }
+ }
+
return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
{
MediaTypes = new[] { MediaType.Video },
@@ -1430,13 +1479,75 @@ namespace MediaBrowser.Server.Implementations.LiveTv
AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
IsFolder = false,
ExcludeLocationTypes = new[] { LocationType.Virtual },
- Limit = Math.Min(200, query.Limit ?? int.MaxValue),
+ Limit = query.Limit,
SortBy = new[] { ItemSortBy.DateCreated },
SortOrder = SortOrder.Descending,
- EnableTotalRecordCount = query.EnableTotalRecordCount
+ EnableTotalRecordCount = query.EnableTotalRecordCount,
+ IncludeItemTypes = includeItemTypes.ToArray(),
+ ExcludeItemTypes = excludeItemTypes.ToArray(),
+ Genres = genres.ToArray()
});
}
+ public async Task<QueryResult<BaseItemDto>> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken)
+ {
+ var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
+ if (user != null && !IsLiveTvEnabled(user))
+ {
+ return new QueryResult<BaseItemDto>();
+ }
+
+ if (_services.Count > 1)
+ {
+ return new QueryResult<BaseItemDto>();
+ }
+
+ if (user == null || (query.IsInProgress ?? false))
+ {
+ return new QueryResult<BaseItemDto>();
+ }
+
+ var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders()
+ .SelectMany(i => i.Locations)
+ .Distinct(StringComparer.OrdinalIgnoreCase)
+ .Select(i => _libraryManager.FindByPath(i, true))
+ .Where(i => i != null)
+ .Where(i => i.IsVisibleStandalone(user))
+ .ToList();
+
+ if (folders.Count == 0)
+ {
+ return new QueryResult<BaseItemDto>();
+ }
+
+ var includeItemTypes = new List<string>();
+ var excludeItemTypes = new List<string>();
+
+ includeItemTypes.Add(typeof(Series).Name);
+
+ var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
+ {
+ Recursive = true,
+ AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
+ Limit = query.Limit,
+ SortBy = new[] { ItemSortBy.DateCreated },
+ SortOrder = SortOrder.Descending,
+ EnableTotalRecordCount = query.EnableTotalRecordCount,
+ IncludeItemTypes = includeItemTypes.ToArray(),
+ ExcludeItemTypes = excludeItemTypes.ToArray()
+ });
+
+ RemoveFields(options);
+
+ var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray();
+
+ return new QueryResult<BaseItemDto>
+ {
+ Items = returnArray,
+ TotalRecordCount = internalResult.TotalRecordCount
+ };
+ }
+
public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
{
var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
@@ -1492,6 +1603,30 @@ namespace MediaBrowser.Server.Implementations.LiveTv
recordings = recordings.Where(i => i.Status == val);
}
+ if (query.IsMovie.HasValue)
+ {
+ var val = query.IsMovie.Value;
+ recordings = recordings.Where(i => i.IsMovie == val);
+ }
+
+ if (query.IsSeries.HasValue)
+ {
+ var val = query.IsSeries.Value;
+ recordings = recordings.Where(i => i.IsSeries == val);
+ }
+
+ if (query.IsKids.HasValue)
+ {
+ var val = query.IsKids.Value;
+ recordings = recordings.Where(i => i.IsKids == val);
+ }
+
+ if (query.IsSports.HasValue)
+ {
+ var val = query.IsSports.Value;
+ recordings = recordings.Where(i => i.IsSports == val);
+ }
+
if (!string.IsNullOrEmpty(query.SeriesTimerId))
{
var guid = new Guid(query.SeriesTimerId);
@@ -1950,16 +2085,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv
dto.Number = channel.Number;
dto.ChannelNumber = channel.Number;
dto.ChannelType = channel.ChannelType;
- dto.ServiceName = GetService(channel).Name;
+ dto.ServiceName = channel.ServiceName;
if (options.Fields.Contains(ItemFields.MediaSources))
{
dto.MediaSources = channel.GetMediaSources(true).ToList();
}
- var channelIdString = channel.Id.ToString("N");
if (options.AddCurrentProgram)
{
+ var channelIdString = channel.Id.ToString("N");
var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString));
if (currentProgram != null)
@@ -2091,6 +2226,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken)
{
+ var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false);
+
+ if (!registration.IsValid)
+ {
+ _logger.Info("Creating series recordings requires an active Emby Premiere subscription.");
+ return;
+ }
+
var service = GetService(timer.ServiceName);
var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false);
@@ -2653,33 +2796,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
- public Task<MBRegistrationRecord> GetRegistrationInfo(string channelId, string programId, string feature)
+ public Task<MBRegistrationRecord> GetRegistrationInfo(string feature)
{
- ILiveTvService service;
-
- if (string.IsNullOrWhiteSpace(programId))
- {
- var channel = GetInternalChannel(channelId);
- service = GetService(channel);
- }
- else
+ if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase))
{
- var program = GetInternalProgram(programId);
- service = GetService(program);
+ feature = "embytvseriesrecordings";
}
- var hasRegistration = service as IHasRegistrationInfo;
-
- if (hasRegistration != null)
+ if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase))
{
- return hasRegistration.GetRegistrationInfo(feature);
+ var config = GetConfiguration();
+ if (config.TunerHosts.Count(i => i.IsEnabled) > 0 &&
+ config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0)
+ {
+ return Task.FromResult(new MBRegistrationRecord
+ {
+ IsRegistered = true,
+ IsValid = true
+ });
+ }
}
- return Task.FromResult(new MBRegistrationRecord
- {
- IsValid = true,
- IsRegistered = true
- });
+ return _security.GetRegistrationStatus(feature);
}
public List<NameValuePair> GetSatIniMappings()
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 9bb5b4fd7..7aa9eb1cf 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -6,6 +6,7 @@ using MediaBrowser.Model.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -195,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
else if (streamId.StartsWith(host.Id, StringComparison.OrdinalIgnoreCase))
{
- hostsWithChannel = new List<TunerHostInfo> { host };
+ hostsWithChannel = new List<TunerHostInfo> {host};
streamId = streamId.Substring(host.Id.Length);
break;
}
@@ -222,7 +223,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
}
- var stream = await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
+ var stream =
+ await GetChannelStream(host, channelId, streamId, cancellationToken).ConfigureAwait(false);
if (EnableMediaProbing)
{
@@ -239,6 +241,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
}
}
+ else
+ {
+ throw new FileNotFoundException();
+ }
throw new LiveTvConflictException();
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
index 9ba1c60cc..ef37e3b35 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
@@ -10,6 +10,7 @@ using System;
using System.Linq;
using System.Threading;
using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Events;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
@@ -39,13 +40,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
string server = null;
- if (e.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
+ var info = e.Argument;
+
+ if (info.Headers.TryGetValue("SERVER", out server) && server.IndexOf("HDHomeRun", StringComparison.OrdinalIgnoreCase) != -1)
{
string location;
- if (e.Headers.TryGetValue("Location", out location))
+ if (info.Headers.TryGetValue("Location", out location))
{
//_logger.Debug("HdHomerun found at {0}", location);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
index ffe95c862..8095a6989 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
@@ -70,7 +71,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith("#", StringComparison.OrdinalIgnoreCase))
{
- var channel = GetChannelnfo(extInf, tunerHostId);
+ var channel = GetChannelnfo(extInf, tunerHostId, line);
channel.Id = channelIdPrefix + urlHash + line.GetMD5().ToString("N");
channel.Path = line;
channels.Add(channel);
@@ -79,7 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
}
return channels;
}
- private M3UChannel GetChannelnfo(string extInf, string tunerHostId)
+ private M3UChannel GetChannelnfo(string extInf, string tunerHostId, string mediaUrl)
{
var titleIndex = extInf.LastIndexOf(',');
var channel = new M3UChannel();
@@ -87,8 +88,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
channel.Number = extInf.Trim().Split(' ')[0] ?? "0";
channel.Name = extInf.Substring(titleIndex + 1);
-
- if(channel.Number == "-1") { channel.Number = "0"; }
//Check for channel number with the format from SatIp
int number;
@@ -101,6 +100,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
channel.Name = channel.Name.Substring(numberIndex + 1);
}
}
+
+ if (string.Equals(channel.Number, "-1", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(mediaUrl))
+ {
+ channel.Number = Path.GetFileNameWithoutExtension(mediaUrl.Split('/').Last());
+ }
+
+ if (string.Equals(channel.Number, "-1", StringComparison.OrdinalIgnoreCase))
+ {
+ channel.Number = "0";
+ }
+
channel.ImageUrl = FindProperty("tvg-logo", extInf, null);
channel.Number = FindProperty("tvg-id", extInf, channel.Number);
channel.Number = FindProperty("channel-id", extInf, channel.Number);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs
new file mode 100644
index 000000000..dddd77179
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs
@@ -0,0 +1,79 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ public class ReportBlock
+ {
+ /// <summary>
+ /// Get the length of the block.
+ /// </summary>
+ public int BlockLength { get { return (24); } }
+ /// <summary>
+ /// Get the synchronization source.
+ /// </summary>
+ public string SynchronizationSource { get; private set; }
+ /// <summary>
+ /// Get the fraction lost.
+ /// </summary>
+ public int FractionLost { get; private set; }
+ /// <summary>
+ /// Get the cumulative packets lost.
+ /// </summary>
+ public int CumulativePacketsLost { get; private set; }
+ /// <summary>
+ /// Get the highest number received.
+ /// </summary>
+ public int HighestNumberReceived { get; private set; }
+ /// <summary>
+ /// Get the inter arrival jitter.
+ /// </summary>
+ public int InterArrivalJitter { get; private set; }
+ /// <summary>
+ /// Get the timestamp of the last report.
+ /// </summary>
+ public int LastReportTimeStamp { get; private set; }
+ /// <summary>
+ /// Get the delay since the last report.
+ /// </summary>
+ public int DelaySinceLastReport { get; private set; }
+
+ /// <summary>
+ /// Initialize a new instance of the ReportBlock class.
+ /// </summary>
+ public ReportBlock() { }
+
+ /// <summary>
+ /// Unpack the data in a packet.
+ /// </summary>
+ /// <param name="buffer">The buffer containing the packet.</param>
+ /// <param name="offset">The offset to the first byte of the packet within the buffer.</param>
+ /// <returns>An ErrorSpec instance if an error occurs; null otherwise.</returns>
+ public void Process(byte[] buffer, int offset)
+ {
+ SynchronizationSource = Utils.ConvertBytesToString(buffer, offset, 4);
+ FractionLost = buffer[offset + 4];
+ CumulativePacketsLost = Utils.Convert3BytesToInt(buffer, offset + 5);
+ HighestNumberReceived = Utils.Convert4BytesToInt(buffer, offset + 8);
+ InterArrivalJitter = Utils.Convert4BytesToInt(buffer, offset + 12);
+ LastReportTimeStamp = Utils.Convert4BytesToInt(buffer, offset + 16);
+ DelaySinceLastReport = Utils.Convert4BytesToInt(buffer, offset + 20);
+
+
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs
new file mode 100644
index 000000000..990b6dd94
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs
@@ -0,0 +1,68 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ class RtcpAppPacket : RtcpPacket
+ {
+ /// <summary>
+ /// Get the synchronization source.
+ /// </summary>
+ public int SynchronizationSource { get; private set; }
+ /// <summary>
+ /// Get the name.
+ /// </summary>
+ public string Name { get; private set; }
+ /// <summary>
+ /// Get the identity.
+ /// </summary>
+ public int Identity { get; private set; }
+ /// <summary>
+ /// Get the variable data portion.
+ /// </summary>
+ public string Data { get; private set; }
+
+ public override void Parse(byte[] buffer, int offset)
+ {
+ base.Parse(buffer, offset);
+ SynchronizationSource = Utils.Convert4BytesToInt(buffer, offset + 4);
+ Name = Utils.ConvertBytesToString(buffer, offset + 8, 4);
+ Identity = Utils.Convert2BytesToInt(buffer, offset + 12);
+
+ int dataLength = Utils.Convert2BytesToInt(buffer, offset + 14);
+ if (dataLength != 0)
+ Data = Utils.ConvertBytesToString(buffer, offset + 16, dataLength);
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("Application Specific.\n");
+ sb.AppendFormat("Version : {0} .\n", Version);
+ sb.AppendFormat("Padding : {0} .\n", Padding);
+ sb.AppendFormat("Report Count : {0} .\n", ReportCount);
+ sb.AppendFormat("PacketType: {0} .\n", Type);
+ sb.AppendFormat("Length : {0} .\n", Length);
+ sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource);
+ sb.AppendFormat("Name : {0} .\n", Name);
+ sb.AppendFormat("Identity : {0} .\n", Identity);
+ sb.AppendFormat("Data : {0} .\n", Data);
+ sb.AppendFormat(".\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs
new file mode 100644
index 000000000..c79ea31a8
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs
@@ -0,0 +1,59 @@
+using System.Collections.ObjectModel;
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ public class RtcpByePacket :RtcpPacket
+ {
+ public Collection<string> SynchronizationSources { get; private set; }
+ public string ReasonForLeaving { get; private set; }
+ public override void Parse(byte[] buffer, int offset)
+ {
+ base.Parse(buffer, offset);
+ SynchronizationSources = new Collection<string>();
+ int index = 4;
+
+ while (SynchronizationSources.Count < ReportCount)
+ {
+ SynchronizationSources.Add(Utils.ConvertBytesToString(buffer, offset + index, 4));
+ index += 4;
+ }
+
+ if (index < Length)
+ {
+ int reasonLength = buffer[offset + index];
+ ReasonForLeaving = Utils.ConvertBytesToString(buffer, offset + index + 1, reasonLength);
+ }
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("ByeBye .\n");
+ sb.AppendFormat("Version : {0} .\n", Version);
+ sb.AppendFormat("Padding : {0} .\n", Padding);
+ sb.AppendFormat("Report Count : {0} .\n", ReportCount);
+ sb.AppendFormat("PacketType: {0} .\n", Type);
+ sb.AppendFormat("Length : {0} .\n", Length);
+ sb.AppendFormat("SynchronizationSources : {0} .\n", SynchronizationSources);
+ sb.AppendFormat("ReasonForLeaving : {0} .\n", ReasonForLeaving);
+ sb.AppendFormat(".\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs
new file mode 100644
index 000000000..2c54f0665
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs
@@ -0,0 +1,203 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ public class RtcpListener
+ {
+ private readonly ILogger _logger;
+ private Thread _rtcpListenerThread;
+ private AutoResetEvent _rtcpListenerThreadStopEvent = null;
+ private UdpClient _udpClient;
+ private IPEndPoint _multicastEndPoint;
+ private IPEndPoint _serverEndPoint;
+ private TransmissionMode _transmissionMode;
+
+ public RtcpListener(String address, int port, TransmissionMode mode,ILogger logger)
+ {
+ _logger = logger;
+ _transmissionMode = mode;
+ switch (mode)
+ {
+ case TransmissionMode.Unicast:
+ _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(address), port));
+ _serverEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ break;
+ case TransmissionMode.Multicast:
+ _multicastEndPoint = new IPEndPoint(IPAddress.Parse(address), port);
+ _serverEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ _udpClient = new UdpClient();
+ _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
+ _udpClient.ExclusiveAddressUse = false;
+ _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port));
+ _udpClient.JoinMulticastGroup(_multicastEndPoint.Address);
+ break;
+ }
+ //StartRtcpListenerThread();
+ }
+
+ public void StartRtcpListenerThread()
+ {
+ // Kill the existing thread if it is in "zombie" state.
+ if (_rtcpListenerThread != null && !_rtcpListenerThread.IsAlive)
+ {
+ StopRtcpListenerThread();
+ }
+
+ if (_rtcpListenerThread == null)
+ {
+ _logger.Info("SAT>IP : starting new RTCP listener thread");
+ _rtcpListenerThreadStopEvent = new AutoResetEvent(false);
+ _rtcpListenerThread = new Thread(new ThreadStart(RtcpListenerThread));
+ _rtcpListenerThread.Name = string.Format("SAT>IP tuner RTCP listener");
+ _rtcpListenerThread.IsBackground = true;
+ _rtcpListenerThread.Priority = ThreadPriority.Lowest;
+ _rtcpListenerThread.Start();
+ }
+ }
+
+ public void StopRtcpListenerThread()
+ {
+ if (_rtcpListenerThread != null)
+ {
+ if (!_rtcpListenerThread.IsAlive)
+ {
+ _logger.Info("SAT>IP : aborting old RTCP listener thread");
+ _rtcpListenerThread.Abort();
+ }
+ else
+ {
+ _rtcpListenerThreadStopEvent.Set();
+ if (!_rtcpListenerThread.Join(400 * 2))
+ {
+ _logger.Info("SAT>IP : failed to join RTCP listener thread, aborting thread");
+ _rtcpListenerThread.Abort();
+ }
+ }
+ _rtcpListenerThread = null;
+ if (_rtcpListenerThreadStopEvent != null)
+ {
+ _rtcpListenerThreadStopEvent.Close();
+ _rtcpListenerThreadStopEvent = null;
+ }
+ }
+ }
+
+ private void RtcpListenerThread()
+ {
+ try
+ {
+ bool receivedGoodBye = false;
+ try
+ {
+ _udpClient.Client.ReceiveTimeout = 400;
+ IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ while (!receivedGoodBye && !_rtcpListenerThreadStopEvent.WaitOne(1))
+ {
+ byte[] packets = _udpClient.Receive(ref serverEndPoint);
+ if (packets == null)
+ {
+ continue;
+ }
+
+ int offset = 0;
+ while (offset < packets.Length)
+ {
+ switch (packets[offset + 1])
+ {
+ case 200: //sr
+ var sr = new RtcpSenderReportPacket();
+ sr.Parse(packets, offset);
+ offset += sr.Length;
+ break;
+ case 201: //rr
+ var rr = new RtcpReceiverReportPacket();
+ rr.Parse(packets, offset);
+ offset += rr.Length;
+ break;
+ case 202: //sd
+ var sd = new RtcpSourceDescriptionPacket();
+ sd.Parse(packets, offset);
+ offset += sd.Length;
+ break;
+ case 203: // bye
+ var bye = new RtcpByePacket();
+ bye.Parse(packets, offset);
+ receivedGoodBye = true;
+ OnPacketReceived(new RtcpPacketReceivedArgs(bye));
+ offset += bye.Length;
+ break;
+ case 204: // app
+ var app = new RtcpAppPacket();
+ app.Parse(packets, offset);
+ OnPacketReceived(new RtcpPacketReceivedArgs(app));
+ offset += app.Length;
+ break;
+ }
+ }
+
+ }
+ }
+ finally
+ {
+ switch (_transmissionMode)
+ {
+ case TransmissionMode.Multicast:
+ _udpClient.DropMulticastGroup(_multicastEndPoint.Address);
+ _udpClient.Close();
+ break;
+ case TransmissionMode.Unicast:
+ _udpClient.Close();
+ break;
+ }
+ }
+ }
+ catch (ThreadAbortException)
+ {
+ }
+ catch (Exception ex)
+ {
+ _logger.Info(string.Format("SAT>IP : RTCP listener thread exception"), ex);
+ return;
+ }
+ _logger.Info("SAT>IP : RTCP listener thread stopping");
+ }
+ public delegate void PacketReceivedHandler(object sender, RtcpPacketReceivedArgs e);
+ public event PacketReceivedHandler PacketReceived;
+ public class RtcpPacketReceivedArgs : EventArgs
+ {
+ public Object Packet { get; private set; }
+
+ public RtcpPacketReceivedArgs(Object packet)
+ {
+ Packet = packet;
+ }
+ }
+ protected void OnPacketReceived(RtcpPacketReceivedArgs args)
+ {
+ if (PacketReceived != null)
+ {
+ PacketReceived(this, args);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs
new file mode 100644
index 000000000..0a949eb7e
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs
@@ -0,0 +1,37 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ public abstract class RtcpPacket
+ {
+ public int Version { get; private set; }
+ public bool Padding { get; private set; }
+ public int ReportCount { get; private set; }
+ public int Type { get; private set; }
+ public int Length { get; private set; }
+
+ public virtual void Parse(byte[] buffer, int offset)
+ {
+ Version = buffer[offset] >> 6;
+ Padding = (buffer[offset] & 0x20) != 0;
+ ReportCount = buffer[offset] & 0x1f;
+ Type = buffer[offset + 1];
+ Length = (Utils.Convert2BytesToInt(buffer, offset + 2) * 4) + 4;
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs
new file mode 100644
index 000000000..abb863652
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs
@@ -0,0 +1,68 @@
+using System.Collections.ObjectModel;
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ public class RtcpReceiverReportPacket :RtcpPacket
+ {
+ public string SynchronizationSource { get; private set; }
+ public Collection<ReportBlock> ReportBlocks { get; private set; }
+ public byte[] ProfileExtension { get; private set; }
+ public override void Parse(byte[] buffer, int offset)
+ {
+ base.Parse(buffer, offset);
+ SynchronizationSource = Utils.ConvertBytesToString(buffer, offset + 4, 4);
+
+ ReportBlocks = new Collection<ReportBlock>();
+ int index = 8;
+
+ while (ReportBlocks.Count < ReportCount)
+ {
+ ReportBlock reportBlock = new ReportBlock();
+ reportBlock.Process(buffer, offset + index);
+ ReportBlocks.Add(reportBlock);
+ index += reportBlock.BlockLength;
+ }
+
+ if (index < Length)
+ {
+ ProfileExtension = new byte[Length - index];
+
+ for (int extensionIndex = 0; index < Length; index++)
+ {
+ ProfileExtension[extensionIndex] = buffer[offset + index];
+ extensionIndex++;
+ }
+ }
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("Receiver Report.\n");
+ sb.AppendFormat("Version : {0} .\n", Version);
+ sb.AppendFormat("Padding : {0} .\n", Padding);
+ sb.AppendFormat("Report Count : {0} .\n", ReportCount);
+ sb.AppendFormat("PacketType: {0} .\n", Type);
+ sb.AppendFormat("Length : {0} .\n", Length);
+ sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource);
+ sb.AppendFormat(".\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs
new file mode 100644
index 000000000..dda5d6a03
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs
@@ -0,0 +1,105 @@
+using System.Collections.ObjectModel;
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ public class RtcpSenderReportPacket : RtcpPacket
+ {
+ #region Properties
+ /// <summary>
+ /// Get the synchronization source.
+ /// </summary>
+ public int SynchronizationSource { get; private set; }
+ /// <summary>
+ /// Get the NPT timestamp.
+ /// </summary>
+ public long NPTTimeStamp { get; private set; }
+ /// <summary>
+ /// Get the RTP timestamp.
+ /// </summary>
+ public int RTPTimeStamp { get; private set; }
+ /// <summary>
+ /// Get the packet count.
+ /// </summary>
+ public int SenderPacketCount { get; private set; }
+ /// <summary>
+ /// Get the octet count.
+ /// </summary>
+ public int SenderOctetCount { get; private set; }
+ /// <summary>
+ /// Get the list of report blocks.
+ /// </summary>
+ public Collection<ReportBlock> ReportBlocks { get; private set; }
+ /// <summary>
+ /// Get the profile extension data.
+ /// </summary>
+ public byte[] ProfileExtension { get; private set; }
+ #endregion
+
+ public override void Parse(byte[] buffer, int offset)
+ {
+ base.Parse(buffer, offset);
+ SynchronizationSource = Utils.Convert4BytesToInt(buffer, offset + 4);
+ NPTTimeStamp = Utils.Convert8BytesToLong(buffer, offset + 8);
+ RTPTimeStamp = Utils.Convert4BytesToInt(buffer, offset + 16);
+ SenderPacketCount = Utils.Convert4BytesToInt(buffer, offset + 20);
+ SenderOctetCount = Utils.Convert4BytesToInt(buffer, offset + 24);
+
+ ReportBlocks = new Collection<ReportBlock>();
+ int index = 28;
+
+ while (ReportBlocks.Count < ReportCount)
+ {
+ ReportBlock reportBlock = new ReportBlock();
+ reportBlock.Process(buffer, offset + index);
+ ReportBlocks.Add(reportBlock);
+ index += reportBlock.BlockLength;
+ }
+
+ if (index < Length)
+ {
+ ProfileExtension = new byte[Length - index];
+
+ for (int extensionIndex = 0; index < Length; index++)
+ {
+ ProfileExtension[extensionIndex] = buffer[offset + index];
+ extensionIndex++;
+ }
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("Sender Report.\n");
+ sb.AppendFormat("Version : {0} .\n", Version);
+ sb.AppendFormat("Padding : {0} .\n", Padding);
+ sb.AppendFormat("Report Count : {0} .\n", ReportCount);
+ sb.AppendFormat("PacketType: {0} .\n", Type);
+ sb.AppendFormat("Length : {0} .\n", Length);
+ sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource);
+ sb.AppendFormat("NTP Timestamp : {0} .\n", Utils.NptTimestampToDateTime(NPTTimeStamp));
+ sb.AppendFormat("RTP Timestamp : {0} .\n", RTPTimeStamp);
+ sb.AppendFormat("Sender PacketCount : {0} .\n", SenderPacketCount);
+ sb.AppendFormat("Sender Octet Count : {0} .\n", SenderOctetCount);
+ sb.AppendFormat(".\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs
new file mode 100644
index 000000000..0a95a4413
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs
@@ -0,0 +1,57 @@
+using System.Collections.ObjectModel;
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ class RtcpSourceDescriptionPacket :RtcpPacket
+ { /// <summary>
+ /// Get the list of source descriptions.
+ /// </summary>
+ public Collection<SourceDescriptionBlock> Descriptions;
+ public override void Parse(byte[] buffer, int offset)
+ {
+ base.Parse(buffer, offset);
+ Descriptions = new Collection<SourceDescriptionBlock>();
+
+ int index = 4;
+
+ while (Descriptions.Count < ReportCount)
+ {
+ SourceDescriptionBlock descriptionBlock = new SourceDescriptionBlock();
+ descriptionBlock.Process(buffer, offset + index);
+ Descriptions.Add(descriptionBlock);
+ index += descriptionBlock.BlockLength;
+ }
+ }
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("Source Description.\n");
+ sb.AppendFormat("Version : {0} .\n", Version);
+ sb.AppendFormat("Padding : {0} .\n", Padding);
+ sb.AppendFormat("Report Count : {0} .\n", ReportCount);
+ sb.AppendFormat("PacketType: {0} .\n", Type);
+ sb.AppendFormat("Length : {0} .\n", Length);
+ sb.AppendFormat("Descriptions : {0} .\n", Descriptions);
+
+ sb.AppendFormat(".\n");
+ return sb.ToString();
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs
new file mode 100644
index 000000000..bf56087cd
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs
@@ -0,0 +1,65 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System.Collections.ObjectModel;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ class SourceDescriptionBlock
+ {
+ /// <summary>
+ /// Get the length of the block.
+ /// </summary>
+ public int BlockLength { get { return (blockLength + (blockLength % 4)); } }
+
+ /// <summary>
+ /// Get the synchronization source.
+ /// </summary>
+ public string SynchronizationSource { get; private set; }
+ /// <summary>
+ /// Get the list of source descriptioni items.
+ /// </summary>
+ public Collection<SourceDescriptionItem> Items;
+
+ private int blockLength;
+
+ public void Process(byte[] buffer, int offset)
+ {
+ SynchronizationSource = Utils.ConvertBytesToString(buffer, offset, 4);
+ Items = new Collection<SourceDescriptionItem>();
+ int index = 4;
+ bool done = false;
+ do
+ {
+ SourceDescriptionItem item = new SourceDescriptionItem();
+ item.Process(buffer, offset + index);
+
+ if (item.Type != 0)
+ {
+ Items.Add(item);
+ index += item.ItemLength;
+ blockLength += item.ItemLength;
+ }
+ else
+ {
+ blockLength++;
+ done = true;
+ }
+ }
+ while (!done);
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs
new file mode 100644
index 000000000..5dd033642
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs
@@ -0,0 +1,60 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp
+{
+ /// <summary>
+ /// The class that describes a source description item.
+ /// </summary>
+ public class SourceDescriptionItem
+ {
+ /// <summary>
+ /// Get the type.
+ /// </summary>
+ public int Type { get; private set; }
+ /// <summary>
+ /// Get the text.
+ /// </summary>
+ public string Text { get; private set; }
+
+ /// <summary>
+ /// Get the length of the item.
+ /// </summary>
+ public int ItemLength { get { return (Text.Length + 2); } }
+
+ /// <summary>
+ /// Initialize a new instance of the SourceDescriptionItem class.
+ /// </summary>
+ public SourceDescriptionItem() { }
+
+ /// <summary>
+ /// Unpack the data in a packet.
+ /// </summary>
+ /// <param name="buffer">The buffer containing the packet.</param>
+ /// <param name="offset">The offset to the first byte of the packet within the buffer.</param>
+ /// <returns>An ErrorSpec instance if an error occurs; null otherwise.</returns>
+ public void Process(byte[] buffer, int offset)
+ {
+ Type = buffer[offset];
+ if (Type != 0)
+ {
+ int length = buffer[offset + 1];
+ Text = Utils.ConvertBytesToString(buffer, offset + 2, length);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs
new file mode 100644
index 000000000..ea6a9ba6a
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs
@@ -0,0 +1,160 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtp
+{
+ public class RtpListener
+ {
+ private readonly ILogger _logger;
+ private AutoResetEvent _rtpListenerThreadStopEvent;
+ private Thread _rtpListenerThread;
+ private UdpClient _udpClient;
+ private IPEndPoint _multicastEndPoint;
+ private IPEndPoint _serverEndPoint;
+ private TransmissionMode _transmissionMode;
+ public RtpListener(String address, int port,TransmissionMode mode,ILogger logger)
+ {
+ _logger = logger;
+ _transmissionMode = mode;
+ switch (mode)
+ {
+ case TransmissionMode.Unicast:
+ _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(address), port));
+ _serverEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ break;
+ case TransmissionMode.Multicast:
+ _multicastEndPoint = new IPEndPoint(IPAddress.Parse(address), port);
+ _serverEndPoint = null;
+ _udpClient = new UdpClient();
+ _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
+ _udpClient.ExclusiveAddressUse = false;
+ _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, _multicastEndPoint.Port));
+ _udpClient.JoinMulticastGroup(_multicastEndPoint.Address);
+ break;
+ }
+ //StartRtpListenerThread();
+ }
+ public void StartRtpListenerThread()
+ {
+ // Kill the existing thread if it is in "zombie" state.
+ if (_rtpListenerThread != null && !_rtpListenerThread.IsAlive)
+ {
+ StopRtpListenerThread();
+ }
+
+ if (_rtpListenerThread == null)
+ {
+ _logger.Info("SAT>IP : starting new RTP listener thread");
+ _rtpListenerThreadStopEvent = new AutoResetEvent(false);
+ _rtpListenerThread = new Thread(new ThreadStart(RtpListenerThread));
+ _rtpListenerThread.Name = string.Format("SAT>IP tuner RTP listener");
+ _rtpListenerThread.IsBackground = true;
+ _rtpListenerThread.Priority = ThreadPriority.Lowest;
+ _rtpListenerThread.Start();
+ }
+ }
+
+ public void StopRtpListenerThread()
+ {
+ if (_rtpListenerThread != null)
+ {
+ if (!_rtpListenerThread.IsAlive)
+ {
+ _logger.Info("SAT>IP : aborting old RTP listener thread");
+ _rtpListenerThread.Abort();
+ }
+ else
+ {
+ _rtpListenerThreadStopEvent.Set();
+ if (!_rtpListenerThread.Join(400 * 2))
+ {
+ _logger.Info("SAT>IP : failed to join RTP listener thread, aborting thread");
+ _rtpListenerThread.Abort();
+ }
+ }
+ _rtpListenerThread = null;
+ if (_rtpListenerThreadStopEvent != null)
+ {
+ _rtpListenerThreadStopEvent.Close();
+ _rtpListenerThreadStopEvent = null;
+ }
+ }
+ }
+
+ private void RtpListenerThread()
+ {
+ try
+ {
+ try
+ {
+
+ while (!_rtpListenerThreadStopEvent.WaitOne(1))
+ {
+ byte[] receivedbytes = _udpClient.Receive(ref _serverEndPoint);
+ RtpPacket packet = RtpPacket.Decode(receivedbytes);
+ OnPacketReceived(new RtpPacketReceivedArgs(packet));
+ }
+ }
+ finally
+ {
+ switch (_transmissionMode)
+ {
+ case TransmissionMode.Multicast:
+ _udpClient.DropMulticastGroup(_multicastEndPoint.Address);
+ _udpClient.Close();
+ break;
+ case TransmissionMode.Unicast:
+ _udpClient.Close();
+ break;
+ }
+ }
+ }
+ catch (ThreadAbortException)
+ {
+ }
+ catch (Exception ex)
+ {
+ _logger.Info(string.Format("SAT>IP : RTP listener thread exception"), ex);
+ return;
+ }
+ _logger.Info("SAT>IP : RTP listener thread stopping");
+ }
+ public delegate void PacketReceivedHandler(object sender, RtpPacketReceivedArgs e);
+ public event PacketReceivedHandler PacketReceived;
+ public class RtpPacketReceivedArgs : EventArgs
+ {
+ public RtpPacket Packet { get; private set; }
+
+ public RtpPacketReceivedArgs(RtpPacket packet)
+ {
+ Packet = packet;
+ }
+ }
+ protected void OnPacketReceived(RtpPacketReceivedArgs args)
+ {
+ if (PacketReceived != null)
+ {
+ PacketReceived(this, args);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs
new file mode 100644
index 000000000..489d7f087
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs
@@ -0,0 +1,116 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+using System;
+using System.Collections.ObjectModel;
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtp
+{
+ public class RtpPacket
+ {
+ private static int MinHeaderLength = 12;
+ public int HeaderSize = MinHeaderLength;
+ public int Version { get; set; }
+ public Boolean Padding { get; set; }
+ public Boolean Extension { get; set; }
+ public int ContributingSourceCount { get; set; }
+ public Boolean Marker { get; set; }
+ public int PayloadType { get; set; }
+ public int SequenceNumber { get; set; }
+ public long TimeStamp { get; set; }
+ public long SynchronizationSource { get; set; }
+ public Collection<string> ContributingSources { get; private set; }
+ public int ExtensionHeaderId = 0;
+ public int ExtensionHeaderLength = 0;
+ public bool HasPayload { get; set; }
+ public byte[] Payload { get; set; }
+ public RtpPacket()
+ {
+
+ }
+ public static RtpPacket Decode(byte[] buffer)
+ {
+ var packet = new RtpPacket();
+ packet.Version = buffer[0] >> 6;
+ packet.Padding = (buffer[0] & 0x20) != 0;
+ packet.Extension = (buffer[0] & 0x10) != 0;
+ packet.ContributingSourceCount = buffer[0] & 0x0f;
+
+ packet.Marker = (buffer[1] & 0x80) != 0;
+ packet.PayloadType = buffer[1] & 0x7f;
+
+ packet.SequenceNumber = Utils.Convert2BytesToInt(buffer, 2);
+ packet.TimeStamp = Utils.Convert4BytesToLong(buffer, 4);
+ packet.SynchronizationSource = Utils.Convert4BytesToLong(buffer, 8);
+
+ int index = 12;
+
+ if (packet.ContributingSourceCount != 0)
+ {
+ packet.ContributingSources = new Collection<string>();
+
+ while (packet.ContributingSources.Count < packet.ContributingSourceCount)
+ {
+ packet.ContributingSources.Add(Utils.ConvertBytesToString(buffer, index, 4));
+ index += 4;
+ }
+ }
+ var dataoffset = 0;
+ if (!packet.Extension)
+ dataoffset = index;
+ else
+ {
+ packet.ExtensionHeaderId = Utils.Convert2BytesToInt(buffer, index);
+ packet.ExtensionHeaderLength = Utils.Convert2BytesToInt(buffer, index + 2);
+ dataoffset = index + packet.ExtensionHeaderLength + 4;
+ }
+
+ var dataLength = buffer.Length - dataoffset;
+ if (dataLength > dataoffset)
+ {
+ packet.HasPayload = true;
+ packet.Payload = new byte[dataLength];
+ Array.Copy(buffer, dataoffset, packet.Payload, 0, dataLength);
+ }
+ else
+ {
+ packet.HasPayload = false;
+ }
+ return packet;
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("RTP Packet");
+ sb.AppendFormat("Version: {0} \n", Version);
+ sb.AppendFormat("Padding: {0} \n", Padding);
+ sb.AppendFormat("Extension: {0} \n", Extension);
+ sb.AppendFormat("Contributing Source Identifiers Count: {0} \n", ContributingSourceCount);
+ sb.AppendFormat("Marker: {0} \n", Marker);
+ sb.AppendFormat("Payload Type: {0} \n", PayloadType);
+ sb.AppendFormat("Sequence Number: {0} \n", SequenceNumber);
+ sb.AppendFormat("Timestamp: {0} .\n", TimeStamp);
+ sb.AppendFormat("Synchronization Source Identifier: {0} \n", SynchronizationSource);
+ sb.AppendFormat("\n");
+ return sb.ToString();
+ }
+
+ }
+
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
index 9d5dba282..a0b8ef5f7 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
@@ -14,6 +14,7 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Serialization;
using MediaBrowser.Model.Extensions;
using System.Xml.Linq;
+using MediaBrowser.Model.Events;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -26,7 +27,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IHttpClient _httpClient;
private readonly IJsonSerializer _json;
-
+ private int _tunerCountDVBS=0;
+ private int _tunerCountDVBC=0;
+ private int _tunerCountDVBT=0;
+ private bool _supportsDVBS=false;
+ private bool _supportsDVBC=false;
+ private bool _supportsDVBT=false;
public static SatIpDiscovery Current;
public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
@@ -45,18 +51,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
_deviceDiscovery.DeviceDiscovered += _deviceDiscovery_DeviceDiscovered;
}
- void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
+ void _deviceDiscovery_DeviceDiscovered(object sender, GenericEventArgs<UpnpDeviceInfo> e)
{
+ var info = e.Argument;
+
string st = null;
string nt = null;
- e.Headers.TryGetValue("ST", out st);
- e.Headers.TryGetValue("NT", out nt);
+ info.Headers.TryGetValue("ST", out st);
+ info.Headers.TryGetValue("NT", out nt);
if (string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase) ||
string.Equals(nt, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
{
string location;
- if (e.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
+ if (info.Headers.TryGetValue("Location", out location) && !string.IsNullOrWhiteSpace(location))
{
_logger.Debug("SAT IP found at {0}", location);
@@ -167,7 +175,57 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
public void Dispose()
{
}
+ private void ReadCapability(string capability)
+ {
+
+ string[] cap = capability.Split('-');
+ switch (cap[0].ToLower())
+ {
+ case "dvbs":
+ case "dvbs2":
+ {
+ // Optional that you know what an device Supports can you add an flag
+ _supportsDVBS = true;
+ for (int i = 0; i < int.Parse(cap[1]); i++)
+ {
+ //ToDo Create Digital Recorder / Tuner Capture Instance here for each with index FE param in Sat>Ip Spec for direct communication with this instance
+ }
+ _tunerCountDVBS = int.Parse(cap[1]);
+ break;
+ }
+ case "dvbc":
+ case "dvbc2":
+ {
+ // Optional that you know what an device Supports can you add an flag
+ _supportsDVBC = true;
+
+ for (int i = 0; i < int.Parse(cap[1]); i++)
+ {
+ //ToDo Create Digital Recorder / Tuner Capture Instance here for each with index FE param in Sat>Ip Spec for direct communication with this instance
+
+ }
+ _tunerCountDVBC = int.Parse(cap[1]);
+ break;
+ }
+ case "dvbt":
+ case "dvbt2":
+ {
+ // Optional that you know what an device Supports can you add an flag
+ _supportsDVBT = true;
+
+
+ for (int i = 0; i < int.Parse(cap[1]); i++)
+ {
+ //ToDo Create Digital Recorder / Tuner Capture Instance here for each with index FE param in Sat>Ip Spec for direct communication with this instance
+
+ }
+ _tunerCountDVBT = int.Parse(cap[1]);
+ break;
+ }
+ }
+
+ }
public async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
{
Uri locationUri = new Uri(url);
@@ -182,7 +240,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
string modelurl = "";
string serialnumber = "";
string presentationurl = "";
- string capabilities = "";
+ //string capabilities = "";
string m3u = "";
var document = XDocument.Load(locationUri.AbsoluteUri);
var xnm = new XmlNamespaceManager(new NameTable());
@@ -227,7 +285,27 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
var presentationUrlElement = deviceElement.Element(n0 + "presentationURL");
if (presentationUrlElement != null) presentationurl = presentationUrlElement.Value;
var capabilitiesElement = deviceElement.Element(n1 + "X_SATIPCAP");
- if (capabilitiesElement != null) capabilities = capabilitiesElement.Value;
+ if (capabilitiesElement != null)
+ {
+ //_capabilities = capabilitiesElement.Value;
+ if (capabilitiesElement.Value.Contains(','))
+ {
+ string[] capabilities = capabilitiesElement.Value.Split(',');
+ foreach (var capability in capabilities)
+ {
+ ReadCapability(capability);
+ }
+ }
+ else
+ {
+ ReadCapability(capabilitiesElement.Value);
+ }
+ }
+ else
+ {
+ _supportsDVBS = true;
+ _tunerCountDVBS =1;
+ }
var m3uElement = deviceElement.Element(n1 + "X_SATIPM3U");
if (m3uElement != null) m3u = m3uElement.Value;
}
@@ -239,8 +317,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
Id = uniquedevicename,
IsEnabled = true,
Type = SatIpHost.DeviceType,
- Tuners = 1,
- TunersAvailable = 1,
+ Tuners = _tunerCountDVBS,
+ TunersAvailable = _tunerCountDVBS,
M3UUrl = m3u
};
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs
new file mode 100644
index 000000000..71d7656d9
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs
@@ -0,0 +1,25 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
+{
+ public enum TransmissionMode
+ {
+ Unicast,
+ Multicast
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs
new file mode 100644
index 000000000..3595e4b0a
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) <2007-2016> <Kay Diefenthal>
+
+ SatIp.RtspSample is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ SatIp.RtspSample is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>.
+*/
+using System;
+using System.Text;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
+{
+ public class Utils
+ {
+ public static int Convert2BytesToInt(byte[] buffer, int offset)
+ {
+ int temp = (int)buffer[offset];
+ temp = (temp * 256) + buffer[offset + 1];
+
+ return (temp);
+ }
+ public static int Convert3BytesToInt(byte[] buffer, int offset)
+ {
+ int temp = (int)buffer[offset];
+ temp = (temp * 256) + buffer[offset + 1];
+ temp = (temp * 256) + buffer[offset + 2];
+
+ return (temp);
+ }
+ public static int Convert4BytesToInt(byte[] buffer, int offset)
+ {
+ int temp =(int)buffer[offset];
+ temp = (temp * 256) + buffer[offset + 1];
+ temp = (temp * 256) + buffer[offset + 2];
+ temp = (temp * 256) + buffer[offset + 3];
+
+ return (temp);
+ }
+ public static long Convert4BytesToLong(byte[] buffer, int offset)
+ {
+ long temp = 0;
+
+ for (int index = 0; index < 4; index++)
+ temp = (temp * 256) + buffer[offset + index];
+
+ return (temp);
+ }
+ public static long Convert8BytesToLong(byte[] buffer, int offset)
+ {
+ long temp = 0;
+
+ for (int index = 0; index < 8; index++)
+ temp = (temp * 256) + buffer[offset + index];
+
+ return (temp);
+ }
+ public static string ConvertBytesToString(byte[] buffer, int offset, int length)
+ {
+ StringBuilder reply = new StringBuilder(4);
+ for (int index = 0; index < length; index++)
+ reply.Append((char)buffer[offset + index]);
+ return (reply.ToString());
+ }
+ public static DateTime NptTimestampToDateTime(long nptTimestamp) { return NptTimestampToDateTime((uint)((nptTimestamp >> 32) & 0xFFFFFFFF), (uint)(nptTimestamp & 0xFFFFFFFF),null); }
+
+ public static DateTime NptTimestampToDateTime(uint seconds, uint fractions, DateTime? epoch )
+ {
+ ulong ticks =(ulong)((seconds * TimeSpan.TicksPerSecond) + ((fractions * TimeSpan.TicksPerSecond) / 0x100000000L));
+ if (epoch.HasValue) return epoch.Value + TimeSpan.FromTicks((Int64)ticks);
+ return (seconds & 0x80000000L) == 0 ? UtcEpoch2036 + TimeSpan.FromTicks((Int64)ticks) : UtcEpoch1900 + TimeSpan.FromTicks((Int64)ticks);
+ }
+
+ //When the First Epoch will wrap (The real Y2k)
+ public static DateTime UtcEpoch2036 = new DateTime(2036, 2, 7, 6, 28, 16, DateTimeKind.Utc);
+
+ public static DateTime UtcEpoch1900 = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ public static DateTime UtcEpoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ }
+}
diff --git a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
index 723807509..ad1f18619 100644
--- a/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
+++ b/MediaBrowser.Server.Implementations/Localization/Ratings/de.txt
@@ -1,5 +1,10 @@
DE-0,1
+FSK-0,1
DE-6,5
+FSK-6,5
DE-12,7
+FSK-12,7
DE-16,8
+FSK-16,8
DE-18,9
+FSK-18,9 \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 6879c3f40..e182ad6a5 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -105,9 +105,6 @@
<Reference Include="UniversalDetector">
<HintPath>..\ThirdParty\UniversalDetector\UniversalDetector.dll</HintPath>
</Reference>
- <Reference Include="Mono.Nat">
- <HintPath>..\packages\Mono.Nat.1.2.24.0\lib\net40\Mono.Nat.dll</HintPath>
- </Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\SharedVersion.cs">
@@ -124,7 +121,6 @@
<Compile Include="Channels\RefreshChannelsScheduledTask.cs" />
<Compile Include="Collections\CollectionManager.cs" />
<Compile Include="Collections\CollectionsDynamicFolder.cs" />
- <Compile Include="Collections\ManualCollectionsFolder.cs" />
<Compile Include="Collections\CollectionImageProvider.cs" />
<Compile Include="Configuration\ServerConfigurationManager.cs" />
<Compile Include="Connect\ConnectData.cs" />
@@ -251,6 +247,18 @@
<Compile Include="LiveTv\RecordingImageProvider.cs" />
<Compile Include="LiveTv\RefreshChannelsScheduledTask.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\ChannelScan.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\ReportBlock.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpAppPacket.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpByePacket.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpListener.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpPacket.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpReceiverReportPacket.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpSenderReportPacket.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\RtcpSourceDescriptionPacket.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\SourceDescriptionBlock.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtcp\SourceDescriptionItem.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtp\RtpListener.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Rtp\RtpPacket.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspMethod.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspRequest.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspResponse.cs" />
@@ -258,6 +266,8 @@
<Compile Include="LiveTv\TunerHosts\SatIp\Rtsp\RtspStatusCode.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\SatIpHost.cs" />
<Compile Include="LiveTv\TunerHosts\SatIp\SatIpDiscovery.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\TransmissionMode.cs" />
+ <Compile Include="LiveTv\TunerHosts\SatIp\Utils.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
<Compile Include="Logging\PatternsLogger.cs" />
<Compile Include="MediaEncoder\EncodingManager.cs" />
@@ -377,6 +387,10 @@
<Project>{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
+ <ProjectReference Include="..\Mono.Nat\Mono.Nat.csproj">
+ <Project>{d7453b88-2266-4805-b39b-2b5a2a33e1ba}</Project>
+ <Name>Mono.Nat</Name>
+ </ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Localization\Ratings\us.txt" />
diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
index 46ba7d2e7..7d0841fa6 100644
--- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
+++ b/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
@@ -14,6 +14,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Library;
namespace MediaBrowser.Server.Implementations.MediaEncoder
{
@@ -24,16 +25,18 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
private readonly ILogger _logger;
private readonly IMediaEncoder _encoder;
private readonly IChapterManager _chapterManager;
+ private readonly ILibraryManager _libraryManager;
public EncodingManager(IFileSystem fileSystem,
ILogger logger,
IMediaEncoder encoder,
- IChapterManager chapterManager)
+ IChapterManager chapterManager, ILibraryManager libraryManager)
{
_fileSystem = fileSystem;
_logger = logger;
_encoder = encoder;
_chapterManager = chapterManager;
+ _libraryManager = libraryManager;
}
/// <summary>
@@ -57,27 +60,38 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
return false;
}
- var options = _chapterManager.GetConfiguration();
-
- if (video is Movie)
+ var libraryOptions = _libraryManager.GetLibraryOptions(video);
+ if (libraryOptions != null && libraryOptions.SchemaVersion >= 2)
{
- if (!options.EnableMovieChapterImageExtraction)
+ if (!libraryOptions.EnableChapterImageExtraction)
{
return false;
}
}
- else if (video is Episode)
+ else
{
- if (!options.EnableEpisodeChapterImageExtraction)
+ var options = _chapterManager.GetConfiguration();
+
+ if (video is Movie)
{
- return false;
+ if (!options.EnableMovieChapterImageExtraction)
+ {
+ return false;
+ }
}
- }
- else
- {
- if (!options.EnableOtherVideoChapterImageExtraction)
+ else if (video is Episode)
{
- return false;
+ if (!options.EnableEpisodeChapterImageExtraction)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (!options.EnableOtherVideoChapterImageExtraction)
+ {
+ return false;
+ }
}
}
@@ -123,21 +137,28 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
{
if (extractImages)
{
- if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd)
+ if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso)
{
continue;
}
+ if (video.VideoType == VideoType.BluRay || video.VideoType == VideoType.Dvd)
+ {
+ if (video.PlayableStreamFileNames.Count != 1)
+ {
+ continue;
+ }
+ }
- // Add some time for the first chapter to make sure we don't end up with a black image
- var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
+ try
+ {
+ // Add some time for the first chapter to make sure we don't end up with a black image
+ var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks);
- var protocol = MediaProtocol.File;
+ var protocol = MediaProtocol.File;
- var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, video.PlayableStreamFileNames);
+ var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, video.PlayableStreamFileNames);
- try
- {
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
var tempFile = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false);
File.Copy(tempFile, path, true);
@@ -157,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
}
catch (Exception ex)
{
- _logger.ErrorException("Error extracting chapter images for {0}", ex, string.Join(",", inputPath));
+ _logger.ErrorException("Error extracting chapter images for {0}", ex, string.Join(",", video.Path));
success = false;
break;
}
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index b21fcddd4..afcdf9d90 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -1341,8 +1341,19 @@ namespace MediaBrowser.Server.Implementations.Session
private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
{
- var user = _userManager.Users
- .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
+ User user = null;
+ if (!string.IsNullOrWhiteSpace(request.UserId))
+ {
+ var idGuid = new Guid(request.UserId);
+ user = _userManager.Users
+ .FirstOrDefault(i => i.Id == idGuid);
+ }
+
+ if (user == null)
+ {
+ user = _userManager.Users
+ .FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
+ }
if (user != null && !string.IsNullOrWhiteSpace(request.DeviceId))
{
diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
index 175dbbc01..f40b64498 100644
--- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
+++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.Sync
if (supportsDca)
{
- mkvAudio += ",dca";
+ mkvAudio += ",dca,dts";
}
var videoProfile = "high|main|baseline|constrained baseline";
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
index ffba60af8..1278a40a4 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
@@ -541,6 +541,11 @@ namespace MediaBrowser.Server.Implementations.Sync
return true;
}
+ if (item.SourceType == SourceType.Channel)
+ {
+ return BaseItem.ChannelManager.SupportsSync(item.ChannelId);
+ }
+
return item.LocationType == LocationType.FileSystem || item is Season;
}
diff --git a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs
index 32992b9b2..6dd5de548 100644
--- a/MediaBrowser.Server.Implementations/Udp/UdpServer.cs
+++ b/MediaBrowser.Server.Implementations/Udp/UdpServer.cs
@@ -30,7 +30,7 @@ namespace MediaBrowser.Server.Implementations.Udp
private bool _isDisposed;
- private readonly List<Tuple<string, byte[], Action<string, Encoding>>> _responders = new List<Tuple<string, byte[], Action<string, Encoding>>>();
+ private readonly List<Tuple<string, bool, Func<string, string, Encoding, Task>>> _responders = new List<Tuple<string, bool, Func<string, string, Encoding, Task>>>();
private readonly IServerApplicationHost _appHost;
private readonly IJsonSerializer _json;
@@ -49,43 +49,35 @@ namespace MediaBrowser.Server.Implementations.Udp
_appHost = appHost;
_json = json;
- AddMessageResponder("who is EmbyServer?", RespondToV2Message);
- AddMessageResponder("who is MediaBrowserServer_v2?", RespondToV2Message);
- AddMessageResponder("who is MediaBrowserServer?", RespondToV1Message);
+ AddMessageResponder("who is EmbyServer?", true, RespondToV2Message);
+ AddMessageResponder("who is MediaBrowserServer_v2?", false, RespondToV2Message);
}
- private void AddMessageResponder(string message, Action<string, Encoding> responder)
+ private void AddMessageResponder(string message, bool isSubstring, Func<string, string, Encoding, Task> responder)
{
- var expectedMessageBytes = Encoding.UTF8.GetBytes(message);
-
- _responders.Add(new Tuple<string, byte[], Action<string, Encoding>>(message, expectedMessageBytes, responder));
+ _responders.Add(new Tuple<string, bool, Func<string, string, Encoding, Task>>(message, isSubstring, responder));
}
/// <summary>
/// Raises the <see cref="E:MessageReceived" /> event.
/// </summary>
/// <param name="e">The <see cref="UdpMessageReceivedEventArgs"/> instance containing the event data.</param>
- private void OnMessageReceived(UdpMessageReceivedEventArgs e)
+ private async void OnMessageReceived(UdpMessageReceivedEventArgs e)
{
- var responder = _responders.FirstOrDefault(i => i.Item2.SequenceEqual(e.Bytes));
var encoding = Encoding.UTF8;
+ var responder = GetResponder(e.Bytes, encoding);
if (responder == null)
{
- var text = Encoding.Unicode.GetString(e.Bytes);
- responder = _responders.FirstOrDefault(i => string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase));
-
- if (responder != null)
- {
- encoding = Encoding.Unicode;
- }
+ encoding = Encoding.Unicode;
+ responder = GetResponder(e.Bytes, encoding);
}
if (responder != null)
{
try
{
- responder.Item3(e.RemoteEndPoint, encoding);
+ await responder.Item2.Item3(responder.Item1, e.RemoteEndPoint, encoding).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -94,33 +86,29 @@ namespace MediaBrowser.Server.Implementations.Udp
}
}
- private async void RespondToV1Message(string endpoint, Encoding encoding)
+ private Tuple<string, Tuple<string, bool, Func<string, string, Encoding, Task>>> GetResponder(byte[] bytes, Encoding encoding)
{
- var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
-
- if (!string.IsNullOrEmpty(localUrl))
+ var text = encoding.GetString(bytes);
+ var responder = _responders.FirstOrDefault(i =>
{
- // This is how we did the old v1 search, so need to strip off the protocol
- var index = localUrl.IndexOf("://", StringComparison.OrdinalIgnoreCase);
-
- if (index != -1)
+ if (i.Item2)
{
- localUrl = localUrl.Substring(index + 3);
+ return text.IndexOf(i.Item1, StringComparison.OrdinalIgnoreCase) != -1;
}
+ return string.Equals(i.Item1, text, StringComparison.OrdinalIgnoreCase);
+ });
- // Send a response back with our ip address and port
- var response = String.Format("MediaBrowserServer|{0}", localUrl);
-
- await SendAsync(Encoding.UTF8.GetBytes(response), endpoint);
- }
- else
+ if (responder == null)
{
- _logger.Warn("Unable to respond to udp request because the local ip address could not be determined.");
+ return null;
}
+ return new Tuple<string, Tuple<string, bool, Func<string, string, Encoding, Task>>>(text, responder);
}
- private async void RespondToV2Message(string endpoint, Encoding encoding)
+ private async Task RespondToV2Message(string messageText, string endpoint, Encoding encoding)
{
+ var parts = messageText.Split('|');
+
var localUrl = await _appHost.GetLocalApiUrl().ConfigureAwait(false);
if (!string.IsNullOrEmpty(localUrl))
@@ -132,7 +120,12 @@ namespace MediaBrowser.Server.Implementations.Udp
Name = _appHost.FriendlyName
};
- await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint);
+ await SendAsync(encoding.GetBytes(_json.SerializeToString(response)), endpoint).ConfigureAwait(false);
+
+ if (parts.Length > 1)
+ {
+ _appHost.EnableLoopback(parts[1]);
+ }
}
else
{
diff --git a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
index 29716d33e..2cff4a14f 100644
--- a/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/CollectionFolderImageProvider.cs
@@ -13,6 +13,10 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.Collections;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Querying;
namespace MediaBrowser.Server.Implementations.UserViews
{
@@ -109,4 +113,62 @@ namespace MediaBrowser.Server.Implementations.UserViews
return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false);
}
}
+
+ public class ManualCollectionFolderImageProvider : BaseDynamicImageProvider<ManualCollectionsFolder>
+ {
+ private readonly ILibraryManager _libraryManager;
+
+ public ManualCollectionFolderImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, ILibraryManager libraryManager) : base(fileSystem, providerManager, applicationPaths, imageProcessor)
+ {
+ _libraryManager = libraryManager;
+ }
+
+ public override IEnumerable<ImageType> GetSupportedImages(IHasImages item)
+ {
+ return new List<ImageType>
+ {
+ ImageType.Primary
+ };
+ }
+
+ protected override async Task<List<BaseItem>> GetItemsWithImages(IHasImages item)
+ {
+ var view = (ManualCollectionsFolder)item;
+
+ var recursive = !new[] { CollectionType.Playlists, CollectionType.Channels }.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+
+ var items = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ Recursive = recursive,
+ IncludeItemTypes = new[] { typeof(BoxSet).Name },
+ Limit = 20,
+ SortBy = new[] { ItemSortBy.Random }
+ });
+
+ return GetFinalItems(items.Where(i => i.HasImage(ImageType.Primary) || i.HasImage(ImageType.Thumb)).ToList(), 8);
+ }
+
+ protected override bool Supports(IHasImages item)
+ {
+ return item is ManualCollectionsFolder;
+ }
+
+ protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+ {
+ var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ".png");
+
+ if (imageType == ImageType.Primary)
+ {
+ if (itemsWithImages.Count == 0)
+ {
+ return null;
+ }
+
+ return await CreateThumbCollage(item, itemsWithImages, outputPath, 960, 540).ConfigureAwait(false);
+ }
+
+ return await base.CreateImage(item, itemsWithImages, outputPath, imageType, imageIndex).ConfigureAwait(false);
+ }
+ }
+
}
diff --git a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
index 3c75c8a48..f40072897 100644
--- a/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/UserViews/DynamicImageProvider.cs
@@ -14,17 +14,20 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Controller.LiveTv;
namespace MediaBrowser.Server.Implementations.UserViews
{
public class DynamicImageProvider : BaseDynamicImageProvider<UserView>
{
private readonly IUserManager _userManager;
+ private readonly ILibraryManager _libraryManager;
- public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager)
+ public DynamicImageProvider(IFileSystem fileSystem, IProviderManager providerManager, IApplicationPaths applicationPaths, IImageProcessor imageProcessor, IUserManager userManager, ILibraryManager libraryManager)
: base(fileSystem, providerManager, applicationPaths, imageProcessor)
{
_userManager = userManager;
+ _libraryManager = libraryManager;
}
public override IEnumerable<ImageType> GetSupportedImages(IHasImages item)
@@ -50,7 +53,15 @@ namespace MediaBrowser.Server.Implementations.UserViews
if (string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
{
- return new List<BaseItem>();
+ var programs = _libraryManager.GetItemList(new InternalItemsQuery
+ {
+ IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
+ ImageTypes = new[] { ImageType.Primary },
+ Limit = 30,
+ IsMovie = true
+ }).ToList();
+
+ return GetFinalItems(programs).ToList();
}
if (string.Equals(view.ViewType, SpecialFolder.MovieGenre, StringComparison.OrdinalIgnoreCase) ||
@@ -147,6 +158,7 @@ namespace MediaBrowser.Server.Implementations.UserViews
CollectionType.MusicVideos,
CollectionType.HomeVideos,
CollectionType.BoxSets,
+ CollectionType.LiveTv,
CollectionType.Playlists,
CollectionType.Photos,
string.Empty
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 746dc7f62..94522cd50 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -5,7 +5,6 @@
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
<package id="MediaBrowser.Naming" version="1.0.0.55" targetFramework="net45" />
- <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
<package id="morelinq" version="1.4.0" targetFramework="net45" />
<package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
<package id="SimpleInjector" version="3.2.0" targetFramework="net45" />
diff --git a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
index f04b833b9..9f3bf5102 100644
--- a/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
+++ b/MediaBrowser.Server.Mac/Emby.Server.Mac.csproj
@@ -498,9 +498,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvitems.html">
<Link>Resources\dashboard-ui\livetvitems.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvrecordinglist.html">
- <Link>Resources\dashboard-ui\livetvrecordinglist.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\livetvseriestimer.html">
<Link>Resources\dashboard-ui\livetvseriestimer.html</Link>
</BundleResource>
@@ -1050,6 +1047,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-chrome.js">
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-chrome.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-winjs.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-apiclient\serverdiscovery-winjs.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-apiclient\serverdiscovery.js">
<Link>Resources\dashboard-ui\bower_components\emby-apiclient\serverdiscovery.js</Link>
</BundleResource>
@@ -1215,6 +1215,9 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\dialog\dialog.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\dialog\dialog.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\dialog\dialog.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\dialog\dialog.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\dialoghelper\dialoghelper.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\dialoghelper\dialoghelper.css</Link>
</BundleResource>
@@ -1470,6 +1473,18 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fonts\roboto\ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fonts\roboto\ty9dfvLAziwdqQ2dHoyjphTbgVql8nDJpwnrE27mub0.woff2</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fullscreen\fullscreen-doubleclick.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fullscreen\fullscreen-doubleclick.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\fullscreen\fullscreenmanager.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\fullscreen\fullscreenmanager.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide-settings.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide-settings.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide-settings.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide-settings.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\guide.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\guide.css</Link>
</BundleResource>
@@ -1479,6 +1494,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\guide\tvguide.template.html">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\guide\tvguide.template.html</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\imageeditor\imageeditor.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\imageeditor\imageeditor.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\imageeditor\imageeditor.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\imageeditor\imageeditor.template.html</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\images\basicimagefetcher.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\images\basicimagefetcher.js</Link>
</BundleResource>
@@ -1557,14 +1578,17 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\multiselect\multiselect.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\multiselect\multiselect.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\notifications\badge.png">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\notifications\badge.png</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\notifications\notificationicon.png">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\notifications\notificationicon.png</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\notifications\notifications.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\notifications\notifications.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\page.js\page.js">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\page.js\page.js</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\pagejs\page.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\pagejs\page.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\playlisteditor\playlisteditor.js</Link>
@@ -1578,14 +1602,17 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\polyfills\objectassign.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\polyfills\objectassign.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\polyfills\raf.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\polyfills\raf.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\nativeprompt.js</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\prompt.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\prompt.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\style.css">
- <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\style.css</Link>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\prompt\prompt.template.html">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\prompt\prompt.template.html</Link>
</BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\recordingcreator\recordingcreator.css</Link>
@@ -1617,6 +1644,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\scroller\smoothscroller.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\scroller\smoothscroller.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\serviceworker\notifications.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\serviceworker\notifications.js</Link>
+ </BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\serviceworker\sync.js">
+ <Link>Resources\dashboard-ui\bower_components\emby-webcomponents\serviceworker\sync.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmanager.js">
<Link>Resources\dashboard-ui\bower_components\emby-webcomponents\sharing\sharingmanager.js</Link>
</BundleResource>
@@ -3423,12 +3456,6 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\imagedownloader\imagedownloader.template.html">
<Link>Resources\dashboard-ui\components\imagedownloader\imagedownloader.template.html</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\imageeditor\imageeditor.js">
- <Link>Resources\dashboard-ui\components\imageeditor\imageeditor.js</Link>
- </BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\imageeditor\imageeditor.template.html">
- <Link>Resources\dashboard-ui\components\imageeditor\imageeditor.template.html</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\components\imageuploader\imageuploader.js">
<Link>Resources\dashboard-ui\components\imageuploader\imageuploader.js</Link>
</BundleResource>
@@ -3936,12 +3963,12 @@
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvitems.js">
<Link>Resources\dashboard-ui\scripts\livetvitems.js</Link>
</BundleResource>
- <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvrecordinglist.js">
- <Link>Resources\dashboard-ui\scripts\livetvrecordinglist.js</Link>
- </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvrecordings.js">
<Link>Resources\dashboard-ui\scripts\livetvrecordings.js</Link>
</BundleResource>
+ <BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvschedule.js">
+ <Link>Resources\dashboard-ui\scripts\livetvschedule.js</Link>
+ </BundleResource>
<BundleResource Include="..\MediaBrowser.WebDashboard\dashboard-ui\scripts\livetvseriestimer.js">
<Link>Resources\dashboard-ui\scripts\livetvseriestimer.js</Link>
</BundleResource>
diff --git a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
index 7c9b43026..f4e054406 100644
--- a/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mac/Native/BaseMonoApp.cs
@@ -205,6 +205,16 @@ namespace MediaBrowser.Server.Mac
return new NullPowerManagement ();
}
+ public void EnableLoopback(string appName)
+ {
+
+ }
+
+ public bool PortsRequireAuthorization(string applicationPath)
+ {
+ return false;
+ }
+
private NativeEnvironment GetEnvironmentInfo()
{
var info = new NativeEnvironment
diff --git a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
index bcbb10174..e7acb3f50 100644
--- a/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
+++ b/MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
@@ -10,8 +10,9 @@
<RootNamespace>MediaBrowser.Server.Mono</RootNamespace>
<AssemblyName>MediaBrowser.Server.Mono</AssemblyName>
<StartupObject>MediaBrowser.Server.Mono.MainClass</StartupObject>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
diff --git a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
index faf3ba37e..d2a544477 100644
--- a/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
+++ b/MediaBrowser.Server.Mono/Native/BaseMonoApp.cs
@@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text.RegularExpressions;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.System;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
@@ -232,11 +231,6 @@ namespace MediaBrowser.Server.Mono.Native
public string machine = string.Empty;
}
- public IPowerManagement GetPowerManagement()
- {
- return new NullPowerManagement();
- }
-
public FFMpegInstallInfo GetFfmpegInstallInfo()
{
return GetInfo(Environment);
@@ -278,13 +272,15 @@ namespace MediaBrowser.Server.Mono.Native
return info;
}
- }
- public class NullPowerManagement : IPowerManagement
- {
- public void ScheduleWake(DateTime utcTime)
+ public void EnableLoopback(string appName)
{
- throw new NotImplementedException();
+
+ }
+
+ public bool PortsRequireAuthorization(string applicationPath)
+ {
+ return false;
}
}
}
diff --git a/MediaBrowser.Server.Mono/app.config b/MediaBrowser.Server.Mono/app.config
index b0e8558fd..e14b908ad 100644
--- a/MediaBrowser.Server.Mono/app.config
+++ b/MediaBrowser.Server.Mono/app.config
@@ -1,21 +1,21 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
- <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
+ <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<targets async="true"></targets>
</nlog>
<appSettings>
- <add key="DebugProgramDataPath" value="ProgramData-Server" />
- <add key="ReleaseProgramDataPath" value="ProgramData-Server" />
+ <add key="DebugProgramDataPath" value="ProgramData-Server"/>
+ <add key="ReleaseProgramDataPath" value="ProgramData-Server"/>
</appSettings>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
- <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
- <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0" />
+ <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral"/>
+ <bindingRedirect oldVersion="0.0.0.0-1.0.94.0" newVersion="1.0.94.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
-</configuration>
+<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1"/></startup></configuration>
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
index 9c5015b0e..f5419e5cf 100644
--- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
+++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs
@@ -325,6 +325,15 @@ namespace MediaBrowser.Server.Startup.Common
await MediaEncoder.Init().ConfigureAwait(false);
+ if (string.IsNullOrWhiteSpace(MediaEncoder.EncoderPath))
+ {
+ if (ServerConfigurationManager.Configuration.IsStartupWizardCompleted)
+ {
+ ServerConfigurationManager.Configuration.IsStartupWizardCompleted = false;
+ ServerConfigurationManager.SaveConfiguration();
+ }
+ }
+
Logger.Info("ServerId: {0}", SystemId);
Logger.Info("Core startup complete");
HttpServer.GlobalResponse = null;
@@ -336,6 +345,7 @@ namespace MediaBrowser.Server.Startup.Common
{
var name = entryPoint.GetType().FullName;
Logger.Info("Starting entry point {0}", name);
+ var now = DateTime.UtcNow;
try
{
entryPoint.Run();
@@ -344,7 +354,7 @@ namespace MediaBrowser.Server.Startup.Common
{
Logger.ErrorException("Error in {0}", ex, name);
}
- Logger.Info("Entry point completed: {0}", name);
+ Logger.Info("Entry point completed: {0}. Duration: {1} seconds", name, (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
}
Logger.Info("All entry points have started");
@@ -363,7 +373,10 @@ namespace MediaBrowser.Server.Startup.Common
private void PerformPreInitMigrations()
{
- var migrations = new List<IVersionMigration>();
+ var migrations = new List<IVersionMigration>
+ {
+ new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename)
+ };
foreach (var task in migrations)
{
@@ -382,11 +395,8 @@ namespace MediaBrowser.Server.Startup.Common
{
var migrations = new List<IVersionMigration>
{
- new OmdbEpisodeProviderMigration(ServerConfigurationManager),
new MovieDbEpisodeProviderMigration(ServerConfigurationManager),
- new DbMigration(ServerConfigurationManager, TaskManager),
- new UpdateLevelMigration(ServerConfigurationManager, this, HttpClient, JsonSerializer, _releaseAssetFilename),
- new CollectionsViewMigration(ServerConfigurationManager, UserManager)
+ new DbMigration(ServerConfigurationManager, TaskManager)
};
foreach (var task in migrations)
@@ -522,7 +532,7 @@ namespace MediaBrowser.Server.Startup.Common
PlaylistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager, ProviderManager);
RegisterSingleInstance<IPlaylistManager>(PlaylistManager);
- LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager);
+ LiveTvManager = new LiveTvManager(this, ServerConfigurationManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager, LocalizationManager, JsonSerializer, ProviderManager, FileSystemManager, SecurityManager);
RegisterSingleInstance(LiveTvManager);
UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, UserManager, ChannelManager, LiveTvManager, ServerConfigurationManager);
@@ -540,7 +550,7 @@ namespace MediaBrowser.Server.Startup.Common
SubtitleManager = new SubtitleManager(LogManager.GetLogger("SubtitleManager"), FileSystemManager, LibraryMonitor, LibraryManager, MediaSourceManager);
RegisterSingleInstance(SubtitleManager);
- RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager, this, NetworkManager));
+ RegisterSingleInstance<IDeviceDiscovery>(new DeviceDiscovery(LogManager.GetLogger("IDeviceDiscovery"), ServerConfigurationManager));
ChapterManager = new ChapterManager(LibraryManager, LogManager.GetLogger("ChapterManager"), ServerConfigurationManager, ItemRepository);
RegisterSingleInstance(ChapterManager);
@@ -548,17 +558,13 @@ namespace MediaBrowser.Server.Startup.Common
await RegisterMediaEncoder(innerProgress).ConfigureAwait(false);
progress.Report(90);
- EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager);
+ EncodingManager = new EncodingManager(FileSystemManager, Logger, MediaEncoder, ChapterManager, LibraryManager);
RegisterSingleInstance(EncodingManager);
- RegisterSingleInstance(NativeApp.GetPowerManagement());
-
var sharingRepo = new SharingRepository(LogManager, ApplicationPaths, NativeApp.GetDbConnector());
await sharingRepo.Initialize().ConfigureAwait(false);
RegisterSingleInstance<ISharingManager>(new SharingManager(sharingRepo, ServerConfigurationManager, LibraryManager, this));
- RegisterSingleInstance<ISsdpHandler>(new SsdpHandler(LogManager.GetLogger("SsdpHandler"), ServerConfigurationManager, this));
-
var activityLogRepo = await GetActivityLogRepository().ConfigureAwait(false);
RegisterSingleInstance(activityLogRepo);
RegisterSingleInstance<IActivityManager>(new ActivityManager(LogManager.GetLogger("ActivityManager"), activityLogRepo, UserManager));
@@ -775,7 +781,19 @@ namespace MediaBrowser.Server.Startup.Common
/// </summary>
protected override void FindParts()
{
- if (!ServerConfigurationManager.Configuration.IsPortAuthorized)
+ var isAuthorized = ServerConfigurationManager.Configuration.IsPortAuthorized;
+ if (isAuthorized)
+ {
+ try
+ {
+ isAuthorized = !NativeApp.PortsRequireAuthorization(ConfigurationManager.CommonApplicationPaths.ApplicationPath);
+ }
+ catch
+ {
+
+ }
+ }
+ if (!isAuthorized)
{
RegisterServerWithAdministratorAccess();
ServerConfigurationManager.Configuration.IsPortAuthorized = true;
@@ -948,7 +966,7 @@ namespace MediaBrowser.Server.Startup.Common
{
if (!CanSelfRestart)
{
- throw new InvalidOperationException("The server is unable to self-restart. Please restart manually.");
+ throw new PlatformNotSupportedException("The server is unable to self-restart. Please restart manually.");
}
try
@@ -1098,7 +1116,8 @@ namespace MediaBrowser.Server.Startup.Common
LocalAddress = localAddress,
SupportsLibraryMonitor = SupportsLibraryMonitor,
EncoderLocationType = MediaEncoder.EncoderLocationType,
- SystemArchitecture = NativeApp.Environment.SystemArchitecture
+ SystemArchitecture = NativeApp.Environment.SystemArchitecture,
+ SystemUpdateLevel = ConfigurationManager.CommonConfiguration.SystemUpdateLevel
};
}
@@ -1156,20 +1175,24 @@ namespace MediaBrowser.Server.Startup.Common
public async Task<List<IPAddress>> GetLocalIpAddresses()
{
- var localAddresses = NetworkManager.GetLocalIpAddresses()
- .Where(IsIpAddressValid)
- .ToList();
+ var addresses = NetworkManager.GetLocalIpAddresses().ToList();
+ var list = new List<IPAddress>();
- return localAddresses;
+ foreach (var address in addresses)
+ {
+ var valid = await IsIpAddressValidAsync(address).ConfigureAwait(false);
+ if (valid)
+ {
+ list.Add(address);
+ }
+ }
+
+ return list;
}
private readonly ConcurrentDictionary<string, bool> _validAddressResults = new ConcurrentDictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
private DateTime _lastAddressCacheClear;
- private bool IsIpAddressValid(IPAddress address)
- {
- return IsIpAddressValidInternal(address).Result;
- }
- private async Task<bool> IsIpAddressValidInternal(IPAddress address)
+ private async Task<bool> IsIpAddressValidAsync(IPAddress address)
{
if (IPAddress.IsLoopback(address))
{
@@ -1332,8 +1355,8 @@ namespace MediaBrowser.Server.Startup.Common
cacheLength = TimeSpan.FromMinutes(5);
}
- var result = await new GithubUpdater(HttpClient, JsonSerializer, cacheLength).CheckForUpdateResult("MediaBrowser", "Emby", ApplicationVersion, updateLevel, _releaseAssetFilename,
- "MBServer", "Mbserver.zip", cancellationToken).ConfigureAwait(false);
+ var result = await new GithubUpdater(HttpClient, JsonSerializer).CheckForUpdateResult("MediaBrowser", "Emby", ApplicationVersion, updateLevel, _releaseAssetFilename,
+ "MBServer", "Mbserver.zip", cacheLength, cancellationToken).ConfigureAwait(false);
HasUpdateAvailable = result.IsUpdateAvailable;
@@ -1394,5 +1417,10 @@ namespace MediaBrowser.Server.Startup.Common
{
NativeApp.LaunchUrl(url);
}
+
+ public void EnableLoopback(string appName)
+ {
+ NativeApp.EnableLoopback(appName);
+ }
}
}
diff --git a/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs b/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs
index 6b3602a73..1a0e2d973 100644
--- a/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs
+++ b/MediaBrowser.Server.Startup.Common/Browser/BrowserLauncher.cs
@@ -28,6 +28,11 @@ namespace MediaBrowser.Server.Startup.Common.Browser
OpenUrl(appHost, "http://emby.media/community");
}
+ public static void OpenEmbyPremiere(IServerApplicationHost appHost)
+ {
+ OpenDashboardPage("supporterkey.html", appHost);
+ }
+
/// <summary>
/// Opens the web client.
/// </summary>
diff --git a/MediaBrowser.Server.Startup.Common/INativeApp.cs b/MediaBrowser.Server.Startup.Common/INativeApp.cs
index c13d3624e..bf8314d13 100644
--- a/MediaBrowser.Server.Startup.Common/INativeApp.cs
+++ b/MediaBrowser.Server.Startup.Common/INativeApp.cs
@@ -2,7 +2,6 @@
using MediaBrowser.Model.Logging;
using System.Collections.Generic;
using System.Reflection;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
@@ -25,6 +24,8 @@ namespace MediaBrowser.Server.Startup.Common
/// <param name="tempDirectory">The temporary directory.</param>
void AuthorizeServer(int udpPort, int httpServerPort, int httpsServerPort, string applicationPath, string tempDirectory);
+ bool PortsRequireAuthorization(string applicationPath);
+
/// <summary>
/// Gets the environment.
/// </summary>
@@ -96,16 +97,12 @@ namespace MediaBrowser.Server.Startup.Common
void AllowSystemStandby();
- /// <summary>
- /// Gets the power management.
- /// </summary>
- /// <returns>IPowerManagement.</returns>
- IPowerManagement GetPowerManagement();
-
FFMpegInstallInfo GetFfmpegInstallInfo();
void LaunchUrl(string url);
IDbConnector GetDbConnector();
+
+ void EnableLoopback(string appName);
}
}
diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
index 5ee7d49e8..7eba89650 100644
--- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
+++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj
@@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Server.Startup.Common</RootNamespace>
<AssemblyName>MediaBrowser.Server.Startup.Common</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFrameworkVersion>v4.5.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -70,11 +71,9 @@
<Compile Include="FFMpeg\FFMpegInfo.cs" />
<Compile Include="INativeApp.cs" />
<Compile Include="MbLinkShortcutHandler.cs" />
- <Compile Include="Migrations\CollectionsViewMigration.cs" />
<Compile Include="Migrations\IVersionMigration.cs" />
<Compile Include="Migrations\DbMigration.cs" />
<Compile Include="Migrations\MovieDbEpisodeProviderMigration.cs" />
- <Compile Include="Migrations\OmdbEpisodeProviderMigration.cs" />
<Compile Include="Migrations\UpdateLevelMigration.cs" />
<Compile Include="NativeEnvironment.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs
deleted file mode 100644
index 3f68ec48b..000000000
--- a/MediaBrowser.Server.Startup.Common/Migrations/CollectionsViewMigration.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-using System.Linq;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Library;
-
-namespace MediaBrowser.Server.Startup.Common.Migrations
-{
- public class CollectionsViewMigration : IVersionMigration
- {
- private readonly IServerConfigurationManager _config;
- private readonly IUserManager _userManager;
-
- public CollectionsViewMigration(IServerConfigurationManager config, IUserManager userManager)
- {
- _config = config;
- _userManager = userManager;
- }
-
- public void Run()
- {
- var migrationKey = this.GetType().Name;
- var migrationKeyList = _config.Configuration.Migrations.ToList();
-
- if (!migrationKeyList.Contains(migrationKey))
- {
- if (_config.Configuration.IsStartupWizardCompleted)
- {
- if (_userManager.Users.Any(i => i.Configuration.DisplayCollectionsView))
- {
- _config.Configuration.DisplayCollectionsView = true;
- }
- }
-
- migrationKeyList.Add(migrationKey);
- _config.Configuration.Migrations = migrationKeyList.ToArray();
- _config.SaveConfiguration();
- }
-
- }
- }
-}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs
deleted file mode 100644
index ebc0e67de..000000000
--- a/MediaBrowser.Server.Startup.Common/Migrations/OmdbEpisodeProviderMigration.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using System.Linq;
-
-namespace MediaBrowser.Server.Startup.Common.Migrations
-{
- class OmdbEpisodeProviderMigration : IVersionMigration
- {
- private readonly IServerConfigurationManager _config;
- private const string _providerName = "The Open Movie Database";
-
- public OmdbEpisodeProviderMigration(IServerConfigurationManager config)
- {
- _config = config;
- }
-
- public void Run()
- {
- var migrationKey = this.GetType().FullName;
- var migrationKeyList = _config.Configuration.Migrations.ToList();
-
- if (!migrationKeyList.Contains(migrationKey))
- {
- foreach (var metaDataOption in _config.Configuration.MetadataOptions)
- {
- if (metaDataOption.ItemType == "Episode")
- {
- var disabledFetchers = metaDataOption.DisabledMetadataFetchers.ToList();
- if (!disabledFetchers.Contains(_providerName))
- {
- disabledFetchers.Add(_providerName);
- metaDataOption.DisabledMetadataFetchers = disabledFetchers.ToArray();
- }
- }
- }
-
- migrationKeyList.Add(migrationKey);
- _config.Configuration.Migrations = migrationKeyList.ToArray();
- _config.SaveConfiguration();
- }
-
- }
- }
-}
diff --git a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs
index ec00fb33d..4afd5bd34 100644
--- a/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs
+++ b/MediaBrowser.Server.Startup.Common/Migrations/UpdateLevelMigration.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Implementations.Updates;
@@ -41,12 +42,6 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
{
var updateLevel = _config.Configuration.SystemUpdateLevel;
- if (updateLevel == PackageVersionClass.Dev)
- {
- // It's already dev, there's nothing to check
- return;
- }
-
await CheckVersion(currentVersion, updateLevel, CancellationToken.None).ConfigureAwait(false);
}
catch
@@ -55,21 +50,39 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
}
}
- private async Task CheckVersion(Version currentVersion, PackageVersionClass updateLevel, CancellationToken cancellationToken)
+ private async Task CheckVersion(Version currentVersion, PackageVersionClass currentUpdateLevel, CancellationToken cancellationToken)
{
- var releases = await new GithubUpdater(_httpClient, _jsonSerializer, TimeSpan.FromMinutes(5))
+ var releases = await new GithubUpdater(_httpClient, _jsonSerializer)
.GetLatestReleases("MediaBrowser", "Emby", _releaseAssetFilename, cancellationToken).ConfigureAwait(false);
- var newUpdateLevel = updateLevel;
+ var newUpdateLevel = GetNewUpdateLevel(currentVersion, currentUpdateLevel, releases);
+
+ if (newUpdateLevel != currentUpdateLevel)
+ {
+ _config.Configuration.SystemUpdateLevel = newUpdateLevel;
+ _config.SaveConfiguration();
+ }
+ }
+
+ private PackageVersionClass GetNewUpdateLevel(Version currentVersion, PackageVersionClass currentUpdateLevel, List<GithubUpdater.RootObject> releases)
+ {
+ var newUpdateLevel = currentUpdateLevel;
// If the current version is later than current stable, set the update level to beta
if (releases.Count >= 1)
{
var release = releases[0];
var version = ParseVersion(release.tag_name);
- if (version != null && currentVersion > version)
+ if (version != null)
{
- newUpdateLevel = PackageVersionClass.Beta;
+ if (currentVersion > version)
+ {
+ newUpdateLevel = PackageVersionClass.Beta;
+ }
+ else
+ {
+ return PackageVersionClass.Release;
+ }
}
}
@@ -78,17 +91,20 @@ namespace MediaBrowser.Server.Startup.Common.Migrations
{
var release = releases[1];
var version = ParseVersion(release.tag_name);
- if (version != null && currentVersion > version)
+ if (version != null)
{
- newUpdateLevel = PackageVersionClass.Dev;
+ if (currentVersion > version)
+ {
+ newUpdateLevel = PackageVersionClass.Dev;
+ }
+ else
+ {
+ return PackageVersionClass.Beta;
+ }
}
}
- if (newUpdateLevel != updateLevel)
- {
- _config.Configuration.SystemUpdateLevel = newUpdateLevel;
- _config.SaveConfiguration();
- }
+ return newUpdateLevel;
}
private Version ParseVersion(string versionString)
diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs
index e4f5f3a69..90e688733 100644
--- a/MediaBrowser.ServerApplication/MainStartup.cs
+++ b/MediaBrowser.ServerApplication/MainStartup.cs
@@ -15,6 +15,7 @@ using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.ServiceProcess;
+using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
@@ -38,9 +39,31 @@ namespace MediaBrowser.ServerApplication
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetDllDirectory(string lpPathName);
+ public static bool TryGetLocalFromUncDirectory(string local, out string unc)
+ {
+ if ((local == null) || (local == ""))
+ {
+ unc = "";
+ throw new ArgumentNullException("local");
+ }
+
+ ManagementObjectSearcher searcher = new ManagementObjectSearcher("SELECT Name FROM Win32_share WHERE path ='" + local.Replace("\\", "\\\\") + "'");
+ ManagementObjectCollection coll = searcher.Get();
+ if (coll.Count == 1)
+ {
+ foreach (ManagementObject share in searcher.Get())
+ {
+ unc = share["Name"] as String;
+ unc = "\\\\" + SystemInformation.ComputerName + "\\" + unc;
+ return true;
+ }
+ }
+ unc = "";
+ return false;
+ }
/// <summary>
- /// Defines the entry point of the application.
- /// </summary>
+ /// Defines the entry point of the application.
+ /// </summary>
public static void Main()
{
var options = new StartupOptions();
@@ -312,7 +335,6 @@ namespace MediaBrowser.ServerApplication
ErrorModes.SEM_NOGPFAULTERRORBOX | ErrorModes.SEM_NOOPENFILEERRORBOX);
}
-
var task = _appHost.Init(initProgress);
Task.WaitAll(task);
@@ -352,7 +374,7 @@ namespace MediaBrowser.ServerApplication
Application.Run();
}
- private static SplashForm _splash;
+ internal static SplashForm _splash;
private static Thread _splashThread;
private static void ShowSplashScreen(Version appVersion, Progress<double> progress, ILogger logger)
{
diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
index 9c5470358..65b91e6f7 100644
--- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
+++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
@@ -119,10 +119,10 @@
<Compile Include="MainStartup.cs" />
<Compile Include="Native\LnkShortcutHandler.cs" />
<Compile Include="Native\DbConnector.cs" />
+ <Compile Include="Native\LoopbackUtil.cs" />
<Compile Include="Native\Standby.cs" />
<Compile Include="Native\ServerAuthorization.cs" />
<Compile Include="Native\WindowsApp.cs" />
- <Compile Include="Native\WindowsPowerManagement.cs" />
<Compile Include="Networking\CertificateGenerator.cs" />
<Compile Include="Networking\NativeMethods.cs" />
<Compile Include="Networking\NetworkManager.cs" />
diff --git a/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs b/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs
new file mode 100644
index 000000000..5b260685b
--- /dev/null
+++ b/MediaBrowser.ServerApplication/Native/LoopbackUtil.cs
@@ -0,0 +1,358 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.ServerApplication.Native
+{
+ /// <summary>
+ /// http://blogs.msdn.com/b/fiddler/archive/2011/12/10/fiddler-windows-8-apps-enable-LoopUtil-network-isolation-exemption.aspx
+ /// </summary>
+ public class LoopUtil
+ {
+ //http://msdn.microsoft.com/en-us/library/windows/desktop/aa379595(v=vs.85).aspx
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct SID_AND_ATTRIBUTES
+ {
+ public IntPtr Sid;
+ public uint Attributes;
+ }
+
+ [StructLayoutAttribute(LayoutKind.Sequential)]
+ internal struct INET_FIREWALL_AC_CAPABILITIES
+ {
+ public uint count;
+ public IntPtr capabilities; //SID_AND_ATTRIBUTES
+ }
+
+ [StructLayoutAttribute(LayoutKind.Sequential)]
+ internal struct INET_FIREWALL_AC_BINARIES
+ {
+ public uint count;
+ public IntPtr binaries;
+ }
+
+ [StructLayoutAttribute(LayoutKind.Sequential)]
+ internal struct INET_FIREWALL_APP_CONTAINER
+ {
+ internal IntPtr appContainerSid;
+ internal IntPtr userSid;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string appContainerName;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string displayName;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string description;
+ internal INET_FIREWALL_AC_CAPABILITIES capabilities;
+ internal INET_FIREWALL_AC_BINARIES binaries;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string workingDirectory;
+ [MarshalAs(UnmanagedType.LPWStr)]
+ public string packageFullName;
+ }
+
+
+ // Call this API to free the memory returned by the Enumeration API
+ [DllImport("FirewallAPI.dll")]
+ internal static extern void NetworkIsolationFreeAppContainers(IntPtr pACs);
+
+ // Call this API to load the current list of LoopUtil-enabled AppContainers
+ [DllImport("FirewallAPI.dll")]
+ internal static extern uint NetworkIsolationGetAppContainerConfig(out uint pdwCntACs, out IntPtr appContainerSids);
+
+ // Call this API to set the LoopUtil-exemption list
+ [DllImport("FirewallAPI.dll")]
+ private static extern uint NetworkIsolationSetAppContainerConfig(uint pdwCntACs, SID_AND_ATTRIBUTES[] appContainerSids);
+
+
+ // Use this API to convert a string SID into an actual SID
+ [DllImport("advapi32.dll", SetLastError = true)]
+ internal static extern bool ConvertStringSidToSid(string strSid, out IntPtr pSid);
+
+ [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern bool ConvertSidToStringSid(
+ [MarshalAs(UnmanagedType.LPArray)] byte[] pSID,
+ out IntPtr ptrSid);
+
+ [DllImport("advapi32", CharSet = CharSet.Auto, SetLastError = true)]
+ static extern bool ConvertSidToStringSid(IntPtr pSid, out string strSid);
+
+ // Use this API to convert a string reference (e.g. "@{blah.pri?ms-resource://whatever}") into a plain string
+ [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ internal static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf);
+
+ // Call this API to enumerate all of the AppContainers on the system
+ [DllImport("FirewallAPI.dll")]
+ internal static extern uint NetworkIsolationEnumAppContainers(uint Flags, out uint pdwCntPublicACs, out IntPtr ppACs);
+ // DWORD NetworkIsolationEnumAppContainers(
+ // _In_ DWORD Flags,
+ // _Out_ DWORD *pdwNumPublicAppCs,
+ // _Out_ PINET_FIREWALL_APP_CONTAINER *ppPublicAppCs
+ //);
+
+ //http://msdn.microsoft.com/en-gb/library/windows/desktop/hh968116.aspx
+ enum NETISO_FLAG
+ {
+ NETISO_FLAG_FORCE_COMPUTE_BINARIES = 0x1,
+ NETISO_FLAG_MAX = 0x2
+ }
+
+
+ public class AppContainer
+ {
+ public String appContainerName { get; set; }
+ public String displayName { get; set; }
+ public String workingDirectory { get; set; }
+ public String StringSid { get; set; }
+ public List<uint> capabilities { get; set; }
+ public bool LoopUtil { get; set; }
+
+ public AppContainer(String _appContainerName, String _displayName, String _workingDirectory, IntPtr _sid)
+ {
+ this.appContainerName = _appContainerName;
+ this.displayName = _displayName;
+ this.workingDirectory = _workingDirectory;
+ String tempSid;
+ ConvertSidToStringSid(_sid, out tempSid);
+ this.StringSid = tempSid;
+ }
+ }
+
+ internal List<LoopUtil.INET_FIREWALL_APP_CONTAINER> _AppList;
+ internal List<LoopUtil.SID_AND_ATTRIBUTES> _AppListConfig;
+ public List<AppContainer> Apps = new List<AppContainer>();
+ internal IntPtr _pACs;
+
+ public LoopUtil()
+ {
+ LoadApps();
+ }
+
+ public void LoadApps()
+ {
+ Apps.Clear();
+ _pACs = IntPtr.Zero;
+ //Full List of Apps
+ _AppList = PI_NetworkIsolationEnumAppContainers();
+ //List of Apps that have LoopUtil enabled.
+ _AppListConfig = PI_NetworkIsolationGetAppContainerConfig();
+ foreach (var PI_app in _AppList)
+ {
+ AppContainer app = new AppContainer(PI_app.appContainerName, PI_app.displayName, PI_app.workingDirectory, PI_app.appContainerSid);
+
+ var app_capabilities = LoopUtil.getCapabilites(PI_app.capabilities);
+ if (app_capabilities.Count > 0)
+ {
+ //var sid = new SecurityIdentifier(app_capabilities[0], 0);
+
+ IntPtr arrayValue = IntPtr.Zero;
+ //var b = LoopUtil.ConvertStringSidToSid(app_capabilities[0].Sid, out arrayValue);
+ //string mysid;
+ //var b = LoopUtil.ConvertSidToStringSid(app_capabilities[0].Sid, out mysid);
+ }
+ app.LoopUtil = CheckLoopback(PI_app.appContainerSid);
+ Apps.Add(app);
+ }
+ }
+ private bool CheckLoopback(IntPtr intPtr)
+ {
+ foreach (SID_AND_ATTRIBUTES item in _AppListConfig)
+ {
+ string left, right;
+ ConvertSidToStringSid(item.Sid, out left);
+ ConvertSidToStringSid(intPtr, out right);
+
+ if (left == right)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private bool CreateExcemptions(string appName)
+ {
+ var hasChanges = false;
+
+ foreach (var app in Apps)
+ {
+ if ((app.appContainerName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1 ||
+ (app.displayName ?? string.Empty).IndexOf(appName, StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ if (!app.LoopUtil)
+ {
+ app.LoopUtil = true;
+ hasChanges = true;
+ }
+ }
+ }
+
+ return hasChanges;
+ }
+
+ public static void Run(string appName)
+ {
+ var util = new LoopUtil();
+ util.LoadApps();
+
+ var hasChanges = util.CreateExcemptions(appName);
+
+ if (hasChanges)
+ {
+ util.SaveLoopbackState();
+ }
+ util.SaveLoopbackState();
+ }
+
+ private static List<SID_AND_ATTRIBUTES> getCapabilites(INET_FIREWALL_AC_CAPABILITIES cap)
+ {
+ List<SID_AND_ATTRIBUTES> mycap = new List<SID_AND_ATTRIBUTES>();
+
+ IntPtr arrayValue = cap.capabilities;
+
+ var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
+ for (var i = 0; i < cap.count; i++)
+ {
+ var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES));
+ mycap.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ return mycap;
+
+ }
+
+ private static List<SID_AND_ATTRIBUTES> getContainerSID(INET_FIREWALL_AC_CAPABILITIES cap)
+ {
+ List<SID_AND_ATTRIBUTES> mycap = new List<SID_AND_ATTRIBUTES>();
+
+ IntPtr arrayValue = cap.capabilities;
+
+ var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
+ for (var i = 0; i < cap.count; i++)
+ {
+ var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES));
+ mycap.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ return mycap;
+
+ }
+
+ private static List<SID_AND_ATTRIBUTES> PI_NetworkIsolationGetAppContainerConfig()
+ {
+
+ IntPtr arrayValue = IntPtr.Zero;
+ uint size = 0;
+ var list = new List<SID_AND_ATTRIBUTES>();
+
+ // Pin down variables
+ GCHandle handle_pdwCntPublicACs = GCHandle.Alloc(size, GCHandleType.Pinned);
+ GCHandle handle_ppACs = GCHandle.Alloc(arrayValue, GCHandleType.Pinned);
+
+ uint retval = NetworkIsolationGetAppContainerConfig(out size, out arrayValue);
+
+ var structSize = Marshal.SizeOf(typeof(SID_AND_ATTRIBUTES));
+ for (var i = 0; i < size; i++)
+ {
+ var cur = (SID_AND_ATTRIBUTES)Marshal.PtrToStructure(arrayValue, typeof(SID_AND_ATTRIBUTES));
+ list.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ //release pinned variables.
+ handle_pdwCntPublicACs.Free();
+ handle_ppACs.Free();
+
+ return list;
+
+
+ }
+
+ private List<INET_FIREWALL_APP_CONTAINER> PI_NetworkIsolationEnumAppContainers()
+ {
+
+ IntPtr arrayValue = IntPtr.Zero;
+ uint size = 0;
+ var list = new List<INET_FIREWALL_APP_CONTAINER>();
+
+ // Pin down variables
+ GCHandle handle_pdwCntPublicACs = GCHandle.Alloc(size, GCHandleType.Pinned);
+ GCHandle handle_ppACs = GCHandle.Alloc(arrayValue, GCHandleType.Pinned);
+
+ //uint retval2 = NetworkIsolationGetAppContainerConfig( out size, out arrayValue);
+
+ uint retval = NetworkIsolationEnumAppContainers((Int32)NETISO_FLAG.NETISO_FLAG_MAX, out size, out arrayValue);
+ _pACs = arrayValue; //store the pointer so it can be freed when we close the form
+
+ var structSize = Marshal.SizeOf(typeof(INET_FIREWALL_APP_CONTAINER));
+ for (var i = 0; i < size; i++)
+ {
+ var cur = (INET_FIREWALL_APP_CONTAINER)Marshal.PtrToStructure(arrayValue, typeof(INET_FIREWALL_APP_CONTAINER));
+ list.Add(cur);
+ arrayValue = new IntPtr((long)(arrayValue) + (long)(structSize));
+ }
+
+ //release pinned variables.
+ handle_pdwCntPublicACs.Free();
+ handle_ppACs.Free();
+
+ return list;
+
+
+ }
+
+ public bool SaveLoopbackState()
+ {
+ var countEnabled = CountEnabledLoopUtil();
+ SID_AND_ATTRIBUTES[] arr = new SID_AND_ATTRIBUTES[countEnabled];
+ int count = 0;
+
+ for (int i = 0; i < Apps.Count; i++)
+ {
+ if (Apps[i].LoopUtil)
+ {
+ arr[count].Attributes = 0;
+ //TO DO:
+ IntPtr ptr;
+ ConvertStringSidToSid(Apps[i].StringSid, out ptr);
+ arr[count].Sid = ptr;
+ count++;
+ }
+
+ }
+
+
+ if (NetworkIsolationSetAppContainerConfig((uint)countEnabled, arr) == 0)
+ {
+ return true;
+ }
+ else
+ { return false; }
+
+ }
+
+ private int CountEnabledLoopUtil()
+ {
+ var count = 0;
+ for (int i = 0; i < Apps.Count; i++)
+ {
+ if (Apps[i].LoopUtil)
+ {
+ count++;
+ }
+
+ }
+ return count;
+ }
+
+ public void FreeResources()
+ {
+ NetworkIsolationFreeAppContainers(_pACs);
+ }
+
+ }
+}
diff --git a/MediaBrowser.ServerApplication/Native/RegisterServer.bat b/MediaBrowser.ServerApplication/Native/RegisterServer.bat
index 27f863d58..85baa0d03 100644
--- a/MediaBrowser.ServerApplication/Native/RegisterServer.bat
+++ b/MediaBrowser.ServerApplication/Native/RegisterServer.bat
@@ -20,7 +20,9 @@ netsh advfirewall firewall add rule name="Port %3" dir=in action=allow protocol=
if [%4]==[] GOTO DONE
+netsh advfirewall firewall delete rule name="mediabrowser.serverapplication.exe"
netsh advfirewall firewall delete rule name="Emby Server"
+
netsh advfirewall firewall add rule name="Emby Server" dir=in action=allow protocol=TCP program=%4 enable=yes
netsh advfirewall firewall add rule name="Emby Server" dir=in action=allow protocol=UDP program=%4 enable=yes
diff --git a/MediaBrowser.ServerApplication/Native/WindowsApp.cs b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
index 3c9c04acb..1e50ac85e 100644
--- a/MediaBrowser.ServerApplication/Native/WindowsApp.cs
+++ b/MediaBrowser.ServerApplication/Native/WindowsApp.cs
@@ -7,8 +7,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
+using System.Windows.Forms;
using CommonIO;
-using MediaBrowser.Controller.Power;
using MediaBrowser.Model.System;
using MediaBrowser.Server.Implementations.Persistence;
using MediaBrowser.Server.Startup.Common.FFMpeg;
@@ -147,11 +147,6 @@ namespace MediaBrowser.ServerApplication.Native
MainStartup.Invoke(Standby.AllowSleep);
}
- public IPowerManagement GetPowerManagement()
- {
- return new WindowsPowerManagement(_logger);
- }
-
public FFMpegInstallInfo GetFfmpegInstallInfo()
{
var info = new FFMpegInstallInfo();
@@ -203,5 +198,76 @@ namespace MediaBrowser.ServerApplication.Native
{
((Process)sender).Dispose();
}
+
+ public void EnableLoopback(string appName)
+ {
+ LoopUtil.Run(appName);
+ }
+
+ private bool Confirm()
+ {
+ if (MainStartup._splash == null)
+ {
+ return false;
+ }
+
+ return MessageBox.Show(MainStartup._splash, "Emby has detected that a rule has been added to Windows Firewall that may prevent your other devices from accessing Emby Server. Click OK to remove this rule, or cancel to proceed anyway.", "Windows Firewall", MessageBoxButtons.OKCancel) == DialogResult.OK;
+ }
+
+ public bool PortsRequireAuthorization(string applicationPath)
+ {
+ var appNameSrch = Path.GetFileName(applicationPath);
+
+ var startInfo = new ProcessStartInfo
+ {
+ FileName = "netsh",
+
+ Arguments = "advfirewall firewall show rule \"" + appNameSrch + "\"",
+
+ CreateNoWindow = true,
+ UseShellExecute = false,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ ErrorDialog = false,
+ RedirectStandardOutput = true
+ };
+
+ using (var process = Process.Start(startInfo))
+ {
+ process.Start();
+
+ try
+ {
+ var data = process.StandardOutput.ReadToEnd() ?? string.Empty;
+
+ if (data.IndexOf("Block", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ _logger.Info("Found potential windows firewall rule blocking Emby Server: " + data);
+ return Confirm();
+ }
+
+ //var parts = data.Split('\n');
+
+ //return parts.Length > 4;
+ //return Confirm();
+ return false;
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error querying windows firewall", ex);
+
+ // Hate having to do this
+ try
+ {
+ process.Kill();
+ }
+ catch (Exception ex1)
+ {
+ _logger.ErrorException("Error killing process", ex1);
+ }
+
+ throw;
+ }
+ }
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs b/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs
deleted file mode 100644
index 866272639..000000000
--- a/MediaBrowser.ServerApplication/Native/WindowsPowerManagement.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System;
-using System.ComponentModel;
-using System.Runtime.InteropServices;
-using System.Threading;
-using MediaBrowser.Controller.Power;
-using MediaBrowser.Model.Logging;
-using Microsoft.Win32.SafeHandles;
-
-namespace MediaBrowser.ServerApplication.Native
-{
- public class WindowsPowerManagement : IPowerManagement
- {
- [DllImport("kernel32.dll")]
- public static extern SafeWaitHandle CreateWaitableTimer(IntPtr lpTimerAttributes,
- bool bManualReset,
- string lpTimerName);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool SetWaitableTimer(SafeWaitHandle hTimer,
- [In] ref long pDueTime,
- int lPeriod,
- IntPtr pfnCompletionRoutine,
- IntPtr lpArgToCompletionRoutine,
- bool fResume);
-
- private BackgroundWorker _bgWorker;
- private readonly ILogger _logger;
- private readonly object _initLock = new object();
-
- public WindowsPowerManagement(ILogger logger)
- {
- _logger = logger;
- }
-
- public void ScheduleWake(DateTime utcTime)
- {
- //Initialize();
- //_bgWorker.RunWorkerAsync(utcTime.ToFileTime());
- throw new NotImplementedException();
- }
-
- private void Initialize()
- {
- lock (_initLock)
- {
- if (_bgWorker == null)
- {
- _bgWorker = new BackgroundWorker();
-
- _bgWorker.DoWork += bgWorker_DoWork;
- _bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
- }
- }
- }
-
- void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
- {
- //if (Woken != null)
- //{
- // Woken(this, new EventArgs());
- //}
- }
-
- private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
- {
- try
- {
- long waketime = (long)e.Argument;
-
- using (SafeWaitHandle handle = CreateWaitableTimer(IntPtr.Zero, true, GetType().Assembly.GetName().Name + "Timer"))
- {
- if (SetWaitableTimer(handle, ref waketime, 0, IntPtr.Zero, IntPtr.Zero, true))
- {
- using (EventWaitHandle wh = new EventWaitHandle(false,
- EventResetMode.AutoReset))
- {
- wh.SafeWaitHandle = handle;
- wh.WaitOne();
- }
- }
- else
- {
- throw new Win32Exception(Marshal.GetLastWin32Error());
- }
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error scheduling wake timer", ex);
- }
- }
- }
-}
diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
index d04128a18..7805c0d68 100644
--- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
+++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs
@@ -20,6 +20,7 @@ namespace MediaBrowser.ServerApplication
private ToolStripMenuItem cmdRestart;
private ToolStripSeparator toolStripSeparator1;
private ToolStripMenuItem cmdCommunity;
+ private ToolStripMenuItem cmdPremiere;
private Container components;
private readonly ILogger _logger;
@@ -50,6 +51,7 @@ namespace MediaBrowser.ServerApplication
cmdExit = new ToolStripMenuItem();
cmdCommunity = new ToolStripMenuItem();
+ cmdPremiere = new ToolStripMenuItem();
toolStripSeparator1 = new ToolStripSeparator();
cmdRestart = new ToolStripMenuItem();
toolStripSeparator2 = new ToolStripSeparator();
@@ -69,6 +71,7 @@ namespace MediaBrowser.ServerApplication
contextMenuStrip1.Items.AddRange(new ToolStripItem[] {
cmdBrowse,
cmdConfigure,
+ cmdPremiere,
toolStripSeparator2,
cmdRestart,
toolStripSeparator1,
@@ -89,6 +92,11 @@ namespace MediaBrowser.ServerApplication
cmdCommunity.Name = "cmdCommunity";
cmdCommunity.Size = new System.Drawing.Size(208, 22);
//
+ // cmdPremiere
+ //
+ cmdPremiere.Name = "cmdPremiere";
+ cmdPremiere.Size = new System.Drawing.Size(208, 22);
+ //
// toolStripSeparator1
//
toolStripSeparator1.Name = "toolStripSeparator1";
@@ -118,6 +126,7 @@ namespace MediaBrowser.ServerApplication
cmdRestart.Click += cmdRestart_Click;
cmdConfigure.Click += cmdConfigure_Click;
cmdCommunity.Click += cmdCommunity_Click;
+ cmdPremiere.Click += cmdPremiere_Click;
cmdBrowse.Click += cmdBrowse_Click;
_configurationManager.ConfigurationUpdated += Instance_ConfigurationUpdated;
@@ -138,6 +147,7 @@ namespace MediaBrowser.ServerApplication
cmdExit.Text = _localization.GetLocalizedString("LabelExit");
cmdCommunity.Text = _localization.GetLocalizedString("LabelVisitCommunity");
+ cmdPremiere.Text = _localization.GetLocalizedString("Emby Premiere");
cmdBrowse.Text = _localization.GetLocalizedString("LabelBrowseLibrary");
cmdConfigure.Text = _localization.GetLocalizedString("LabelConfigureServer");
cmdRestart.Text = _localization.GetLocalizedString("LabelRestartServer");
@@ -163,6 +173,11 @@ namespace MediaBrowser.ServerApplication
BrowserLauncher.OpenWebClient(_appHost);
}
+ void cmdPremiere_Click(object sender, EventArgs e)
+ {
+ BrowserLauncher.OpenEmbyPremiere(_appHost);
+ }
+
void cmdCommunity_Click(object sender, EventArgs e)
{
BrowserLauncher.OpenCommunity(_appHost);
diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs
index aec4632ae..2f8b34879 100644
--- a/MediaBrowser.WebDashboard/Api/DashboardService.cs
+++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs
@@ -58,11 +58,6 @@ namespace MediaBrowser.WebDashboard.Api
{
}
- [Route("/web/staticfiles", "GET")]
- public class GetCacheFiles
- {
- }
-
/// <summary>
/// Class GetDashboardResource
/// </summary>
@@ -145,37 +140,6 @@ namespace MediaBrowser.WebDashboard.Api
return ResultFactory.GetStaticResult(Request, page.Plugin.Version.ToString().GetMD5(), null, null, MimeTypes.GetMimeType("page.html"), () => GetPackageCreator().ModifyHtml("dummy.html", page.GetHtmlStream(), null, _appHost.ApplicationVersion.ToString(), null, false));
}
- public object Get(GetCacheFiles request)
- {
- var allFiles = GetCacheFileList();
-
- return ResultFactory.GetOptimizedResult(Request, _jsonSerializer.SerializeToString(allFiles));
- }
-
- private List<string> GetCacheFileList()
- {
- var creator = GetPackageCreator();
- var directory = creator.DashboardUIPath;
-
- var skipExtensions = GetDeployIgnoreExtensions();
- var skipNames = GetDeployIgnoreFilenames();
-
- return
- Directory.GetFiles(directory, "*", SearchOption.AllDirectories)
- .Where(i => !skipExtensions.Contains(Path.GetExtension(i) ?? string.Empty, StringComparer.OrdinalIgnoreCase))
- .Where(i => !skipNames.Any(s =>
- {
- if (s.Item2)
- {
- return string.Equals(s.Item1, Path.GetFileName(i), StringComparison.OrdinalIgnoreCase);
- }
-
- return (Path.GetFileName(i) ?? string.Empty).IndexOf(s.Item1, StringComparison.OrdinalIgnoreCase) != -1;
- }))
- .Select(i => i.Replace(directory, string.Empty, StringComparison.OrdinalIgnoreCase).Replace("\\", "/").TrimStart('/') + "?v=" + _appHost.ApplicationVersion.ToString())
- .ToList();
- }
-
/// <summary>
/// Gets the specified request.
/// </summary>
@@ -369,8 +333,6 @@ namespace MediaBrowser.WebDashboard.Api
var appVersion = _appHost.ApplicationVersion.ToString();
- File.WriteAllText(Path.Combine(path, "staticfiles"), _jsonSerializer.SerializeToString(GetCacheFileList()));
-
var mode = request.Mode;
// Try to trim the output size a bit
diff --git a/MediaBrowser.WebDashboard/Api/PackageCreator.cs b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
index 65e7fa5d3..02e8ad6f2 100644
--- a/MediaBrowser.WebDashboard/Api/PackageCreator.cs
+++ b/MediaBrowser.WebDashboard/Api/PackageCreator.cs
@@ -346,11 +346,14 @@ namespace MediaBrowser.WebDashboard.Api
if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase))
{
- sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'unsafe-inline' 'unsafe-eval' data: filesystem:;\">");
+ sb.Append("<meta http-equiv=\"Content-Security-Policy\" content=\"default-src * 'self' 'unsafe-inline' 'unsafe-eval' data: gap: file: filesystem:;\">");
+ }
+ else
+ {
+ sb.Append("<meta http-equiv=\"X-UA-Compatibility\" content=\"IE=Edge\">");
}
sb.Append("<link rel=\"manifest\" href=\"manifest.json\">");
- sb.Append("<meta http-equiv=\"X-UA-Compatibility\" content=\"IE=Edge\">");
sb.Append("<meta name=\"format-detection\" content=\"telephone=no\">");
sb.Append("<meta name=\"msapplication-tap-highlight\" content=\"no\">");
sb.Append("<meta name=\"viewport\" content=\"user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width\">");
diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
index 5b66c27f4..eebc0f20e 100644
--- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
+++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
@@ -191,6 +191,9 @@
<Content Include="dashboard-ui\scripts\camerauploadsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\livetvschedule.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\userpasswordpage.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -281,9 +284,6 @@
<Content Include="dashboard-ui\css\nowplayingbar.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\imageeditor\imageeditor.template.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\favorites.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -437,9 +437,6 @@
<Content Include="dashboard-ui\scripts\sections.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.css">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.collapsible.css">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -470,9 +467,6 @@
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.slider.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.checkbox.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\thirdparty\jquerymobile-1.4.5\jqm.widget.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -755,9 +749,6 @@
<Content Include="dashboard-ui\reports.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\livetvrecordinglist.html">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\livetvseriestimer.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -944,9 +935,6 @@
<Content Include="dashboard-ui\scripts\livetvchannel.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\scripts\livetvrecordinglist.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\livetvseriestimer.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -1086,9 +1074,6 @@
<Content Include="dashboard-ui\music.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\components\imageeditor\imageeditor.js">
- <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
- </Content>
<Content Include="dashboard-ui\scripts\edititemmetadata.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index f777ae16b..1f222e75c 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -25,19 +25,22 @@ namespace MediaBrowser.XbmcMetadata.Parsers
/// The logger
/// </summary>
protected ILogger Logger { get; private set; }
+ protected IProviderManager ProviderManager { get; private set; }
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
private readonly IConfigurationManager _config;
+ private Dictionary<string, string> _validProviderIds;
/// <summary>
/// Initializes a new instance of the <see cref="BaseNfoParser{T}" /> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="config">The configuration.</param>
- public BaseNfoParser(ILogger logger, IConfigurationManager config)
+ public BaseNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager)
{
Logger = logger;
_config = config;
+ ProviderManager = providerManager;
}
/// <summary>
@@ -68,6 +71,24 @@ namespace MediaBrowser.XbmcMetadata.Parsers
ValidationType = ValidationType.None
};
+ _validProviderIds = _validProviderIds = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
+
+ var idInfos = ProviderManager.GetExternalIdInfos(item.Item);
+
+ foreach (var info in idInfos)
+ {
+ var id = info.Key + "Id";
+ if (!_validProviderIds.ContainsKey(id))
+ {
+ _validProviderIds.Add(id, info.Key);
+ }
+ }
+
+ //Additional Mappings
+ _validProviderIds.Add("collectionnumber", "TmdbCollection");
+ _validProviderIds.Add("tmdbcolid", "TmdbCollection");
+ _validProviderIds.Add("imdb_id", "Imdb");
+
Fetch(item, metadataFile, settings, cancellationToken);
}
@@ -760,14 +781,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
break;
}
- case "tvdbid":
- var tvdbId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(tvdbId))
- {
- item.SetProviderId(MetadataProviders.Tvdb, tvdbId);
- }
- break;
-
case "votes":
{
var val = reader.ReadElementContentAsString();
@@ -782,127 +795,6 @@ namespace MediaBrowser.XbmcMetadata.Parsers
}
break;
}
- case "musicbrainzalbumid":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzAlbum, mbz);
- }
- break;
- }
- case "musicbrainzalbumartistid":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzAlbumArtist, mbz);
- }
- break;
- }
- case "musicbrainzartistid":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzArtist, mbz);
- }
- break;
- }
- case "musicbrainzreleasegroupid":
- {
- var mbz = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(mbz))
- {
- item.SetProviderId(MetadataProviders.MusicBrainzReleaseGroup, mbz);
- }
- break;
- }
- case "tvrageid":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.TvRage, id);
- }
- break;
- }
- case "tvmazeid":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.TvMaze, id);
- }
- break;
- }
- case "audiodbartistid":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.AudioDbArtist, id);
- }
- break;
- }
- case "audiodbalbumid":
- {
- var id = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(id))
- {
- item.SetProviderId(MetadataProviders.AudioDbAlbum, id);
- }
- break;
- }
- case "rottentomatoesid":
- var rtId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(rtId))
- {
- item.SetProviderId(MetadataProviders.RottenTomatoes, rtId);
- }
- break;
-
- case "tmdbid":
- var tmdb = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(tmdb))
- {
- item.SetProviderId(MetadataProviders.Tmdb, tmdb);
- }
- break;
-
- case "collectionnumber":
- case "tmdbcolid":
- var tmdbCollection = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(tmdbCollection))
- {
- item.SetProviderId(MetadataProviders.TmdbCollection, tmdbCollection);
- }
- break;
-
- case "tvcomid":
- var TVcomId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(TVcomId))
- {
- item.SetProviderId(MetadataProviders.Tvcom, TVcomId);
- }
- break;
-
- case "zap2itid":
- var zap2ItId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(zap2ItId))
- {
- item.SetProviderId(MetadataProviders.Zap2It, zap2ItId);
- }
- break;
-
- case "imdb_id":
- case "imdbid":
- var imDbId = reader.ReadElementContentAsString();
- if (!string.IsNullOrWhiteSpace(imDbId))
- {
- item.SetProviderId(MetadataProviders.Imdb, imDbId);
- }
- break;
case "genre":
{
diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
index dfa5c1b71..a5a86fc58 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs
@@ -12,7 +12,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
public class EpisodeNfoParser : BaseNfoParser<Episode>
{
- public EpisodeNfoParser(ILogger logger, IConfigurationManager config) : base(logger, config)
+ public EpisodeNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(logger, config, providerManager)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
index dfe88cd3f..3e6196238 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs
@@ -10,8 +10,8 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
class MovieNfoParser : BaseNfoParser<Video>
{
- public MovieNfoParser(ILogger logger, IConfigurationManager config)
- : base(logger, config)
+ public MovieNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager)
+ : base(logger, config, providerManager)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
index 1164040fd..c051ad4f8 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeasonNfoParser.cs
@@ -9,7 +9,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
public class SeasonNfoParser : BaseNfoParser<Season>
{
- public SeasonNfoParser(ILogger logger, IConfigurationManager config) : base(logger, config)
+ public SeasonNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(logger, config, providerManager)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
index 4dd2e1759..8b7deebf2 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
{
public class SeriesNfoParser : BaseNfoParser<Series>
{
- public SeriesNfoParser(ILogger logger, IConfigurationManager config) : base(logger, config)
+ public SeriesNfoParser(ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(logger, config, providerManager)
{
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
index d0b1b142c..a90cb20fd 100644
--- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs
@@ -13,17 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
+ private readonly IProviderManager _providerManager;
- public AlbumNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config)
+ public AlbumNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
_config = config;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<MusicAlbum> result, string path, CancellationToken cancellationToken)
{
- new BaseNfoParser<MusicAlbum>(_logger, _config).Fetch(result, path, cancellationToken);
+ new BaseNfoParser<MusicAlbum>(_logger, _config, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
index 81c6b1c28..391eb459a 100644
--- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs
@@ -13,17 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
+ private readonly IProviderManager _providerManager;
- public ArtistNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config)
+ public ArtistNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
_config = config;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<MusicArtist> result, string path, CancellationToken cancellationToken)
{
- new BaseNfoParser<MusicArtist>(_logger, _config).Fetch(result, path, cancellationToken);
+ new BaseNfoParser<MusicArtist>(_logger, _config, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
index 26ffc9a19..0a268e8e2 100644
--- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs
@@ -15,12 +15,14 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
+ private readonly IProviderManager _providerManager;
- public BaseVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config)
+ public BaseVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
_config = config;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<T> result, string path, CancellationToken cancellationToken)
@@ -29,7 +31,7 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
Item = result.Item
};
- new MovieNfoParser(_logger, _config).Fetch(tmpItem, path, cancellationToken);
+ new MovieNfoParser(_logger, _config, _providerManager).Fetch(tmpItem, path, cancellationToken);
result.Item = (T)tmpItem.Item;
result.People = tmpItem.People;
diff --git a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
index 5587c7515..d8f3f1a21 100644
--- a/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/EpisodeNfoProvider.cs
@@ -14,19 +14,21 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
+ private readonly IProviderManager _providerManager;
- public EpisodeNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config)
+ public EpisodeNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
_config = config;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Episode> result, string path, CancellationToken cancellationToken)
{
var images = new List<LocalImageInfo>();
- new EpisodeNfoParser(_logger, _config).Fetch(result, images, path, cancellationToken);
+ new EpisodeNfoParser(_logger, _config, _providerManager).Fetch(result, images, path, cancellationToken);
result.Images = images;
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
index bba4010dc..5a759f81e 100644
--- a/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/MovieNfoProvider.cs
@@ -2,27 +2,28 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Logging;
namespace MediaBrowser.XbmcMetadata.Providers
{
public class MovieNfoProvider : BaseVideoNfoProvider<Movie>
{
- public MovieNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config) : base(fileSystem, logger, config)
+ public MovieNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem, logger, config, providerManager)
{
}
}
public class MusicVideoNfoProvider : BaseVideoNfoProvider<MusicVideo>
{
- public MusicVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config) : base(fileSystem, logger, config)
+ public MusicVideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem, logger, config, providerManager)
{
}
}
public class VideoNfoProvider : BaseVideoNfoProvider<Video>
{
- public VideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config) : base(fileSystem, logger, config)
+ public VideoNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager) : base(fileSystem, logger, config, providerManager)
{
}
}
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
index 3dac0a531..7e116b2c5 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeasonNfoProvider.cs
@@ -13,17 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
+ private readonly IProviderManager _providerManager;
- public SeasonNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config)
+ public SeasonNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
_config = config;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Season> result, string path, CancellationToken cancellationToken)
{
- new SeasonNfoParser(_logger, _config).Fetch(result, path, cancellationToken);
+ new SeasonNfoParser(_logger, _config, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
index 4bab8d75b..f03c20954 100644
--- a/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
+++ b/MediaBrowser.XbmcMetadata/Providers/SeriesNfoProvider.cs
@@ -13,17 +13,19 @@ namespace MediaBrowser.XbmcMetadata.Providers
{
private readonly ILogger _logger;
private readonly IConfigurationManager _config;
+ private readonly IProviderManager _providerManager;
- public SeriesNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config)
+ public SeriesNfoProvider(IFileSystem fileSystem, ILogger logger, IConfigurationManager config, IProviderManager providerManager)
: base(fileSystem)
{
_logger = logger;
_config = config;
+ _providerManager = providerManager;
}
protected override void Fetch(MetadataResult<Series> result, string path, CancellationToken cancellationToken)
{
- new SeriesNfoParser(_logger, _config).Fetch(result, path, cancellationToken);
+ new SeriesNfoParser(_logger, _config, _providerManager).Fetch(result, path, cancellationToken);
}
protected override FileSystemMetadata GetXmlFile(ItemInfo info, IDirectoryService directoryService)
diff --git a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
index c28a15a93..dc208d495 100644
--- a/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/BaseNfoSaver.cs
@@ -27,8 +27,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
- private static readonly Dictionary<string, string> CommonTags = new[] {
-
+ private static readonly Dictionary<string, string> CommonTags = new[] {
+
"plot",
"customrating",
"lockdata",
@@ -428,6 +428,8 @@ namespace MediaBrowser.XbmcMetadata.Savers
/// <returns>Task.</returns>
public static void AddCommonNodes(BaseItem item, XmlWriter writer, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepo, IFileSystem fileSystem, IServerConfigurationManager config)
{
+ var writtenProviderIds = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
+
var overview = (item.Overview ?? string.Empty)
.StripHtml()
.Replace("&quot;", "'");
@@ -572,6 +574,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(rt))
{
writer.WriteElementString("rottentomatoesid", rt);
+ writtenProviderIds.Add(MetadataProviders.RottenTomatoes.ToString());
}
var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection);
@@ -579,6 +582,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(tmdbCollection))
{
writer.WriteElementString("collectionnumber", tmdbCollection);
+ writtenProviderIds.Add(MetadataProviders.TmdbCollection.ToString());
}
var imdb = item.GetProviderId(MetadataProviders.Imdb);
@@ -592,6 +596,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString("imdbid", imdb);
}
+ writtenProviderIds.Add(MetadataProviders.Imdb.ToString());
}
// Series xml saver already saves this
@@ -601,6 +606,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(tvdb))
{
writer.WriteElementString("tvdbid", tvdb);
+ writtenProviderIds.Add(MetadataProviders.Tvdb.ToString());
}
}
@@ -608,12 +614,14 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(tmdb))
{
writer.WriteElementString("tmdbid", tmdb);
+ writtenProviderIds.Add(MetadataProviders.Tmdb.ToString());
}
var tvcom = item.GetProviderId(MetadataProviders.Tvcom);
if (!string.IsNullOrEmpty(tvcom))
{
writer.WriteElementString("tvcomid", tvcom);
+ writtenProviderIds.Add(MetadataProviders.Tvcom.ToString());
}
if (!string.IsNullOrEmpty(item.PreferredMetadataLanguage))
@@ -766,6 +774,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("audiodbartistid", externalId);
+ writtenProviderIds.Add(MetadataProviders.AudioDbArtist.ToString());
}
externalId = item.GetProviderId(MetadataProviders.AudioDbAlbum);
@@ -773,6 +782,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("audiodbalbumid", externalId);
+ writtenProviderIds.Add(MetadataProviders.AudioDbAlbum.ToString());
}
externalId = item.GetProviderId(MetadataProviders.Zap2It);
@@ -780,6 +790,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("zap2itid", externalId);
+ writtenProviderIds.Add(MetadataProviders.Zap2It.ToString());
}
externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbum);
@@ -787,6 +798,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("musicbrainzalbumid", externalId);
+ writtenProviderIds.Add(MetadataProviders.MusicBrainzAlbum.ToString());
}
externalId = item.GetProviderId(MetadataProviders.MusicBrainzAlbumArtist);
@@ -794,6 +806,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("musicbrainzalbumartistid", externalId);
+ writtenProviderIds.Add(MetadataProviders.MusicBrainzAlbumArtist.ToString());
}
externalId = item.GetProviderId(MetadataProviders.MusicBrainzArtist);
@@ -801,6 +814,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("musicbrainzartistid", externalId);
+ writtenProviderIds.Add(MetadataProviders.MusicBrainzArtist.ToString());
}
externalId = item.GetProviderId(MetadataProviders.MusicBrainzReleaseGroup);
@@ -808,24 +822,33 @@ namespace MediaBrowser.XbmcMetadata.Savers
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("musicbrainzreleasegroupid", externalId);
+ writtenProviderIds.Add(MetadataProviders.MusicBrainzReleaseGroup.ToString());
}
externalId = item.GetProviderId(MetadataProviders.Gamesdb);
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("gamesdbid", externalId);
+ writtenProviderIds.Add(MetadataProviders.Gamesdb.ToString());
}
externalId = item.GetProviderId(MetadataProviders.TvRage);
if (!string.IsNullOrEmpty(externalId))
{
writer.WriteElementString("tvrageid", externalId);
+ writtenProviderIds.Add(MetadataProviders.TvRage.ToString());
}
- externalId = item.GetProviderId(MetadataProviders.TvMaze);
- if (!string.IsNullOrEmpty(externalId))
+ if (item.ProviderIds != null)
{
- writer.WriteElementString("tvmazeid", externalId);
+ foreach (var providerKey in item.ProviderIds.Keys)
+ {
+ var providerId = item.ProviderIds[providerKey];
+ if (!string.IsNullOrEmpty(providerId) && !writtenProviderIds.Contains(providerKey))
+ {
+ writer.WriteElementString(providerKey.ToLower() + "id", providerId);
+ }
+ }
}
if (options.SaveImagePathsInNfo)
@@ -1017,12 +1040,7 @@ namespace MediaBrowser.XbmcMetadata.Savers
private static string GetPathToSave(string path, ILibraryManager libraryManager, IServerConfigurationManager config)
{
- foreach (var map in config.Configuration.PathSubstitutions)
- {
- path = libraryManager.SubstitutePath(path, map.From, map.To);
- }
-
- return path;
+ return libraryManager.GetPathAfterNetworkSubstitution(path);
}
private static bool IsPersonType(PersonInfo person, string type)
diff --git a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
index bf6df2347..d57d22d5c 100644
--- a/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
+++ b/MediaBrowser.XbmcMetadata/Savers/EpisodeNfoSaver.cs
@@ -72,19 +72,23 @@ namespace MediaBrowser.XbmcMetadata.Savers
{
writer.WriteElementString("airsbefore_episode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture));
}
- if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1)
- {
- writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture));
- }
if (episode.AirsBeforeSeasonNumber.HasValue && episode.AirsBeforeSeasonNumber.Value != -1)
{
writer.WriteElementString("airsbefore_season", episode.AirsBeforeSeasonNumber.Value.ToString(UsCulture));
}
- var season = episode.AiredSeasonNumber;
- if (season.HasValue && season.Value != -1)
+ if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0)
{
- writer.WriteElementString("displayseason", season.Value.ToString(UsCulture));
+ if (episode.AirsBeforeEpisodeNumber.HasValue && episode.AirsBeforeEpisodeNumber.Value != -1)
+ {
+ writer.WriteElementString("displayepisode", episode.AirsBeforeEpisodeNumber.Value.ToString(UsCulture));
+ }
+
+ var specialSeason = episode.AiredSeasonNumber;
+ if (specialSeason.HasValue && specialSeason.Value != -1)
+ {
+ writer.WriteElementString("displayseason", specialSeason.Value.ToString(UsCulture));
+ }
}
if (episode.DvdEpisodeNumber.HasValue)
diff --git a/MediaBrowser.sln b/MediaBrowser.sln
index c6068f536..7e0d834fa 100644
--- a/MediaBrowser.sln
+++ b/MediaBrowser.sln
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
-VisualStudioVersion = 14.0.24720.0
+VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F0E0E64C-2A6F-4E35-9533-D53AC07C2CD1}"
EndProject
@@ -64,6 +64,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Server.Startup
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Emby.Drawing", "Emby.Drawing\Emby.Drawing.csproj", "{08FFF49B-F175-4807-A2B5-73B0EBD9F716}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mono.Nat", "Mono.Nat\Mono.Nat.csproj", "{D7453B88-2266-4805-B39B-2B5A2A33E1BA}"
+EndProject
Global
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
@@ -522,6 +524,36 @@ Global
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|Win32.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x64.ActiveCfg = Release|Any CPU
{08FFF49B-F175-4807-A2B5-73B0EBD9F716}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|Win32.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x64.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Debug|x86.Build.0 = Debug|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|Win32.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x64.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release Mono|x86.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|Win32.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x64.Build.0 = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.ActiveCfg = Release|Any CPU
+ {D7453B88-2266-4805-B39B-2B5A2A33E1BA}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Mono.Nat/AbstractNatDevice.cs b/Mono.Nat/AbstractNatDevice.cs
new file mode 100644
index 000000000..046cfc10f
--- /dev/null
+++ b/Mono.Nat/AbstractNatDevice.cs
@@ -0,0 +1,97 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat
+{
+ public abstract class AbstractNatDevice : INatDevice
+ {
+ private DateTime lastSeen;
+
+ protected AbstractNatDevice ()
+ {
+
+ }
+
+ public abstract IPAddress LocalAddress { get; }
+
+ public DateTime LastSeen
+ {
+ get { return lastSeen; }
+ set { lastSeen = value; }
+ }
+
+ public virtual void CreatePortMap (Mapping mapping)
+ {
+ IAsyncResult result = BeginCreatePortMap (mapping, null, null);
+ EndCreatePortMap(result);
+ }
+
+ public virtual void DeletePortMap (Mapping mapping)
+ {
+ IAsyncResult result = BeginDeletePortMap (mapping, null, mapping);
+ EndDeletePortMap(result);
+ }
+
+ public virtual Mapping[] GetAllMappings ()
+ {
+ IAsyncResult result = BeginGetAllMappings (null, null);
+ return EndGetAllMappings (result);
+ }
+
+ public virtual IPAddress GetExternalIP ()
+ {
+ IAsyncResult result = BeginGetExternalIP(null, null);
+ return EndGetExternalIP(result);
+ }
+
+ public virtual Mapping GetSpecificMapping (Protocol protocol, int port)
+ {
+ IAsyncResult result = this.BeginGetSpecificMapping (protocol, port, null, null);
+ return this.EndGetSpecificMapping(result);
+ }
+
+ public abstract IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState);
+ public abstract IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+
+ public abstract IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState);
+ public abstract IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState);
+ public abstract IAsyncResult BeginGetSpecificMapping(Protocol protocol, int externalPort, AsyncCallback callback, object asyncState);
+
+ public abstract void EndCreatePortMap (IAsyncResult result);
+ public abstract void EndDeletePortMap (IAsyncResult result);
+
+ public abstract Mapping[] EndGetAllMappings (IAsyncResult result);
+ public abstract IPAddress EndGetExternalIP (IAsyncResult result);
+ public abstract Mapping EndGetSpecificMapping (IAsyncResult result);
+ }
+}
diff --git a/Mono.Nat/AsyncResults/AsyncResult.cs b/Mono.Nat/AsyncResults/AsyncResult.cs
new file mode 100644
index 000000000..e98e7d7ca
--- /dev/null
+++ b/Mono.Nat/AsyncResults/AsyncResult.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+
+namespace Mono.Nat
+{
+ internal class AsyncResult : IAsyncResult
+ {
+ private object asyncState;
+ private AsyncCallback callback;
+ private bool completedSynchronously;
+ private bool isCompleted;
+ private Exception storedException;
+ private ManualResetEvent waitHandle;
+
+ public AsyncResult(AsyncCallback callback, object asyncState)
+ {
+ this.callback = callback;
+ this.asyncState = asyncState;
+ waitHandle = new ManualResetEvent(false);
+ }
+
+ public object AsyncState
+ {
+ get { return asyncState; }
+ }
+
+ public ManualResetEvent AsyncWaitHandle
+ {
+ get { return waitHandle; }
+ }
+
+ WaitHandle IAsyncResult.AsyncWaitHandle
+ {
+ get { return waitHandle; }
+ }
+
+ public bool CompletedSynchronously
+ {
+ get { return completedSynchronously; }
+ protected internal set { completedSynchronously = value; }
+ }
+
+ public bool IsCompleted
+ {
+ get { return isCompleted; }
+ protected internal set { isCompleted = value; }
+ }
+
+ public Exception StoredException
+ {
+ get { return storedException; }
+ }
+
+ public void Complete()
+ {
+ Complete(storedException);
+ }
+
+ public void Complete(Exception ex)
+ {
+ storedException = ex;
+ isCompleted = true;
+ waitHandle.Set();
+
+ if (callback != null)
+ callback(this);
+ }
+ }
+}
diff --git a/Mono.Nat/Enums/MapState.cs b/Mono.Nat/Enums/MapState.cs
new file mode 100644
index 000000000..5ed2abd8f
--- /dev/null
+++ b/Mono.Nat/Enums/MapState.cs
@@ -0,0 +1,36 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public enum MapState
+ {
+ AlreadyMapped,
+ Available
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Enums/ProtocolType.cs b/Mono.Nat/Enums/ProtocolType.cs
new file mode 100644
index 000000000..a1f5cbb0e
--- /dev/null
+++ b/Mono.Nat/Enums/ProtocolType.cs
@@ -0,0 +1,36 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public enum Protocol
+ {
+ Tcp,
+ Udp
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/EventArgs/DeviceEventArgs.cs b/Mono.Nat/EventArgs/DeviceEventArgs.cs
new file mode 100644
index 000000000..fbbbf63e3
--- /dev/null
+++ b/Mono.Nat/EventArgs/DeviceEventArgs.cs
@@ -0,0 +1,45 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public class DeviceEventArgs : EventArgs
+ {
+ private INatDevice device;
+
+ public DeviceEventArgs(INatDevice device)
+ {
+ this.device = device;
+ }
+
+ public INatDevice Device
+ {
+ get { return this.device; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Exceptions/MappingException.cs b/Mono.Nat/Exceptions/MappingException.cs
new file mode 100644
index 000000000..bb2e6a69d
--- /dev/null
+++ b/Mono.Nat/Exceptions/MappingException.cs
@@ -0,0 +1,87 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Security.Permissions;
+
+namespace Mono.Nat
+{
+ [Serializable]
+ public class MappingException : Exception
+ {
+ private int errorCode;
+ private string errorText;
+
+ public int ErrorCode
+ {
+ get { return this.errorCode; }
+ }
+
+ public string ErrorText
+ {
+ get { return this.errorText; }
+ }
+
+ #region Constructors
+ public MappingException()
+ : base()
+ {
+ }
+
+ public MappingException(string message)
+ : base(message)
+ {
+ }
+
+ public MappingException(int errorCode, string errorText)
+ : base (string.Format ("Error {0}: {1}", errorCode, errorText))
+ {
+ this.errorCode = errorCode;
+ this.errorText = errorText;
+ }
+
+ public MappingException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+
+ protected MappingException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ : base(info, context)
+ {
+ }
+ #endregion
+
+ [SecurityPermission(SecurityAction.Demand, SerializationFormatter=true)]
+ public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
+ {
+ if(info==null) throw new ArgumentNullException("info");
+
+ this.errorCode = info.GetInt32("errorCode");
+ this.errorText = info.GetString("errorText");
+ base.GetObjectData(info, context);
+ }
+ }
+}
diff --git a/Mono.Nat/IMapper.cs b/Mono.Nat/IMapper.cs
new file mode 100644
index 000000000..b18e6cff2
--- /dev/null
+++ b/Mono.Nat/IMapper.cs
@@ -0,0 +1,50 @@
+//
+// Authors:
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Mono.Nat
+{
+ public enum MapperType
+ {
+ Pmp,
+ Upnp
+ }
+
+ internal interface IMapper
+ {
+ event EventHandler<DeviceEventArgs> DeviceFound;
+
+ void Map(IPAddress gatewayAddress);
+
+ void Handle(IPAddress localAddres, byte[] response);
+ }
+}
diff --git a/Mono.Nat/INatDevice.cs b/Mono.Nat/INatDevice.cs
new file mode 100644
index 000000000..c9f27055b
--- /dev/null
+++ b/Mono.Nat/INatDevice.cs
@@ -0,0 +1,62 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat
+{
+ public interface INatDevice
+ {
+ void CreatePortMap (Mapping mapping);
+ void DeletePortMap (Mapping mapping);
+
+ IPAddress LocalAddress { get; }
+ Mapping[] GetAllMappings ();
+ IPAddress GetExternalIP ();
+ Mapping GetSpecificMapping (Protocol protocol, int port);
+
+ IAsyncResult BeginCreatePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+ IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState);
+
+ IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState);
+ IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState);
+ IAsyncResult BeginGetSpecificMapping (Protocol protocol, int externalPort, AsyncCallback callback, object asyncState);
+
+ void EndCreatePortMap (IAsyncResult result);
+ void EndDeletePortMap (IAsyncResult result);
+
+ Mapping[] EndGetAllMappings (IAsyncResult result);
+ IPAddress EndGetExternalIP (IAsyncResult result);
+ Mapping EndGetSpecificMapping (IAsyncResult result);
+
+ DateTime LastSeen { get; set; }
+ }
+}
diff --git a/Mono.Nat/ISearcher.cs b/Mono.Nat/ISearcher.cs
new file mode 100644
index 000000000..56e438105
--- /dev/null
+++ b/Mono.Nat/ISearcher.cs
@@ -0,0 +1,51 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net.Sockets;
+using System.Net;
+
+namespace Mono.Nat
+{
+ public delegate void NatDeviceCallback(INatDevice device);
+
+ internal interface ISearcher
+ {
+ event EventHandler<DeviceEventArgs> DeviceFound;
+ event EventHandler<DeviceEventArgs> DeviceLost;
+
+ void Search();
+ void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint);
+ DateTime NextSearch { get; }
+ NatProtocol Protocol { get; }
+ }
+}
diff --git a/Mono.Nat/Mapping.cs b/Mono.Nat/Mapping.cs
new file mode 100644
index 000000000..dd49404c6
--- /dev/null
+++ b/Mono.Nat/Mapping.cs
@@ -0,0 +1,123 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat
+{
+ public class Mapping
+ {
+ private string description;
+ private DateTime expiration;
+ private int lifetime;
+ private int privatePort;
+ private Protocol protocol;
+ private int publicPort;
+
+
+
+ public Mapping (Protocol protocol, int privatePort, int publicPort)
+ : this (protocol, privatePort, publicPort, 0)
+ {
+ }
+
+ public Mapping (Protocol protocol, int privatePort, int publicPort, int lifetime)
+ {
+ this.protocol = protocol;
+ this.privatePort = privatePort;
+ this.publicPort = publicPort;
+ this.lifetime = lifetime;
+
+ if (lifetime == int.MaxValue)
+ this.expiration = DateTime.MaxValue;
+ else if (lifetime == 0)
+ this.expiration = DateTime.Now;
+ else
+ this.expiration = DateTime.Now.AddSeconds (lifetime);
+ }
+
+ public string Description
+ {
+ get { return description; }
+ set { description = value; }
+ }
+
+ public Protocol Protocol
+ {
+ get { return protocol; }
+ internal set { protocol = value; }
+ }
+
+ public int PrivatePort
+ {
+ get { return privatePort; }
+ internal set { privatePort = value; }
+ }
+
+ public int PublicPort
+ {
+ get { return publicPort; }
+ internal set { publicPort = value; }
+ }
+
+ public int Lifetime
+ {
+ get { return lifetime; }
+ internal set { lifetime = value; }
+ }
+
+ public DateTime Expiration
+ {
+ get { return expiration; }
+ internal set { expiration = value; }
+ }
+
+ public bool IsExpired ()
+ {
+ return expiration < DateTime.Now;
+ }
+
+ public override bool Equals (object obj)
+ {
+ Mapping other = obj as Mapping;
+ return other == null ? false : this.protocol == other.protocol &&
+ this.privatePort == other.privatePort && this.publicPort == other.publicPort;
+ }
+
+ public override int GetHashCode()
+ {
+ return this.protocol.GetHashCode() ^ this.privatePort.GetHashCode() ^ this.publicPort.GetHashCode();
+ }
+
+ public override string ToString( )
+ {
+ return String.Format( "Protocol: {0}, Public Port: {1}, Private Port: {2}, Description: {3}, Expiration: {4}, Lifetime: {5}",
+ this.protocol, this.publicPort, this.privatePort, this.description, this.expiration, this.lifetime );
+ }
+ }
+}
diff --git a/Mono.Nat/Mono.Nat.csproj b/Mono.Nat/Mono.Nat.csproj
new file mode 100644
index 000000000..9c2781433
--- /dev/null
+++ b/Mono.Nat/Mono.Nat.csproj
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{D7453B88-2266-4805-B39B-2B5A2A33E1BA}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Mono.Nat</RootNamespace>
+ <AssemblyName>Mono.Nat</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <TargetFrameworkProfile />
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="..\SharedVersion.cs">
+ <Link>Properties\SharedVersion.cs</Link>
+ </Compile>
+ <Compile Include="AbstractNatDevice.cs" />
+ <Compile Include="AsyncResults\AsyncResult.cs" />
+ <Compile Include="Enums\MapState.cs" />
+ <Compile Include="Enums\ProtocolType.cs" />
+ <Compile Include="EventArgs\DeviceEventArgs.cs" />
+ <Compile Include="Exceptions\MappingException.cs" />
+ <Compile Include="IMapper.cs" />
+ <Compile Include="INatDevice.cs" />
+ <Compile Include="ISearcher.cs" />
+ <Compile Include="Mapping.cs" />
+ <Compile Include="NatProtocol.cs" />
+ <Compile Include="NatUtility.cs" />
+ <Compile Include="Pmp\AsyncResults\PortMapAsyncResult.cs" />
+ <Compile Include="Pmp\Mappers\PmpMapper.cs" />
+ <Compile Include="Pmp\Pmp.cs" />
+ <Compile Include="Pmp\PmpConstants.cs" />
+ <Compile Include="Pmp\PmpNatDevice.cs" />
+ <Compile Include="Pmp\Searchers\PmpSearcher.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Upnp\AsyncResults\GetAllMappingsAsyncResult.cs" />
+ <Compile Include="Upnp\AsyncResults\PortMapAsyncResult.cs" />
+ <Compile Include="Upnp\Mappers\UpnpMapper.cs" />
+ <Compile Include="Upnp\Messages\DiscoverDeviceMessage.cs" />
+ <Compile Include="Upnp\Messages\ErrorMessage.cs" />
+ <Compile Include="Upnp\Messages\GetServicesMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\CreatePortMappingMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\DeletePortMappingMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\GetExternalIPAddressMessage.cs" />
+ <Compile Include="Upnp\Messages\Requests\GetGenericPortMappingEntry.cs" />
+ <Compile Include="Upnp\Messages\Requests\GetSpecificPortMappingEntryMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\CreatePortMappingResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\DeletePortMappingResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\GetExternalIPAddressResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\Responses\GetGenericPortMappingEntryResponseMessage.cs" />
+ <Compile Include="Upnp\Messages\UpnpMessage.cs" />
+ <Compile Include="Upnp\Searchers\UpnpSearcher.cs" />
+ <Compile Include="Upnp\Upnp.cs" />
+ <Compile Include="Upnp\UpnpNatDevice.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project> \ No newline at end of file
diff --git a/Mono.Nat/NatProtocol.cs b/Mono.Nat/NatProtocol.cs
new file mode 100644
index 000000000..ade8d921c
--- /dev/null
+++ b/Mono.Nat/NatProtocol.cs
@@ -0,0 +1,9 @@
+
+namespace Mono.Nat
+{
+ public enum NatProtocol
+ {
+ Upnp = 0,
+ Pmp = 1
+ }
+}
diff --git a/Mono.Nat/NatUtility.cs b/Mono.Nat/NatUtility.cs
new file mode 100644
index 000000000..6d91d2513
--- /dev/null
+++ b/Mono.Nat/NatUtility.cs
@@ -0,0 +1,264 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Linq;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Model.Logging;
+using Mono.Nat.Pmp.Mappers;
+using Mono.Nat.Upnp.Mappers;
+
+namespace Mono.Nat
+{
+ public static class NatUtility
+ {
+ private static ManualResetEvent searching;
+ public static event EventHandler<DeviceEventArgs> DeviceFound;
+ public static event EventHandler<DeviceEventArgs> DeviceLost;
+
+ public static event EventHandler<UnhandledExceptionEventArgs> UnhandledException;
+
+ private static List<ISearcher> controllers;
+ private static bool verbose;
+
+ public static List<NatProtocol> EnabledProtocols { get; set; }
+
+ public static ILogger Logger { get; set; }
+
+ public static bool Verbose
+ {
+ get { return verbose; }
+ set { verbose = value; }
+ }
+
+ static NatUtility()
+ {
+ EnabledProtocols = new List<NatProtocol>
+ {
+ NatProtocol.Upnp,
+ NatProtocol.Pmp
+ };
+
+ searching = new ManualResetEvent(false);
+
+ controllers = new List<ISearcher>();
+ controllers.Add(UpnpSearcher.Instance);
+ controllers.Add(PmpSearcher.Instance);
+
+ controllers.ForEach(searcher =>
+ {
+ searcher.DeviceFound += (sender, args) =>
+ {
+ if (DeviceFound != null)
+ DeviceFound(sender, args);
+ };
+ searcher.DeviceLost += (sender, args) =>
+ {
+ if (DeviceLost != null)
+ DeviceLost(sender, args);
+ };
+ });
+ Thread t = new Thread(SearchAndListen);
+ t.IsBackground = true;
+ t.Start();
+ }
+
+ internal static void Log(string format, params object[] args)
+ {
+ var logger = Logger;
+ if (logger != null)
+ logger.Debug(format, args);
+ }
+
+ private static void SearchAndListen()
+ {
+ while (true)
+ {
+ searching.WaitOne();
+
+ try
+ {
+ var enabledProtocols = EnabledProtocols.ToList();
+
+ if (enabledProtocols.Contains(UpnpSearcher.Instance.Protocol))
+ {
+ Receive(UpnpSearcher.Instance, UpnpSearcher.sockets);
+ }
+ if (enabledProtocols.Contains(PmpSearcher.Instance.Protocol))
+ {
+ Receive(PmpSearcher.Instance, PmpSearcher.sockets);
+ }
+
+ foreach (ISearcher s in controllers)
+ if (s.NextSearch < DateTime.Now && enabledProtocols.Contains(s.Protocol))
+ {
+ Log("Searching for: {0}", s.GetType().Name);
+ s.Search();
+ }
+ }
+ catch (Exception e)
+ {
+ if (UnhandledException != null)
+ UnhandledException(typeof(NatUtility), new UnhandledExceptionEventArgs(e, false));
+ }
+ Thread.Sleep(10);
+ }
+ }
+
+ static void Receive (ISearcher searcher, List<UdpClient> clients)
+ {
+ IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+ foreach (UdpClient client in clients)
+ {
+ if (client.Available > 0)
+ {
+ IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address;
+ byte[] data = client.Receive(ref received);
+ searcher.Handle(localAddress, data, received);
+ }
+ }
+ }
+
+ static void Receive(IMapper mapper, List<UdpClient> clients)
+ {
+ IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+ foreach (UdpClient client in clients)
+ {
+ if (client.Available > 0)
+ {
+ IPAddress localAddress = ((IPEndPoint)client.Client.LocalEndPoint).Address;
+ byte[] data = client.Receive(ref received);
+ mapper.Handle(localAddress, data);
+ }
+ }
+ }
+
+ public static void StartDiscovery ()
+ {
+ searching.Set();
+ }
+
+ public static void StopDiscovery ()
+ {
+ searching.Reset();
+ }
+
+ //This is for when you know the Gateway IP and want to skip the costly search...
+ public static void DirectMap(IPAddress gatewayAddress, MapperType type)
+ {
+ IMapper mapper;
+ switch (type)
+ {
+ case MapperType.Pmp:
+ mapper = new PmpMapper();
+ break;
+ case MapperType.Upnp:
+ mapper = new UpnpMapper();
+ mapper.DeviceFound += (sender, args) =>
+ {
+ if (DeviceFound != null)
+ DeviceFound(sender, args);
+ };
+ mapper.Map(gatewayAddress);
+ break;
+ default:
+ throw new InvalidOperationException("Unsuported type given");
+
+ }
+ searching.Reset();
+
+ }
+
+ //So then why is it here? -Nick
+ [Obsolete ("This method serves no purpose and shouldn't be used")]
+ public static IPAddress[] GetLocalAddresses (bool includeIPv6)
+ {
+ List<IPAddress> addresses = new List<IPAddress> ();
+
+ IPHostEntry hostInfo = Dns.GetHostEntry (Dns.GetHostName ());
+ foreach (IPAddress address in hostInfo.AddressList) {
+ if (address.AddressFamily == AddressFamily.InterNetwork ||
+ (includeIPv6 && address.AddressFamily == AddressFamily.InterNetworkV6)) {
+ addresses.Add (address);
+ }
+ }
+
+ return addresses.ToArray ();
+ }
+
+ //checks if an IP address is a private address space as defined by RFC 1918
+ public static bool IsPrivateAddressSpace (IPAddress address)
+ {
+ byte[] ba = address.GetAddressBytes ();
+
+ switch ((int)ba[0]) {
+ case 10:
+ return true; //10.x.x.x
+ case 172:
+ return ((int)ba[1] & 16) != 0; //172.16-31.x.x
+ case 192:
+ return (int)ba[1] == 168; //192.168.x.x
+ default:
+ return false;
+ }
+ }
+
+ public static void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint, NatProtocol protocol)
+ {
+ switch (protocol)
+ {
+ case NatProtocol.Upnp:
+ UpnpSearcher.Instance.Handle(localAddress, response, endpoint);
+ break;
+ case NatProtocol.Pmp:
+ PmpSearcher.Instance.Handle(localAddress, response, endpoint);
+ break;
+ default:
+ throw new ArgumentException("Unexpected protocol: " + protocol);
+ }
+ }
+
+ public static void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint, NatProtocol protocol)
+ {
+ switch (protocol)
+ {
+ case NatProtocol.Upnp:
+ UpnpSearcher.Instance.Handle(localAddress, deviceInfo, endpoint);
+ break;
+ default:
+ throw new ArgumentException("Unexpected protocol: " + protocol);
+ }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
new file mode 100644
index 000000000..c8ccf5435
--- /dev/null
+++ b/Mono.Nat/Pmp/AsyncResults/PortMapAsyncResult.cs
@@ -0,0 +1,52 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Pmp
+{
+ internal class PortMapAsyncResult : AsyncResult
+ {
+ private Mapping mapping;
+
+ internal PortMapAsyncResult (Mapping mapping, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.mapping = mapping;
+ }
+
+ internal PortMapAsyncResult (Protocol protocol, int port, int lifetime, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.mapping = new Mapping (protocol, port, port, lifetime);
+ }
+
+ internal Mapping Mapping
+ {
+ get { return mapping; }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/Mappers/PmpMapper.cs b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
new file mode 100644
index 000000000..f33ca44c3
--- /dev/null
+++ b/Mono.Nat/Pmp/Mappers/PmpMapper.cs
@@ -0,0 +1,83 @@
+//
+// Authors:
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using Mono.Nat.Pmp;
+
+namespace Mono.Nat.Pmp.Mappers
+{
+ internal class PmpMapper : Pmp, IMapper
+ {
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+
+ static PmpMapper()
+ {
+ CreateSocketsAndAddGateways();
+ }
+
+ public void Map(IPAddress gatewayAddress)
+ {
+ sockets.ForEach(x => Map(x, gatewayAddress));
+ }
+
+ void Map(UdpClient client, IPAddress gatewayAddress)
+ {
+ // The nat-pmp search message. Must be sent to GatewayIP:53531
+ byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
+
+ client.Send(buffer, buffer.Length, new IPEndPoint(gatewayAddress, PmpConstants.ServerPort));
+ }
+
+ public void Handle(IPAddress localAddres, byte[] response)
+ {
+ //if (!IsSearchAddress(endpoint.Address))
+ // return;
+ if (response.Length != 12)
+ return;
+ if (response[0] != PmpConstants.Version)
+ return;
+ if (response[1] != PmpConstants.ServerNoop)
+ return;
+ int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2));
+ if (errorcode != 0)
+ NatUtility.Log("Non zero error: {0}", errorcode);
+
+ IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] });
+ OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(localAddres, publicIp)));
+ }
+
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/Pmp.cs b/Mono.Nat/Pmp/Pmp.cs
new file mode 100644
index 000000000..6795561b1
--- /dev/null
+++ b/Mono.Nat/Pmp/Pmp.cs
@@ -0,0 +1,118 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+
+namespace Mono.Nat.Pmp
+{
+ internal abstract class Pmp
+ {
+ public static List<UdpClient> sockets;
+ protected static Dictionary<UdpClient, List<IPEndPoint>> gatewayLists;
+
+ internal static void CreateSocketsAndAddGateways()
+ {
+ sockets = new List<UdpClient>();
+ gatewayLists = new Dictionary<UdpClient, List<IPEndPoint>>();
+
+ try
+ {
+ foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ if (n.OperationalStatus != OperationalStatus.Up && n.OperationalStatus != OperationalStatus.Unknown)
+ continue;
+ IPInterfaceProperties properties = n.GetIPProperties();
+ List<IPEndPoint> gatewayList = new List<IPEndPoint>();
+
+ foreach (GatewayIPAddressInformation gateway in properties.GatewayAddresses)
+ {
+ if (gateway.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ gatewayList.Add(new IPEndPoint(gateway.Address, PmpConstants.ServerPort));
+ }
+ }
+ if (gatewayList.Count == 0)
+ {
+ /* Mono on OSX doesn't give any gateway addresses, so check DNS entries */
+ foreach (var gw2 in properties.DnsAddresses)
+ {
+ if (gw2.AddressFamily == AddressFamily.InterNetwork)
+ {
+ gatewayList.Add(new IPEndPoint(gw2, PmpConstants.ServerPort));
+ }
+ }
+ foreach (var unicast in properties.UnicastAddresses)
+ {
+ if (/*unicast.DuplicateAddressDetectionState == DuplicateAddressDetectionState.Preferred
+ && unicast.AddressPreferredLifetime != UInt32.MaxValue
+ && */unicast.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ var bytes = unicast.Address.GetAddressBytes();
+ bytes[3] = 1;
+ gatewayList.Add(new IPEndPoint(new IPAddress(bytes), PmpConstants.ServerPort));
+ }
+ }
+ }
+
+ if (gatewayList.Count > 0)
+ {
+ foreach (UnicastIPAddressInformation address in properties.UnicastAddresses)
+ {
+ if (address.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ UdpClient client;
+
+ try
+ {
+ client = new UdpClient(new IPEndPoint(address.Address, 0));
+ }
+ catch (SocketException)
+ {
+ continue; // Move on to the next address.
+ }
+
+ gatewayLists.Add(client, gatewayList);
+ sockets.Add(client);
+ }
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ // NAT-PMP does not use multicast, so there isn't really a good fallback.
+ }
+ }
+ }
+}
diff --git a/Mono.Nat/Pmp/PmpConstants.cs b/Mono.Nat/Pmp/PmpConstants.cs
new file mode 100644
index 000000000..ff3eb6230
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpConstants.cs
@@ -0,0 +1,56 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Pmp
+{
+ internal static class PmpConstants
+ {
+ public const byte Version = (byte)0;
+
+ public const byte OperationCode = (byte)0;
+ public const byte OperationCodeUdp = (byte)1;
+ public const byte OperationCodeTcp = (byte)2;
+ public const byte ServerNoop = (byte)128;
+
+ public const int ClientPort = 5350;
+ public const int ServerPort = 5351;
+
+ public const int RetryDelay = 250;
+ public const int RetryAttempts = 9;
+
+ public const int RecommendedLeaseTime = 60 * 60;
+ public const int DefaultLeaseTime = RecommendedLeaseTime;
+
+ public const short ResultCodeSuccess = 0;
+ public const short ResultCodeUnsupportedVersion = 1;
+ public const short ResultCodeNotAuthorized = 2;
+ public const short ResultCodeNetworkFailure = 3;
+ public const short ResultCodeOutOfResources = 4;
+ public const short ResultCodeUnsupportedOperationCode = 5;
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Pmp/PmpNatDevice.cs b/Mono.Nat/Pmp/PmpNatDevice.cs
new file mode 100644
index 000000000..9a2962c4d
--- /dev/null
+++ b/Mono.Nat/Pmp/PmpNatDevice.cs
@@ -0,0 +1,347 @@
+//
+// Authors:
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Collections.Generic;
+
+namespace Mono.Nat.Pmp
+{
+ internal sealed class PmpNatDevice : AbstractNatDevice, IEquatable<PmpNatDevice>
+ {
+ private AsyncResult externalIpResult;
+ private bool pendingOp;
+ private IPAddress localAddress;
+ private IPAddress publicAddress;
+
+ internal PmpNatDevice (IPAddress localAddress, IPAddress publicAddress)
+ {
+ this.localAddress = localAddress;
+ this.publicAddress = publicAddress;
+ }
+
+ public override IPAddress LocalAddress
+ {
+ get { return localAddress; }
+ }
+
+ public override IPAddress GetExternalIP ()
+ {
+ return publicAddress;
+ }
+
+ public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ PortMapAsyncResult pmar = new PortMapAsyncResult (mapping.Protocol, mapping.PublicPort, PmpConstants.DefaultLeaseTime, callback, asyncState);
+ ThreadPool.QueueUserWorkItem (delegate
+ {
+ try
+ {
+ CreatePortMap(pmar.Mapping, true);
+ pmar.Complete();
+ }
+ catch (Exception e)
+ {
+ pmar.Complete(e);
+ }
+ });
+ return pmar;
+ }
+
+ public override IAsyncResult BeginDeletePortMap (Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ PortMapAsyncResult pmar = new PortMapAsyncResult (mapping, callback, asyncState);
+ ThreadPool.QueueUserWorkItem (delegate {
+ try {
+ CreatePortMap(pmar.Mapping, false);
+ pmar.Complete();
+ } catch (Exception e) {
+ pmar.Complete(e);
+ }
+ });
+ return pmar;
+ }
+
+ public override void EndCreatePortMap (IAsyncResult result)
+ {
+ PortMapAsyncResult pmar = result as PortMapAsyncResult;
+ pmar.AsyncWaitHandle.WaitOne ();
+ }
+
+ public override void EndDeletePortMap (IAsyncResult result)
+ {
+ PortMapAsyncResult pmar = result as PortMapAsyncResult;
+ pmar.AsyncWaitHandle.WaitOne ();
+ }
+
+ public override IAsyncResult BeginGetAllMappings (AsyncCallback callback, object asyncState)
+ {
+ //NAT-PMP does not specify a way to get all port mappings
+ throw new NotSupportedException ();
+ }
+
+ public override IAsyncResult BeginGetExternalIP (AsyncCallback callback, object asyncState)
+ {
+ StartOp(ref externalIpResult, callback, asyncState);
+ AsyncResult result = externalIpResult;
+ result.Complete();
+ return result;
+ }
+
+ public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState)
+ {
+ //NAT-PMP does not specify a way to get a specific port map
+ throw new NotSupportedException ();
+ }
+
+ public override Mapping[] EndGetAllMappings (IAsyncResult result)
+ {
+ //NAT-PMP does not specify a way to get all port mappings
+ throw new NotSupportedException ();
+ }
+
+ public override IPAddress EndGetExternalIP (IAsyncResult result)
+ {
+ EndOp(result, ref externalIpResult);
+ return publicAddress;
+ }
+
+ private void StartOp(ref AsyncResult result, AsyncCallback callback, object asyncState)
+ {
+ if (pendingOp == true)
+ throw new InvalidOperationException("Can only have one simultaenous async operation");
+
+ pendingOp = true;
+ result = new AsyncResult(callback, asyncState);
+ }
+
+ private void EndOp(IAsyncResult supplied, ref AsyncResult actual)
+ {
+ if (supplied == null)
+ throw new ArgumentNullException("result");
+
+ if (supplied != actual)
+ throw new ArgumentException("Supplied IAsyncResult does not match the stored result");
+
+ if (!supplied.IsCompleted)
+ supplied.AsyncWaitHandle.WaitOne();
+
+ if (actual.StoredException != null)
+ throw actual.StoredException;
+
+ pendingOp = false;
+ actual = null;
+ }
+
+ public override Mapping EndGetSpecificMapping (IAsyncResult result)
+ {
+ //NAT-PMP does not specify a way to get a specific port map
+ throw new NotSupportedException ();
+ }
+
+ public override bool Equals(object obj)
+ {
+ PmpNatDevice device = obj as PmpNatDevice;
+ return (device == null) ? false : this.Equals(device);
+ }
+
+ public override int GetHashCode ()
+ {
+ return this.publicAddress.GetHashCode();
+ }
+
+ public bool Equals (PmpNatDevice other)
+ {
+ return (other == null) ? false : this.publicAddress.Equals(other.publicAddress);
+ }
+
+ private Mapping CreatePortMap (Mapping mapping, bool create)
+ {
+ List<byte> package = new List<byte> ();
+
+ package.Add (PmpConstants.Version);
+ package.Add (mapping.Protocol == Protocol.Tcp ? PmpConstants.OperationCodeTcp : PmpConstants.OperationCodeUdp);
+ package.Add ((byte)0); //reserved
+ package.Add ((byte)0); //reserved
+ package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder((short)mapping.PrivatePort)));
+ package.AddRange (BitConverter.GetBytes (create ? IPAddress.HostToNetworkOrder((short)mapping.PublicPort) : (short)0));
+ package.AddRange (BitConverter.GetBytes (IPAddress.HostToNetworkOrder(mapping.Lifetime)));
+
+ CreatePortMapAsyncState state = new CreatePortMapAsyncState ();
+ state.Buffer = package.ToArray ();
+ state.Mapping = mapping;
+
+ ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapAsync), state);
+ WaitHandle.WaitAll (new WaitHandle[] {state.ResetEvent});
+
+ if (!state.Success) {
+ string type = create ? "create" : "delete";
+ throw new MappingException (String.Format ("Failed to {0} portmap (protocol={1}, private port={2}", type, mapping.Protocol, mapping.PrivatePort));
+ }
+
+ return state.Mapping;
+ }
+
+ private void CreatePortMapAsync (object obj)
+ {
+ CreatePortMapAsyncState state = obj as CreatePortMapAsyncState;
+
+ UdpClient udpClient = new UdpClient ();
+ CreatePortMapListenState listenState = new CreatePortMapListenState (state, udpClient);
+
+ int attempt = 0;
+ int delay = PmpConstants.RetryDelay;
+
+ ThreadPool.QueueUserWorkItem (new WaitCallback (CreatePortMapListen), listenState);
+
+ while (attempt < PmpConstants.RetryAttempts && !listenState.Success) {
+ udpClient.Send (state.Buffer, state.Buffer.Length, new IPEndPoint (localAddress, PmpConstants.ServerPort));
+ listenState.UdpClientReady.Set();
+
+ attempt++;
+ delay *= 2;
+ Thread.Sleep (delay);
+ }
+
+ state.Success = listenState.Success;
+
+ udpClient.Close ();
+ state.ResetEvent.Set ();
+ }
+
+ private void CreatePortMapListen (object obj)
+ {
+ CreatePortMapListenState state = obj as CreatePortMapListenState;
+
+ UdpClient udpClient = state.UdpClient;
+ state.UdpClientReady.WaitOne(); // Evidently UdpClient has some lazy-init Send/Receive race?
+ IPEndPoint endPoint = new IPEndPoint (localAddress, PmpConstants.ServerPort);
+
+ while (!state.Success)
+ {
+ byte[] data;
+ try
+ {
+ data = udpClient.Receive(ref endPoint);
+ }
+ catch (SocketException)
+ {
+ state.Success = false;
+ return;
+ }
+
+ catch (ObjectDisposedException)
+ {
+ state.Success = false;
+ return;
+ }
+
+ if (data.Length < 16)
+ continue;
+
+ if (data[0] != PmpConstants.Version)
+ continue;
+
+ byte opCode = (byte)(data[1] & (byte)127);
+
+ Protocol protocol = Protocol.Tcp;
+ if (opCode == PmpConstants.OperationCodeUdp)
+ protocol = Protocol.Udp;
+
+ short resultCode = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 2));
+ uint epoch = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 4));
+
+ int privatePort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 8));
+ int publicPort = IPAddress.NetworkToHostOrder (BitConverter.ToInt16 (data, 10));
+
+ uint lifetime = (uint)IPAddress.NetworkToHostOrder (BitConverter.ToInt32 (data, 12));
+
+ if (publicPort < 0 || privatePort < 0 || resultCode != PmpConstants.ResultCodeSuccess)
+ {
+ state.Success = false;
+ return;
+ }
+
+ if (lifetime == 0)
+ {
+ //mapping was deleted
+ state.Success = true;
+ state.Mapping = null;
+ return;
+ }
+ else
+ {
+ //mapping was created
+ //TODO: verify that the private port+protocol are a match
+ Mapping mapping = state.Mapping;
+ mapping.PublicPort = publicPort;
+ mapping.Protocol = protocol;
+ mapping.Expiration = DateTime.Now.AddSeconds (lifetime);
+
+ state.Success = true;
+ }
+ }
+ }
+
+
+ /// <summary>
+ /// Overridden.
+ /// </summary>
+ /// <returns></returns>
+ public override string ToString( )
+ {
+ return String.Format( "PmpNatDevice - Local Address: {0}, Public IP: {1}, Last Seen: {2}",
+ this.localAddress, this.publicAddress, this.LastSeen );
+ }
+
+
+ private class CreatePortMapAsyncState
+ {
+ internal byte[] Buffer;
+ internal ManualResetEvent ResetEvent = new ManualResetEvent (false);
+ internal Mapping Mapping;
+
+ internal bool Success;
+ }
+
+ private class CreatePortMapListenState
+ {
+ internal volatile bool Success;
+ internal Mapping Mapping;
+ internal UdpClient UdpClient;
+ internal ManualResetEvent UdpClientReady;
+
+ internal CreatePortMapListenState (CreatePortMapAsyncState state, UdpClient client)
+ {
+ Mapping = state.Mapping;
+ UdpClient = client; UdpClientReady = new ManualResetEvent(false);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Mono.Nat/Pmp/Searchers/PmpSearcher.cs b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs
new file mode 100644
index 000000000..df0273ccb
--- /dev/null
+++ b/Mono.Nat/Pmp/Searchers/PmpSearcher.cs
@@ -0,0 +1,149 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Pmp;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Linq;
+
+namespace Mono.Nat
+{
+ internal class PmpSearcher : Pmp.Pmp, ISearcher
+ {
+ static PmpSearcher instance = new PmpSearcher();
+
+
+ public static PmpSearcher Instance
+ {
+ get { return instance; }
+ }
+
+ private int timeout;
+ private DateTime nextSearch;
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+ public event EventHandler<DeviceEventArgs> DeviceLost;
+
+ static PmpSearcher()
+ {
+ CreateSocketsAndAddGateways();
+ }
+
+ PmpSearcher()
+ {
+ timeout = 250;
+ }
+
+ public void Search()
+ {
+ foreach (UdpClient s in sockets)
+ {
+ try
+ {
+ Search(s);
+ }
+ catch
+ {
+ // Ignore any search errors
+ }
+ }
+ }
+
+ void Search (UdpClient client)
+ {
+ // Sort out the time for the next search first. The spec says the
+ // timeout should double after each attempt. Once it reaches 64 seconds
+ // (and that attempt fails), assume no devices available
+ nextSearch = DateTime.Now.AddMilliseconds(timeout);
+ timeout *= 2;
+
+ // We've tried 9 times as per spec, try searching again in 5 minutes
+ if (timeout == 128 * 1000)
+ {
+ timeout = 250;
+ nextSearch = DateTime.Now.AddMinutes(10);
+ return;
+ }
+
+ // The nat-pmp search message. Must be sent to GatewayIP:53531
+ byte[] buffer = new byte[] { PmpConstants.Version, PmpConstants.OperationCode };
+ foreach (IPEndPoint gatewayEndpoint in gatewayLists[client])
+ client.Send(buffer, buffer.Length, gatewayEndpoint);
+ }
+
+ bool IsSearchAddress(IPAddress address)
+ {
+ foreach (List<IPEndPoint> gatewayList in gatewayLists.Values)
+ foreach (IPEndPoint gatewayEndpoint in gatewayList)
+ if (gatewayEndpoint.Address.Equals(address))
+ return true;
+ return false;
+ }
+
+ public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ if (!IsSearchAddress(endpoint.Address))
+ return;
+ if (response.Length != 12)
+ return;
+ if (response[0] != PmpConstants.Version)
+ return;
+ if (response[1] != PmpConstants.ServerNoop)
+ return;
+ int errorcode = IPAddress.NetworkToHostOrder(BitConverter.ToInt16(response, 2));
+ if (errorcode != 0)
+ NatUtility.Log("Non zero error: {0}", errorcode);
+
+ IPAddress publicIp = new IPAddress(new byte[] { response[8], response[9], response[10], response[11] });
+ nextSearch = DateTime.Now.AddMinutes(5);
+ timeout = 250;
+ OnDeviceFound(new DeviceEventArgs(new PmpNatDevice(endpoint.Address, publicIp)));
+ }
+
+ public DateTime NextSearch
+ {
+ get { return nextSearch; }
+ }
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+
+ public NatProtocol Protocol
+ {
+ get { return NatProtocol.Pmp; }
+ }
+ }
+}
diff --git a/Mono.Nat/Properties/AssemblyInfo.cs b/Mono.Nat/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..c3c3101de
--- /dev/null
+++ b/Mono.Nat/Properties/AssemblyInfo.cs
@@ -0,0 +1,31 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Mono.Nat")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Mono.Nat")]
+[assembly: AssemblyCopyright("Copyright © 2016")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d7453b88-2266-4805-b39b-2b5a2a33e1ba")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+// \ No newline at end of file
diff --git a/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
new file mode 100644
index 000000000..51ecfbaf0
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/GetAllMappingsAsyncResult.cs
@@ -0,0 +1,56 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetAllMappingsAsyncResult : PortMapAsyncResult
+ {
+ private List<Mapping> mappings;
+ private Mapping specificMapping;
+
+ public GetAllMappingsAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
+ : base(request, callback, asyncState)
+ {
+ mappings = new List<Mapping>();
+ }
+
+ public List<Mapping> Mappings
+ {
+ get { return this.mappings; }
+ }
+
+ public Mapping SpecificMapping
+ {
+ get { return this.specificMapping; }
+ set { this.specificMapping = value; }
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
new file mode 100644
index 000000000..d8ac3fe61
--- /dev/null
+++ b/Mono.Nat/Upnp/AsyncResults/PortMapAsyncResult.cs
@@ -0,0 +1,75 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+using System.Net;
+using System.Threading;
+
+namespace Mono.Nat.Upnp
+{
+ internal class PortMapAsyncResult : AsyncResult
+ {
+ private WebRequest request;
+ private MessageBase savedMessage;
+
+ protected PortMapAsyncResult(WebRequest request, AsyncCallback callback, object asyncState)
+ : base (callback, asyncState)
+ {
+ this.request = request;
+ }
+
+ internal WebRequest Request
+ {
+ get { return this.request; }
+ set { this.request = value; }
+ }
+
+ internal MessageBase SavedMessage
+ {
+ get { return this.savedMessage; }
+ set { this.savedMessage = value; }
+ }
+
+ internal static PortMapAsyncResult Create (MessageBase message, WebRequest request, AsyncCallback storedCallback, object asyncState)
+ {
+ if (message is GetGenericPortMappingEntry)
+ return new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
+
+ if (message is GetSpecificPortMappingEntryMessage)
+ {
+ GetSpecificPortMappingEntryMessage mapMessage = (GetSpecificPortMappingEntryMessage)message;
+ GetAllMappingsAsyncResult result = new GetAllMappingsAsyncResult(request, storedCallback, asyncState);
+
+ result.SpecificMapping = new Mapping(mapMessage.protocol, 0, mapMessage.externalPort, 0);
+ return result;
+ }
+
+ return new PortMapAsyncResult(request, storedCallback, asyncState);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Mappers/UpnpMapper.cs b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs
new file mode 100644
index 000000000..6f2716805
--- /dev/null
+++ b/Mono.Nat/Upnp/Mappers/UpnpMapper.cs
@@ -0,0 +1,110 @@
+//
+// Authors:
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+
+namespace Mono.Nat.Upnp.Mappers
+{
+ internal class UpnpMapper : Upnp, IMapper
+ {
+
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+
+ public UdpClient Client { get; set; }
+
+ public UpnpMapper()
+ {
+ //Bind to local port 1900 for ssdp responses
+ Client = new UdpClient(1900);
+ }
+
+ public void Map(IPAddress gatewayAddress)
+ {
+ //Get the httpu request payload
+ byte[] data = DiscoverDeviceMessage.EncodeUnicast(gatewayAddress);
+
+ Client.Send(data, data.Length, new IPEndPoint(gatewayAddress, 1900));
+
+ new Thread(Receive).Start();
+ }
+
+ public void Receive()
+ {
+ while (true)
+ {
+ IPEndPoint received = new IPEndPoint(IPAddress.Parse("192.168.0.1"), 5351);
+ if (Client.Available > 0)
+ {
+ IPAddress localAddress = ((IPEndPoint)Client.Client.LocalEndPoint).Address;
+ byte[] data = Client.Receive(ref received);
+ Handle(localAddress, data, received);
+ }
+ }
+ }
+
+ public void Handle(IPAddress localAddres, byte[] response)
+ {
+ Handle(localAddres, response, null);
+ }
+
+ public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ // No matter what, this method should never throw an exception. If something goes wrong
+ // we should still be in a position to handle the next reply correctly.
+ try
+ {
+ UpnpNatDevice d = base.Handle(localAddress, response, endpoint);
+ d.GetServicesList(DeviceSetupComplete);
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: ");
+ Trace.WriteLine("ErrorMessage:");
+ Trace.WriteLine(ex.Message);
+ Trace.WriteLine("Data string:");
+ Trace.WriteLine(Encoding.UTF8.GetString(response));
+ }
+ }
+
+ private void DeviceSetupComplete(INatDevice device)
+ {
+ OnDeviceFound(new DeviceEventArgs(device));
+ }
+
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
new file mode 100644
index 000000000..87f5835a6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/DiscoverDeviceMessage.cs
@@ -0,0 +1,60 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.Text;
+
+namespace Mono.Nat.Upnp
+{
+ internal static class DiscoverDeviceMessage
+ {
+ /// <summary>
+ /// The message sent to discover all uPnP devices on the network
+ /// </summary>
+ /// <returns></returns>
+ public static byte[] EncodeSSDP()
+ {
+ string s = "M-SEARCH * HTTP/1.1\r\n"
+ + "HOST: 239.255.255.250:1900\r\n"
+ + "MAN: \"ssdp:discover\"\r\n"
+ + "MX: 3\r\n"
+ + "ST: ssdp:all\r\n\r\n";
+ return UTF8Encoding.ASCII.GetBytes(s);
+ }
+
+ public static byte[] EncodeUnicast(IPAddress gatewayAddress)
+ {
+ //Format obtained from http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.1.pdf pg 31
+ //This method only works with upnp 1.1 routers... unfortunately
+ string s = "M-SEARCH * HTTP/1.1\r\n"
+ + "HOST: " + gatewayAddress + ":1900\r\n"
+ + "MAN: \"ssdp:discover\"\r\n"
+ + "ST: ssdp:all\r\n\r\n";
+ //+ "USER-AGENT: unix/5.1 UPnP/1.1 MyProduct/1.0\r\n\r\n";
+ return UTF8Encoding.ASCII.GetBytes(s);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/ErrorMessage.cs b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
new file mode 100644
index 000000000..ce5270e9b
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/ErrorMessage.cs
@@ -0,0 +1,63 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+
+namespace Mono.Nat.Upnp
+{
+ internal class ErrorMessage : MessageBase
+ {
+ #region Member Variables
+ public string Description
+ {
+ get { return this.description; }
+ }
+ private string description;
+
+ public int ErrorCode
+ {
+ get { return this.errorCode; }
+ }
+ private int errorCode;
+ #endregion
+
+
+ #region Constructors
+ public ErrorMessage(int errorCode, string description)
+ :base(null)
+ {
+ this.description = description;
+ this.errorCode = errorCode;
+ }
+ #endregion
+
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/GetServicesMessage.cs b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
new file mode 100644
index 000000000..c5d7bce70
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/GetServicesMessage.cs
@@ -0,0 +1,62 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Diagnostics;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetServicesMessage : MessageBase
+ {
+ private string servicesDescriptionUrl;
+ private EndPoint hostAddress;
+
+ public GetServicesMessage(string description, EndPoint hostAddress)
+ :base(null)
+ {
+ if (string.IsNullOrEmpty(description))
+ Trace.WriteLine("Description is null");
+
+ if (hostAddress == null)
+ Trace.WriteLine("hostaddress is null");
+
+ this.servicesDescriptionUrl = description;
+ this.hostAddress = hostAddress;
+ }
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ HttpWebRequest req = (HttpWebRequest)WebRequest.Create("http://" + this.hostAddress.ToString() + this.servicesDescriptionUrl);
+ req.Headers.Add("ACCEPT-LANGUAGE", "en");
+ req.Method = "GET";
+
+ body = new byte[0];
+ return req;
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
new file mode 100644
index 000000000..da650fb41
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/CreatePortMappingMessage.cs
@@ -0,0 +1,75 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.IO;
+using System.Globalization;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class CreatePortMappingMessage : MessageBase
+ {
+ #region Private Fields
+
+ private IPAddress localIpAddress;
+ private Mapping mapping;
+
+ #endregion
+
+
+ #region Constructors
+ public CreatePortMappingMessage(Mapping mapping, IPAddress localIpAddress, UpnpNatDevice device)
+ : base(device)
+ {
+ this.mapping = mapping;
+ this.localIpAddress = localIpAddress;
+ }
+ #endregion
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ CultureInfo culture = CultureInfo.InvariantCulture;
+
+ StringBuilder builder = new StringBuilder(256);
+ XmlWriter writer = CreateWriter(builder);
+
+ WriteFullElement(writer, "NewRemoteHost", string.Empty);
+ WriteFullElement(writer, "NewExternalPort", this.mapping.PublicPort.ToString(culture));
+ WriteFullElement(writer, "NewProtocol", this.mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
+ WriteFullElement(writer, "NewInternalPort", this.mapping.PrivatePort.ToString(culture));
+ WriteFullElement(writer, "NewInternalClient", this.localIpAddress.ToString());
+ WriteFullElement(writer, "NewEnabled", "1");
+ WriteFullElement(writer, "NewPortMappingDescription", string.IsNullOrEmpty(mapping.Description) ? "Mono.Nat" : mapping.Description);
+ WriteFullElement(writer, "NewLeaseDuration", mapping.Lifetime.ToString());
+
+ writer.Flush();
+ return CreateRequest("AddPortMapping", builder.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
new file mode 100644
index 000000000..d9be89a69
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/DeletePortMappingMessage.cs
@@ -0,0 +1,57 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class DeletePortMappingMessage : MessageBase
+ {
+ private Mapping mapping;
+
+ public DeletePortMappingMessage(Mapping mapping, UpnpNatDevice device)
+ : base(device)
+ {
+ this.mapping = mapping;
+ }
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ StringBuilder builder = new StringBuilder(256);
+ XmlWriter writer = CreateWriter(builder);
+
+ WriteFullElement(writer, "NewRemoteHost", string.Empty);
+ WriteFullElement(writer, "NewExternalPort", mapping.PublicPort.ToString(MessageBase.Culture));
+ WriteFullElement(writer, "NewProtocol", mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP");
+
+ writer.Flush();
+ return CreateRequest("DeletePortMapping", builder.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
new file mode 100644
index 000000000..8f97002ea
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetExternalIPAddressMessage.cs
@@ -0,0 +1,51 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using System.IO;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetExternalIPAddressMessage : MessageBase
+ {
+
+ #region Constructors
+ public GetExternalIPAddressMessage(UpnpNatDevice device)
+ :base(device)
+ {
+ }
+ #endregion
+
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ return CreateRequest("GetExternalIPAddress", string.Empty, out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
new file mode 100644
index 000000000..c0c555881
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetGenericPortMappingEntry.cs
@@ -0,0 +1,55 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetGenericPortMappingEntry : MessageBase
+ {
+ private int index;
+
+ public GetGenericPortMappingEntry(int index, UpnpNatDevice device)
+ :base(device)
+ {
+ this.index = index;
+ }
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ XmlWriter writer = CreateWriter(sb);
+
+ WriteFullElement(writer, "NewPortMappingIndex", index.ToString());
+
+ writer.Flush();
+ return CreateRequest("GetGenericPortMappingEntry", sb.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
new file mode 100644
index 000000000..314468ece
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Requests/GetSpecificPortMappingEntryMessage.cs
@@ -0,0 +1,60 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetSpecificPortMappingEntryMessage : MessageBase
+ {
+ internal Protocol protocol;
+ internal int externalPort;
+
+ public GetSpecificPortMappingEntryMessage(Protocol protocol, int externalPort, UpnpNatDevice device)
+ : base(device)
+ {
+ this.protocol = protocol;
+ this.externalPort = externalPort;
+ }
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ StringBuilder sb = new StringBuilder(64);
+ XmlWriter writer = CreateWriter(sb);
+
+ WriteFullElement(writer, "NewRemoteHost", string.Empty);
+ WriteFullElement(writer, "NewExternalPort", externalPort.ToString());
+ WriteFullElement(writer, "NewProtocol", protocol == Protocol.Tcp ? "TCP" : "UDP");
+ writer.Flush();
+
+ return CreateRequest("GetSpecificPortMappingEntry", sb.ToString(), out body);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
new file mode 100644
index 000000000..e75926b09
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/CreatePortMappingResponseMessage.cs
@@ -0,0 +1,46 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+namespace Mono.Nat.Upnp
+{
+ internal class CreatePortMappingResponseMessage : MessageBase
+ {
+ #region Constructors
+ public CreatePortMappingResponseMessage()
+ :base(null)
+ {
+ }
+ #endregion
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
new file mode 100644
index 000000000..1fce4eb04
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/DeletePortMappingResponseMessage.cs
@@ -0,0 +1,44 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+
+
+using System;
+namespace Mono.Nat.Upnp
+{
+ internal class DeletePortMapResponseMessage : MessageBase
+ {
+ public DeletePortMapResponseMessage()
+ :base(null)
+ {
+ }
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
new file mode 100644
index 000000000..ee4b18cd1
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetExternalIPAddressResponseMessage.cs
@@ -0,0 +1,53 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetExternalIPAddressResponseMessage : MessageBase
+ {
+ public IPAddress ExternalIPAddress
+ {
+ get { return this.externalIPAddress; }
+ }
+ private IPAddress externalIPAddress;
+
+ public GetExternalIPAddressResponseMessage(string ip)
+ :base(null)
+ {
+ this.externalIPAddress = IPAddress.Parse(ip);
+ }
+
+ public override WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
new file mode 100644
index 000000000..b11bfa027
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/Responses/GetGenericPortMappingEntryResponseMessage.cs
@@ -0,0 +1,108 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+
+namespace Mono.Nat.Upnp
+{
+ internal class GetGenericPortMappingEntryResponseMessage : MessageBase
+ {
+ private string remoteHost;
+ private int externalPort;
+ private Protocol protocol;
+ private int internalPort;
+ private string internalClient;
+ private bool enabled;
+ private string portMappingDescription;
+ private int leaseDuration;
+
+ public string RemoteHost
+ {
+ get { return this.remoteHost; }
+ }
+
+ public int ExternalPort
+ {
+ get { return this.externalPort; }
+ }
+
+ public Protocol Protocol
+ {
+ get { return this.protocol; }
+ }
+
+ public int InternalPort
+ {
+ get { return this.internalPort; }
+ }
+
+ public string InternalClient
+ {
+ get { return this.internalClient; }
+ }
+
+ public bool Enabled
+ {
+ get { return this.enabled; }
+ }
+
+ public string PortMappingDescription
+ {
+ get { return this.portMappingDescription; }
+ }
+
+ public int LeaseDuration
+ {
+ get { return this.leaseDuration; }
+ }
+
+
+ public GetGenericPortMappingEntryResponseMessage(XmlNode data, bool genericMapping)
+ : base(null)
+ {
+ remoteHost = (genericMapping) ? data["NewRemoteHost"].InnerText : string.Empty;
+ externalPort = (genericMapping) ? Convert.ToInt32(data["NewExternalPort"].InnerText) : -1;
+ if (genericMapping)
+ protocol = data["NewProtocol"].InnerText.Equals("TCP", StringComparison.InvariantCultureIgnoreCase) ? Protocol.Tcp : Protocol.Udp;
+ else
+ protocol = Protocol.Udp;
+
+ internalPort = Convert.ToInt32(data["NewInternalPort"].InnerText);
+ internalClient = data["NewInternalClient"].InnerText;
+ enabled = data["NewEnabled"].InnerText == "1" ? true : false;
+ portMappingDescription = data["NewPortMappingDescription"].InnerText;
+ leaseDuration = Convert.ToInt32(data["NewLeaseDuration"].InnerText);
+ }
+
+ public override System.Net.WebRequest Encode(out byte[] body)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Messages/UpnpMessage.cs b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
new file mode 100644
index 000000000..44c16eec6
--- /dev/null
+++ b/Mono.Nat/Upnp/Messages/UpnpMessage.cs
@@ -0,0 +1,132 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+//
+// Copyright (C) 2006 Alan McGovern
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Diagnostics;
+using System.Xml;
+using System.Net;
+using System.IO;
+using System.Text;
+using System.Globalization;
+
+namespace Mono.Nat.Upnp
+{
+ internal abstract class MessageBase
+ {
+ internal static readonly CultureInfo Culture = CultureInfo.InvariantCulture;
+ protected UpnpNatDevice device;
+
+ protected MessageBase(UpnpNatDevice device)
+ {
+ this.device = device;
+ }
+
+ protected WebRequest CreateRequest(string upnpMethod, string methodParameters, out byte[] body)
+ {
+ string ss = "http://" + this.device.HostEndPoint.ToString() + this.device.ControlUrl;
+ NatUtility.Log("Initiating request to: {0}", ss);
+ Uri location = new Uri(ss);
+
+ HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(location);
+ req.KeepAlive = false;
+ req.Method = "POST";
+ req.ContentType = "text/xml; charset=\"utf-8\"";
+ req.Headers.Add("SOAPACTION", "\"" + device.ServiceType + "#" + upnpMethod + "\"");
+
+ string bodyString = "<s:Envelope "
+ + "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
+ + "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
+ + "<s:Body>"
+ + "<u:" + upnpMethod + " "
+ + "xmlns:u=\"" + device.ServiceType + "\">"
+ + methodParameters
+ + "</u:" + upnpMethod + ">"
+ + "</s:Body>"
+ + "</s:Envelope>\r\n\r\n";
+
+ body = System.Text.Encoding.UTF8.GetBytes(bodyString);
+ return req;
+ }
+
+ public static MessageBase Decode(UpnpNatDevice device, string message)
+ {
+ XmlNode node;
+ XmlDocument doc = new XmlDocument();
+ doc.LoadXml(message);
+
+ XmlNamespaceManager nsm = new XmlNamespaceManager(doc.NameTable);
+
+ // Error messages should be found under this namespace
+ nsm.AddNamespace("errorNs", "urn:schemas-upnp-org:control-1-0");
+ nsm.AddNamespace("responseNs", device.ServiceType);
+
+ // Check to see if we have a fault code message.
+ if ((node = doc.SelectSingleNode("//errorNs:UPnPError", nsm)) != null) {
+ string errorCode = node["errorCode"] != null ? node["errorCode"].InnerText : "";
+ string errorDescription = node["errorDescription"] != null ? node["errorDescription"].InnerText : "";
+
+ return new ErrorMessage(Convert.ToInt32(errorCode, CultureInfo.InvariantCulture), errorDescription);
+ }
+
+ if ((doc.SelectSingleNode("//responseNs:AddPortMappingResponse", nsm)) != null)
+ return new CreatePortMappingResponseMessage();
+
+ if ((doc.SelectSingleNode("//responseNs:DeletePortMappingResponse", nsm)) != null)
+ return new DeletePortMapResponseMessage();
+
+ if ((node = doc.SelectSingleNode("//responseNs:GetExternalIPAddressResponse", nsm)) != null) {
+ string newExternalIPAddress = node["NewExternalIPAddress"] != null ? node["NewExternalIPAddress"].InnerText : "";
+ return new GetExternalIPAddressResponseMessage(newExternalIPAddress);
+ }
+
+ if ((node = doc.SelectSingleNode("//responseNs:GetGenericPortMappingEntryResponse", nsm)) != null)
+ return new GetGenericPortMappingEntryResponseMessage(node, true);
+
+ if ((node = doc.SelectSingleNode("//responseNs:GetSpecificPortMappingEntryResponse", nsm)) != null)
+ return new GetGenericPortMappingEntryResponseMessage(node, false);
+
+ NatUtility.Log("Unknown message returned. Please send me back the following XML:");
+ NatUtility.Log(message);
+ return null;
+ }
+
+ public abstract WebRequest Encode(out byte[] body);
+
+ internal static void WriteFullElement(XmlWriter writer, string element, string value)
+ {
+ writer.WriteStartElement(element);
+ writer.WriteString(value);
+ writer.WriteEndElement();
+ }
+
+ internal static XmlWriter CreateWriter(StringBuilder sb)
+ {
+ XmlWriterSettings settings = new XmlWriterSettings();
+ settings.ConformanceLevel = ConformanceLevel.Fragment;
+ return XmlWriter.Create(sb, settings);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
new file mode 100644
index 000000000..edc5a5d76
--- /dev/null
+++ b/Mono.Nat/Upnp/Searchers/UpnpSearcher.cs
@@ -0,0 +1,287 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Net;
+using Mono.Nat.Upnp;
+using System.Diagnostics;
+using System.Net.Sockets;
+using System.Net.NetworkInformation;
+using MediaBrowser.Controller.Dlna;
+
+namespace Mono.Nat
+{
+ internal class UpnpSearcher : ISearcher
+ {
+ private const int SearchPeriod = 5 * 60; // The time in seconds between each search
+ static UpnpSearcher instance = new UpnpSearcher();
+ public static List<UdpClient> sockets = CreateSockets();
+
+ public static UpnpSearcher Instance
+ {
+ get { return instance; }
+ }
+
+ public event EventHandler<DeviceEventArgs> DeviceFound;
+ public event EventHandler<DeviceEventArgs> DeviceLost;
+
+ private List<INatDevice> devices;
+ private Dictionary<IPAddress, DateTime> lastFetched;
+ private DateTime nextSearch;
+ private IPEndPoint searchEndpoint;
+
+ UpnpSearcher()
+ {
+ devices = new List<INatDevice>();
+ lastFetched = new Dictionary<IPAddress, DateTime>();
+ //searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
+ searchEndpoint = new IPEndPoint(IPAddress.Parse("239.255.255.250"), 1900);
+ }
+
+ static List<UdpClient> CreateSockets()
+ {
+ List<UdpClient> clients = new List<UdpClient>();
+ try
+ {
+ foreach (NetworkInterface n in NetworkInterface.GetAllNetworkInterfaces())
+ {
+ foreach (UnicastIPAddressInformation address in n.GetIPProperties().UnicastAddresses)
+ {
+ if (address.Address.AddressFamily == AddressFamily.InterNetwork)
+ {
+ try
+ {
+ clients.Add(new UdpClient(new IPEndPoint(address.Address, 0)));
+ }
+ catch
+ {
+ continue; // Move on to the next address.
+ }
+ }
+ }
+ }
+ }
+ catch (Exception)
+ {
+ clients.Add(new UdpClient(0));
+ }
+ return clients;
+ }
+
+ public void Search()
+ {
+ foreach (UdpClient s in sockets)
+ {
+ try
+ {
+ Search(s);
+ }
+ catch
+ {
+ // Ignore any search errors
+ }
+ }
+ }
+
+ void Search(UdpClient client)
+ {
+ nextSearch = DateTime.Now.AddSeconds(SearchPeriod);
+ byte[] data = DiscoverDeviceMessage.EncodeSSDP();
+
+ // UDP is unreliable, so send 3 requests at a time (per Upnp spec, sec 1.1.2)
+ for (int i = 0; i < 3; i++)
+ client.Send(data, data.Length, searchEndpoint);
+ }
+
+ public IPEndPoint SearchEndpoint
+ {
+ get { return searchEndpoint; }
+ }
+
+ public void Handle(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint endpoint)
+ {
+ // No matter what, this method should never throw an exception. If something goes wrong
+ // we should still be in a position to handle the next reply correctly.
+ try
+ {
+ /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
+ Any other device type is no good to us for this purpose. See the IGP overview paper
+ page 5 for an overview of device types and their hierarchy.
+ http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+ version it is and apply the correct URN. */
+
+ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+ prefix. */
+
+ // We have an internet gateway device now
+ UpnpNatDevice d = new UpnpNatDevice(localAddress, deviceInfo, endpoint, string.Empty);
+
+ if (devices.Contains(d))
+ {
+ // We already have found this device, so we just refresh it to let people know it's
+ // Still alive. If a device doesn't respond to a search, we dump it.
+ devices[devices.IndexOf(d)].LastSeen = DateTime.Now;
+ }
+ else
+ {
+
+ // If we send 3 requests at a time, ensure we only fetch the services list once
+ // even if three responses are received
+ if (lastFetched.ContainsKey(endpoint.Address))
+ {
+ DateTime last = lastFetched[endpoint.Address];
+ if ((DateTime.Now - last) < TimeSpan.FromSeconds(20))
+ return;
+ }
+ lastFetched[endpoint.Address] = DateTime.Now;
+
+ // Once we've parsed the information we need, we tell the device to retrieve it's service list
+ // Once we successfully receive the service list, the callback provided will be invoked.
+ NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
+ d.GetServicesList(DeviceSetupComplete);
+ }
+ }
+ catch (Exception ex)
+ {
+ NatUtility.Log("Unhandled exception when trying to decode a device's response Send me the following data: ");
+ NatUtility.Log("ErrorMessage:");
+ NatUtility.Log(ex.Message);
+ }
+ }
+
+ public void Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ // Convert it to a string for easy parsing
+ string dataString = null;
+
+ // No matter what, this method should never throw an exception. If something goes wrong
+ // we should still be in a position to handle the next reply correctly.
+ try {
+ string urn;
+ dataString = Encoding.UTF8.GetString(response);
+
+ if (NatUtility.Verbose)
+ NatUtility.Log("UPnP Response: {0}", dataString);
+
+ /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
+ Any other device type is no good to us for this purpose. See the IGP overview paper
+ page 5 for an overview of device types and their hierarchy.
+ http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+ version it is and apply the correct URN. */
+
+ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+ prefix. */
+
+ string log = "UPnP Response: Router advertised a '{0}' service";
+ StringComparison c = StringComparison.OrdinalIgnoreCase;
+ if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1) {
+ urn = "urn:schemas-upnp-org:service:WANIPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1");
+ } else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1) {
+ urn = "urn:schemas-upnp-org:service:WANPPPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:");
+ } else
+ return;
+
+ // We have an internet gateway device now
+ UpnpNatDevice d = new UpnpNatDevice(localAddress, dataString, urn);
+
+ if (devices.Contains(d))
+ {
+ // We already have found this device, so we just refresh it to let people know it's
+ // Still alive. If a device doesn't respond to a search, we dump it.
+ devices[devices.IndexOf(d)].LastSeen = DateTime.Now;
+ }
+ else
+ {
+
+ // If we send 3 requests at a time, ensure we only fetch the services list once
+ // even if three responses are received
+ if (lastFetched.ContainsKey(endpoint.Address))
+ {
+ DateTime last = lastFetched[endpoint.Address];
+ if ((DateTime.Now - last) < TimeSpan.FromSeconds(20))
+ return;
+ }
+ lastFetched[endpoint.Address] = DateTime.Now;
+
+ // Once we've parsed the information we need, we tell the device to retrieve it's service list
+ // Once we successfully receive the service list, the callback provided will be invoked.
+ NatUtility.Log("Fetching service list: {0}", d.HostEndPoint);
+ d.GetServicesList(DeviceSetupComplete);
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine("Unhandled exception when trying to decode a device's response Send me the following data: ");
+ Trace.WriteLine("ErrorMessage:");
+ Trace.WriteLine(ex.Message);
+ Trace.WriteLine("Data string:");
+ Trace.WriteLine(dataString);
+ }
+ }
+
+ public DateTime NextSearch
+ {
+ get { return nextSearch; }
+ }
+
+ private void DeviceSetupComplete(INatDevice device)
+ {
+ lock (this.devices)
+ {
+ // We don't want the same device in there twice
+ if (devices.Contains(device))
+ return;
+
+ devices.Add(device);
+ }
+
+ OnDeviceFound(new DeviceEventArgs(device));
+ }
+
+ private void OnDeviceFound(DeviceEventArgs args)
+ {
+ if (DeviceFound != null)
+ DeviceFound(this, args);
+ }
+
+ public NatProtocol Protocol
+ {
+ get { return NatProtocol.Upnp; }
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/Upnp.cs b/Mono.Nat/Upnp/Upnp.cs
new file mode 100644
index 000000000..e44a51c24
--- /dev/null
+++ b/Mono.Nat/Upnp/Upnp.cs
@@ -0,0 +1,83 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+// Nicholas Terry <nick.i.terry@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+// Copyright (C) 2014 Nicholas Terry
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Text;
+
+namespace Mono.Nat.Upnp
+{
+ internal class Upnp
+ {
+ public UpnpNatDevice Handle(IPAddress localAddress, byte[] response, IPEndPoint endpoint)
+ {
+ // Convert it to a string for easy parsing
+ string dataString = null;
+
+
+ string urn;
+ dataString = Encoding.UTF8.GetString(response);
+
+ if (NatUtility.Verbose)
+ NatUtility.Log("UPnP Response: {0}", dataString);
+
+ /* For UPnP Port Mapping we need ot find either WANPPPConnection or WANIPConnection.
+ Any other device type is no good to us for this purpose. See the IGP overview paper
+ page 5 for an overview of device types and their hierarchy.
+ http://upnp.org/specs/gw/UPnP-gw-InternetGatewayDevice-v1-Device.pdf */
+
+ /* TODO: Currently we are assuming version 1 of the protocol. We should figure out which
+ version it is and apply the correct URN. */
+
+ /* Some routers don't correctly implement the version ID on the URN, so we only search for the type
+ prefix. */
+
+ string log = "UPnP Response: Router advertised a '{0}' service";
+ StringComparison c = StringComparison.OrdinalIgnoreCase;
+ if (dataString.IndexOf("urn:schemas-upnp-org:service:WANIPConnection:", c) != -1)
+ {
+ urn = "urn:schemas-upnp-org:service:WANIPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANIPConnection:1");
+ }
+ else if (dataString.IndexOf("urn:schemas-upnp-org:service:WANPPPConnection:", c) != -1)
+ {
+ urn = "urn:schemas-upnp-org:service:WANPPPConnection:1";
+ NatUtility.Log(log, "urn:schemas-upnp-org:service:WANPPPConnection:");
+ }
+ else
+ throw new NotSupportedException("Received non-supported device type");
+
+ // We have an internet gateway device now
+ return new UpnpNatDevice(localAddress, dataString, urn);
+ }
+ }
+}
diff --git a/Mono.Nat/Upnp/UpnpNatDevice.cs b/Mono.Nat/Upnp/UpnpNatDevice.cs
new file mode 100644
index 000000000..1160d3ac2
--- /dev/null
+++ b/Mono.Nat/Upnp/UpnpNatDevice.cs
@@ -0,0 +1,651 @@
+//
+// Authors:
+// Alan McGovern alan.mcgovern@gmail.com
+// Ben Motmans <ben.motmans@gmail.com>
+//
+// Copyright (C) 2006 Alan McGovern
+// Copyright (C) 2007 Ben Motmans
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using System.Net;
+using System.Xml;
+using System.Text;
+using System.Diagnostics;
+using MediaBrowser.Controller.Dlna;
+
+namespace Mono.Nat.Upnp
+{
+ public sealed class UpnpNatDevice : AbstractNatDevice, IEquatable<UpnpNatDevice>
+ {
+ private EndPoint hostEndPoint;
+ private IPAddress localAddress;
+ private string serviceDescriptionUrl;
+ private string controlUrl;
+ private string serviceType;
+
+ public override IPAddress LocalAddress
+ {
+ get { return localAddress; }
+ }
+
+ /// <summary>
+ /// The callback to invoke when we are finished setting up the device
+ /// </summary>
+ private NatDeviceCallback callback;
+
+ internal UpnpNatDevice(IPAddress localAddress, UpnpDeviceInfo deviceInfo, IPEndPoint hostEndPoint, string serviceType)
+ {
+ this.LastSeen = DateTime.Now;
+ this.localAddress = localAddress;
+
+ // Split the string at the "location" section so i can extract the ipaddress and service description url
+ string locationDetails = deviceInfo.Location.ToString();
+ this.serviceType = serviceType;
+
+ // Make sure we have no excess whitespace
+ locationDetails = locationDetails.Trim();
+
+ // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+ // Are we going to get addresses with the "http://" attached?
+ if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+ {
+ NatUtility.Log("Found device at: {0}", locationDetails);
+ // This bit strings out the "http://" from the string
+ locationDetails = locationDetails.Substring(7);
+
+ this.hostEndPoint = hostEndPoint;
+
+ NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
+
+ // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
+ // and port information
+ this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
+ }
+ else
+ {
+ NatUtility.Log("Couldn't decode address. Please send following string to the developer: ");
+ }
+ }
+
+ internal UpnpNatDevice (IPAddress localAddress, string deviceDetails, string serviceType)
+ {
+ this.LastSeen = DateTime.Now;
+ this.localAddress = localAddress;
+
+ // Split the string at the "location" section so i can extract the ipaddress and service description url
+ string locationDetails = deviceDetails.Substring(deviceDetails.IndexOf("Location", StringComparison.InvariantCultureIgnoreCase) + 9).Split('\r')[0];
+ this.serviceType = serviceType;
+
+ // Make sure we have no excess whitespace
+ locationDetails = locationDetails.Trim();
+
+ // FIXME: Is this reliable enough. What if we get a hostname as opposed to a proper http address
+ // Are we going to get addresses with the "http://" attached?
+ if (locationDetails.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase))
+ {
+ NatUtility.Log("Found device at: {0}", locationDetails);
+ // This bit strings out the "http://" from the string
+ locationDetails = locationDetails.Substring(7);
+
+ // We then split off the end of the string to get something like: 192.168.0.3:241 in our string
+ string hostAddressAndPort = locationDetails.Remove(locationDetails.IndexOf('/'));
+
+ // From this we parse out the IP address and Port
+ if (hostAddressAndPort.IndexOf(':') > 0)
+ {
+ this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort.Remove(hostAddressAndPort.IndexOf(':'))),
+ Convert.ToUInt16(hostAddressAndPort.Substring(hostAddressAndPort.IndexOf(':') + 1), System.Globalization.CultureInfo.InvariantCulture));
+ }
+ else
+ {
+ // there is no port specified, use default port (80)
+ this.hostEndPoint = new IPEndPoint(IPAddress.Parse(hostAddressAndPort), 80);
+ }
+
+ NatUtility.Log("Parsed device as: {0}", this.hostEndPoint.ToString());
+
+ // The service description URL is the remainder of the "locationDetails" string. The bit that was originally after the ip
+ // and port information
+ this.serviceDescriptionUrl = locationDetails.Substring(locationDetails.IndexOf('/'));
+ }
+ else
+ {
+ Trace.WriteLine("Couldn't decode address. Please send following string to the developer: ");
+ Trace.WriteLine(deviceDetails);
+ }
+ }
+
+ /// <summary>
+ /// The EndPoint that the device is at
+ /// </summary>
+ internal EndPoint HostEndPoint
+ {
+ get { return this.hostEndPoint; }
+ }
+
+ /// <summary>
+ /// The relative url of the xml file that describes the list of services is at
+ /// </summary>
+ internal string ServiceDescriptionUrl
+ {
+ get { return this.serviceDescriptionUrl; }
+ }
+
+ /// <summary>
+ /// The relative url that we can use to control the port forwarding
+ /// </summary>
+ internal string ControlUrl
+ {
+ get { return this.controlUrl; }
+ }
+
+ /// <summary>
+ /// The service type we're using on the device
+ /// </summary>
+ public string ServiceType
+ {
+ get { return serviceType; }
+ }
+
+ /// <summary>
+ /// Begins an async call to get the external ip address of the router
+ /// </summary>
+ public override IAsyncResult BeginGetExternalIP(AsyncCallback callback, object asyncState)
+ {
+ // Create the port map message
+ GetExternalIPAddressMessage message = new GetExternalIPAddressMessage(this);
+ return BeginMessageInternal(message, callback, asyncState, EndGetExternalIPInternal);
+ }
+
+ /// <summary>
+ /// Maps the specified port to this computer
+ /// </summary>
+ public override IAsyncResult BeginCreatePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ CreatePortMappingMessage message = new CreatePortMappingMessage(mapping, localAddress, this);
+ return BeginMessageInternal(message, callback, asyncState, EndCreatePortMapInternal);
+ }
+
+ /// <summary>
+ /// Removes a port mapping from this computer
+ /// </summary>
+ public override IAsyncResult BeginDeletePortMap(Mapping mapping, AsyncCallback callback, object asyncState)
+ {
+ DeletePortMappingMessage message = new DeletePortMappingMessage(mapping, this);
+ return BeginMessageInternal(message, callback, asyncState, EndDeletePortMapInternal);
+ }
+
+
+ public override IAsyncResult BeginGetAllMappings(AsyncCallback callback, object asyncState)
+ {
+ GetGenericPortMappingEntry message = new GetGenericPortMappingEntry(0, this);
+ return BeginMessageInternal(message, callback, asyncState, EndGetAllMappingsInternal);
+ }
+
+
+ public override IAsyncResult BeginGetSpecificMapping (Protocol protocol, int port, AsyncCallback callback, object asyncState)
+ {
+ GetSpecificPortMappingEntryMessage message = new GetSpecificPortMappingEntryMessage(protocol, port, this);
+ return this.BeginMessageInternal(message, callback, asyncState, new AsyncCallback(this.EndGetSpecificMappingInternal));
+ }
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="result"></param>
+ public override void EndCreatePortMap(IAsyncResult result)
+ {
+ if (result == null) throw new ArgumentNullException("result");
+
+ PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ // Check if we need to wait for the operation to finish
+ if (!result.IsCompleted)
+ result.AsyncWaitHandle.WaitOne();
+
+ // If we have a saved exception, it means something went wrong during the mapping
+ // so we just rethrow the exception and let the user figure out what they should do.
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ //return result.AsyncState as Mapping;
+ }
+
+
+ /// <summary>
+ ///
+ /// </summary>
+ /// <param name="result"></param>
+ public override void EndDeletePortMap(IAsyncResult result)
+ {
+ if (result == null)
+ throw new ArgumentNullException("result");
+
+ PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ // Check if we need to wait for the operation to finish
+ if (!mappingResult.IsCompleted)
+ mappingResult.AsyncWaitHandle.WaitOne();
+
+ // If we have a saved exception, it means something went wrong during the mapping
+ // so we just rethrow the exception and let the user figure out what they should do.
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ // If all goes well, we just return
+ //return true;
+ }
+
+
+ public override Mapping[] EndGetAllMappings(IAsyncResult result)
+ {
+ if (result == null)
+ throw new ArgumentNullException("result");
+
+ GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ if (!mappingResult.IsCompleted)
+ mappingResult.AsyncWaitHandle.WaitOne();
+
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ if (msg.ErrorCode != 713)
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ return mappingResult.Mappings.ToArray();
+ }
+
+
+ /// <summary>
+ /// Ends an async request to get the external ip address of the router
+ /// </summary>
+ public override IPAddress EndGetExternalIP(IAsyncResult result)
+ {
+ if (result == null) throw new ArgumentNullException("result");
+
+ PortMapAsyncResult mappingResult = result as PortMapAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ if (!result.IsCompleted)
+ result.AsyncWaitHandle.WaitOne();
+
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage msg = mappingResult.SavedMessage as ErrorMessage;
+ throw new MappingException(msg.ErrorCode, msg.Description);
+ }
+
+ if (mappingResult.SavedMessage == null)
+ return null;
+ else
+ return ((GetExternalIPAddressResponseMessage)mappingResult.SavedMessage).ExternalIPAddress;
+ }
+
+
+ public override Mapping EndGetSpecificMapping(IAsyncResult result)
+ {
+ if (result == null)
+ throw new ArgumentNullException("result");
+
+ GetAllMappingsAsyncResult mappingResult = result as GetAllMappingsAsyncResult;
+ if (mappingResult == null)
+ throw new ArgumentException("Invalid AsyncResult", "result");
+
+ if (!mappingResult.IsCompleted)
+ mappingResult.AsyncWaitHandle.WaitOne();
+
+ if (mappingResult.SavedMessage is ErrorMessage)
+ {
+ ErrorMessage message = mappingResult.SavedMessage as ErrorMessage;
+ if (message.ErrorCode != 0x2ca)
+ {
+ throw new MappingException(message.ErrorCode, message.Description);
+ }
+ }
+ if (mappingResult.Mappings.Count == 0)
+ return new Mapping (Protocol.Tcp, -1, -1);
+
+ return mappingResult.Mappings[0];
+ }
+
+
+ public override bool Equals(object obj)
+ {
+ UpnpNatDevice device = obj as UpnpNatDevice;
+ return (device == null) ? false : this.Equals((device));
+ }
+
+
+ public bool Equals(UpnpNatDevice other)
+ {
+ return (other == null) ? false : (this.hostEndPoint.Equals(other.hostEndPoint)
+ //&& this.controlUrl == other.controlUrl
+ && this.serviceDescriptionUrl == other.serviceDescriptionUrl);
+ }
+
+ public override int GetHashCode()
+ {
+ return (this.hostEndPoint.GetHashCode() ^ this.controlUrl.GetHashCode() ^ this.serviceDescriptionUrl.GetHashCode());
+ }
+
+ private IAsyncResult BeginMessageInternal(MessageBase message, AsyncCallback storedCallback, object asyncState, AsyncCallback callback)
+ {
+ byte[] body;
+ WebRequest request = message.Encode(out body);
+ PortMapAsyncResult mappingResult = PortMapAsyncResult.Create(message, request, storedCallback, asyncState);
+
+ if (body.Length > 0)
+ {
+ request.ContentLength = body.Length;
+ request.BeginGetRequestStream(delegate(IAsyncResult result) {
+ try
+ {
+ Stream s = request.EndGetRequestStream(result);
+ s.Write(body, 0, body.Length);
+ request.BeginGetResponse(callback, mappingResult);
+ }
+ catch (Exception ex)
+ {
+ mappingResult.Complete(ex);
+ }
+ }, null);
+ }
+ else
+ {
+ request.BeginGetResponse(callback, mappingResult);
+ }
+ return mappingResult;
+ }
+
+ private void CompleteMessage(IAsyncResult result)
+ {
+ PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
+ mappingResult.CompletedSynchronously = result.CompletedSynchronously;
+ mappingResult.Complete();
+ }
+
+ private MessageBase DecodeMessageFromResponse(Stream s, long length)
+ {
+ StringBuilder data = new StringBuilder();
+ int bytesRead = 0;
+ int totalBytesRead = 0;
+ byte[] buffer = new byte[10240];
+
+ // Read out the content of the message, hopefully picking everything up in the case where we have no contentlength
+ if (length != -1)
+ {
+ while (totalBytesRead < length)
+ {
+ bytesRead = s.Read(buffer, 0, buffer.Length);
+ data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+ totalBytesRead += bytesRead;
+ }
+ }
+ else
+ {
+ while ((bytesRead = s.Read(buffer, 0, buffer.Length)) != 0)
+ data.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+ }
+
+ // Once we have our content, we need to see what kind of message it is. It'll either a an error
+ // or a response based on the action we performed.
+ return MessageBase.Decode(this, data.ToString());
+ }
+
+ private void EndCreatePortMapInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+ CompleteMessage(result);
+ }
+
+ private void EndMessageInternal(IAsyncResult result)
+ {
+ HttpWebResponse response = null;
+ PortMapAsyncResult mappingResult = result.AsyncState as PortMapAsyncResult;
+
+ try
+ {
+ try
+ {
+ response = (HttpWebResponse)mappingResult.Request.EndGetResponse(result);
+ }
+ catch (WebException ex)
+ {
+ // Even if the request "failed" i want to continue on to read out the response from the router
+ response = ex.Response as HttpWebResponse;
+ if (response == null)
+ mappingResult.SavedMessage = new ErrorMessage((int)ex.Status, ex.Message);
+ }
+ if (response != null)
+ mappingResult.SavedMessage = DecodeMessageFromResponse(response.GetResponseStream(), response.ContentLength);
+ }
+
+ finally
+ {
+ if (response != null)
+ response.Close();
+ }
+ }
+
+ private void EndDeletePortMapInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+ CompleteMessage(result);
+ }
+
+ private void EndGetAllMappingsInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+
+ GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
+ GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
+ if (message != null)
+ {
+ Mapping mapping = new Mapping (message.Protocol, message.InternalPort, message.ExternalPort, message.LeaseDuration);
+ mapping.Description = message.PortMappingDescription;
+ mappingResult.Mappings.Add(mapping);
+ GetGenericPortMappingEntry next = new GetGenericPortMappingEntry(mappingResult.Mappings.Count, this);
+
+ // It's ok to do this synchronously because we should already be on anther thread
+ // and this won't block the user.
+ byte[] body;
+ WebRequest request = next.Encode(out body);
+ if (body.Length > 0)
+ {
+ request.ContentLength = body.Length;
+ request.GetRequestStream().Write(body, 0, body.Length);
+ }
+ mappingResult.Request = request;
+ request.BeginGetResponse(EndGetAllMappingsInternal, mappingResult);
+ return;
+ }
+
+ CompleteMessage(result);
+ }
+
+ private void EndGetExternalIPInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+ CompleteMessage(result);
+ }
+
+ private void EndGetSpecificMappingInternal(IAsyncResult result)
+ {
+ EndMessageInternal(result);
+
+ GetAllMappingsAsyncResult mappingResult = result.AsyncState as GetAllMappingsAsyncResult;
+ GetGenericPortMappingEntryResponseMessage message = mappingResult.SavedMessage as GetGenericPortMappingEntryResponseMessage;
+ if (message != null) {
+ Mapping mapping = new Mapping(mappingResult.SpecificMapping.Protocol, message.InternalPort, mappingResult.SpecificMapping.PublicPort, message.LeaseDuration);
+ mapping.Description = mappingResult.SpecificMapping.Description;
+ mappingResult.Mappings.Add(mapping);
+ }
+
+ CompleteMessage(result);
+ }
+
+ internal void GetServicesList(NatDeviceCallback callback)
+ {
+ // Save the callback so i can use it again later when i've finished parsing the services available
+ this.callback = callback;
+
+ // Create a HTTPWebRequest to download the list of services the device offers
+ byte[] body;
+ WebRequest request = new GetServicesMessage(this.serviceDescriptionUrl, this.hostEndPoint).Encode(out body);
+ if (body.Length > 0)
+ NatUtility.Log("Error: Services Message contained a body");
+ request.BeginGetResponse(this.ServicesReceived, request);
+ }
+
+ private void ServicesReceived(IAsyncResult result)
+ {
+ HttpWebResponse response = null;
+ try
+ {
+ int abortCount = 0;
+ int bytesRead = 0;
+ byte[] buffer = new byte[10240];
+ StringBuilder servicesXml = new StringBuilder();
+ XmlDocument xmldoc = new XmlDocument();
+ HttpWebRequest request = result.AsyncState as HttpWebRequest;
+ response = request.EndGetResponse(result) as HttpWebResponse;
+ Stream s = response.GetResponseStream();
+
+ if (response.StatusCode != HttpStatusCode.OK) {
+ NatUtility.Log("{0}: Couldn't get services list: {1}", HostEndPoint, response.StatusCode);
+ return; // FIXME: This the best thing to do??
+ }
+
+ while (true)
+ {
+ bytesRead = s.Read(buffer, 0, buffer.Length);
+ servicesXml.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
+ try
+ {
+ xmldoc.LoadXml(servicesXml.ToString());
+ response.Close();
+ break;
+ }
+ catch (XmlException)
+ {
+ // If we can't receive the entire XML within 500ms, then drop the connection
+ // Unfortunately not all routers supply a valid ContentLength (mine doesn't)
+ // so this hack is needed to keep testing our recieved data until it gets successfully
+ // parsed by the xmldoc. Without this, the code will never pick up my router.
+ if (abortCount++ > 50)
+ {
+ response.Close();
+ return;
+ }
+ NatUtility.Log("{0}: Couldn't parse services list", HostEndPoint);
+ System.Threading.Thread.Sleep(10);
+ }
+ }
+
+ NatUtility.Log("{0}: Parsed services list", HostEndPoint);
+ XmlNamespaceManager ns = new XmlNamespaceManager(xmldoc.NameTable);
+ ns.AddNamespace("ns", "urn:schemas-upnp-org:device-1-0");
+ XmlNodeList nodes = xmldoc.SelectNodes("//*/ns:serviceList", ns);
+
+ foreach (XmlNode node in nodes)
+ {
+ //Go through each service there
+ foreach (XmlNode service in node.ChildNodes)
+ {
+ //If the service is a WANIPConnection, then we have what we want
+ string type = service["serviceType"].InnerText;
+ NatUtility.Log("{0}: Found service: {1}", HostEndPoint, type);
+ StringComparison c = StringComparison.OrdinalIgnoreCase;
+ // TODO: Add support for version 2 of UPnP.
+ if (type.Equals("urn:schemas-upnp-org:service:WANPPPConnection:1", c) ||
+ type.Equals("urn:schemas-upnp-org:service:WANIPConnection:1", c))
+ {
+ this.controlUrl = service["controlURL"].InnerText;
+ NatUtility.Log("{0}: Found upnp service at: {1}", HostEndPoint, controlUrl);
+ try
+ {
+ Uri u = new Uri(controlUrl);
+ if (u.IsAbsoluteUri)
+ {
+ EndPoint old = hostEndPoint;
+ this.hostEndPoint = new IPEndPoint(IPAddress.Parse(u.Host), u.Port);
+ NatUtility.Log("{0}: Absolute URI detected. Host address is now: {1}", old, HostEndPoint);
+ this.controlUrl = controlUrl.Substring(u.GetLeftPart(UriPartial.Authority).Length);
+ NatUtility.Log("{0}: New control url: {1}", HostEndPoint, controlUrl);
+ }
+ }
+ catch
+ {
+ NatUtility.Log("{0}: Assuming control Uri is relative: {1}", HostEndPoint, controlUrl);
+ }
+ NatUtility.Log("{0}: Handshake Complete", HostEndPoint);
+ this.callback(this);
+ return;
+ }
+ }
+ }
+
+ //If we get here, it means that we didn't get WANIPConnection service, which means no uPnP forwarding
+ //So we don't invoke the callback, so this device is never added to our lists
+ }
+ catch (WebException ex)
+ {
+ // Just drop the connection, FIXME: Should i retry?
+ NatUtility.Log("{0}: Device denied the connection attempt: {1}", HostEndPoint, ex);
+ }
+ finally
+ {
+ if (response != null)
+ response.Close();
+ }
+ }
+
+ /// <summary>
+ /// Overridden.
+ /// </summary>
+ /// <returns></returns>
+ public override string ToString( )
+ {
+ //GetExternalIP is blocking and can throw exceptions, can't use it here.
+ return String.Format(
+ "UpnpNatDevice - EndPoint: {0}, External IP: {1}, Control Url: {2}, Service Description Url: {3}, Service Type: {4}, Last Seen: {5}",
+ this.hostEndPoint, "Manually Check" /*this.GetExternalIP()*/, this.controlUrl, this.serviceDescriptionUrl, this.serviceType, this.LastSeen);
+ }
+ }
+} \ No newline at end of file
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec
index d1c4c0b44..ff52aebce 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.655</version>
+ <version>3.0.658</version>
<title>MediaBrowser.Common.Internal</title>
<authors>Luke</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
<copyright>Copyright © Emby 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.655" />
+ <dependency id="MediaBrowser.Common" version="3.0.658" />
<dependency id="NLog" version="4.3.6" />
<dependency id="SimpleInjector" version="3.2.0" />
</dependencies>
diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec
index ffee84de2..822246cf8 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.655</version>
+ <version>3.0.658</version>
<title>MediaBrowser.Common</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec
index 73c51ca35..6b8e2aa65 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.655</version>
+ <version>3.0.658</version>
<title>Media Browser.Server.Core</title>
<authors>Emby Team</authors>
<owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
<description>Contains core components required to build plugins for Emby Server.</description>
<copyright>Copyright © Emby 2013</copyright>
<dependencies>
- <dependency id="MediaBrowser.Common" version="3.0.655" />
+ <dependency id="MediaBrowser.Common" version="3.0.658" />
<dependency id="Interfaces.IO" version="1.0.0.5" />
</dependencies>
</metadata>