aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Server.Implementations
diff options
context:
space:
mode:
authorLuke <luke.pulverenti@gmail.com>2016-03-02 16:06:25 -0500
committerLuke <luke.pulverenti@gmail.com>2016-03-02 16:06:25 -0500
commit8fc7d7ba026ed871524055738dc33ddcac5e674d (patch)
treeb29a3df950c4c4d560701dc4bd1a9a2529dc53ea /MediaBrowser.Server.Implementations
parent9638b242a4c8f614ed4ffa256422cd0ba3a029e2 (diff)
parent81e96ed4f678b4de114e9d03844141ae65b5856b (diff)
Merge pull request #1514 from MediaBrowser/beta
Beta
Diffstat (limited to 'MediaBrowser.Server.Implementations')
-rw-r--r--MediaBrowser.Server.Implementations/Connect/ConnectManager.cs145
-rw-r--r--MediaBrowser.Server.Implementations/Dto/DtoService.cs27
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs14
-rw-r--r--MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs8
-rw-r--r--MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs70
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs6
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs9
-rw-r--r--MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs5
-rw-r--r--MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs23
-rw-r--r--MediaBrowser.Server.Implementations/Library/LibraryManager.cs23
-rw-r--r--MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs60
-rw-r--r--MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs23
-rw-r--r--MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs15
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs7
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs127
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs16
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs11
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs13
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs10
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs5
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs3
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs144
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs2
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs3
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs74
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs81
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs132
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs199
-rw-r--r--MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs186
-rw-r--r--MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj11
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs6
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs9
-rw-r--r--MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs25
-rw-r--r--MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs42
-rw-r--r--MediaBrowser.Server.Implementations/Session/SessionManager.cs81
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncHelper.cs4
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs57
-rw-r--r--MediaBrowser.Server.Implementations/Sync/SyncManager.cs14
-rw-r--r--MediaBrowser.Server.Implementations/packages.config6
39 files changed, 1177 insertions, 519 deletions
diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
index fdc7e9ee2..d7477225c 100644
--- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
+++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
@@ -10,7 +10,6 @@ using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Security;
using MediaBrowser.Model.Connect;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Events;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Serialization;
@@ -24,7 +23,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
-using MediaBrowser.Common.IO;
namespace MediaBrowser.Server.Implementations.Connect
{
@@ -121,7 +119,6 @@ namespace MediaBrowser.Server.Implementations.Connect
_securityManager = securityManager;
_fileSystem = fileSystem;
- _userManager.UserConfigurationUpdated += _userManager_UserConfigurationUpdated;
_config.ConfigurationUpdated += _config_ConfigurationUpdated;
LoadCachedData();
@@ -1071,90 +1068,6 @@ namespace MediaBrowser.Server.Implementations.Connect
}
}
- public async Task<ConnectSupporterSummary> GetConnectSupporterSummary()
- {
- var url = GetConnectUrl("keyAssociation");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None
- };
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"supporterKey", _securityManager.SupporterKey}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
- {
- return _json.DeserializeFromStream<ConnectSupporterSummary>(stream);
- }
- }
-
- public async Task AddConnectSupporter(string id)
- {
- var url = GetConnectUrl("keyAssociation");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None
- };
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"supporterKey", _securityManager.SupporterKey},
- {"userId", id}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
- {
- }
- }
-
- public async Task RemoveConnectSupporter(string id)
- {
- var url = GetConnectUrl("keyAssociation");
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = CancellationToken.None
- };
-
- var postData = new Dictionary<string, string>
- {
- {"serverId", ConnectServerId},
- {"supporterKey", _securityManager.SupporterKey},
- {"userId", id}
- };
-
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "DELETE").ConfigureAwait(false)).Content)
- {
- }
- }
-
public async Task Authenticate(string username, string passwordMd5)
{
if (string.IsNullOrWhiteSpace(username))
@@ -1186,64 +1099,6 @@ namespace MediaBrowser.Server.Implementations.Connect
}
}
- async void _userManager_UserConfigurationUpdated(object sender, GenericEventArgs<User> e)
- {
- var user = e.Argument;
-
- await TryUploadUserPreferences(user, CancellationToken.None).ConfigureAwait(false);
- }
-
- private async Task TryUploadUserPreferences(User user, CancellationToken cancellationToken)
- {
- if (user == null)
- {
- throw new ArgumentNullException("user");
- }
-
- if (string.IsNullOrEmpty(user.ConnectUserId))
- {
- return;
- }
- if (string.IsNullOrEmpty(ConnectAccessKey))
- {
- return;
- }
-
- var url = GetConnectUrl("user/preferences");
- url += "?userId=" + user.ConnectUserId;
- url += "&key=userpreferences";
-
- var options = new HttpRequestOptions
- {
- Url = url,
- CancellationToken = cancellationToken
- };
-
- var postData = new Dictionary<string, string>();
- postData["data"] = _json.SerializeToString(ConnectUserPreferences.FromUserConfiguration(user.Configuration));
- options.SetPostData(postData);
-
- SetServerAccessToken(options);
- SetApplicationHeader(options);
-
- try
- {
- // No need to examine the response
- using (var stream = (await _httpClient.SendAsync(options, "POST").ConfigureAwait(false)).Content)
- {
- }
- }
- catch (Exception ex)
- {
- _logger.ErrorException("Error uploading user preferences", ex);
- }
- }
-
- private async Task DownloadUserPreferences(User user, CancellationToken cancellationToken)
- {
-
- }
-
public async Task<User> GetLocalUser(string connectUserId)
{
var user = _userManager.Users
diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
index 590c5fd3f..a19a122c3 100644
--- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs
+++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs
@@ -26,6 +26,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Server.Implementations.Dto
@@ -92,11 +93,17 @@ namespace MediaBrowser.Server.Implementations.Dto
var syncDictionary = GetSyncedItemProgressDictionary(syncJobItems);
var list = new List<BaseItemDto>();
+ var programTuples = new List<Tuple<BaseItem, BaseItemDto>> { };
foreach (var item in items)
{
var dto = GetBaseItemDtoInternal(item, options, syncDictionary, user, owner);
+ if (item is LiveTvProgram)
+ {
+ programTuples.Add(new Tuple<BaseItem, BaseItemDto>(item, dto));
+ }
+
var byName = item as IItemByName;
if (byName != null)
@@ -118,6 +125,12 @@ namespace MediaBrowser.Server.Implementations.Dto
list.Add(dto);
}
+ if (programTuples.Count > 0)
+ {
+ var task = _livetvManager().AddInfoToProgramDto(programTuples, options.Fields, user);
+ Task.WaitAll(task);
+ }
+
return list;
}
@@ -139,6 +152,13 @@ namespace MediaBrowser.Server.Implementations.Dto
var dto = GetBaseItemDtoInternal(item, options, GetSyncedItemProgressDictionary(syncProgress), user, owner);
+ if (item is LiveTvProgram)
+ {
+ var list = new List<Tuple<BaseItem, BaseItemDto>> { new Tuple<BaseItem, BaseItemDto>(item, dto) };
+ var task = _livetvManager().AddInfoToProgramDto(list, options.Fields, user);
+ Task.WaitAll(task);
+ }
+
var byName = item as IItemByName;
if (byName != null)
@@ -393,11 +413,6 @@ namespace MediaBrowser.Server.Implementations.Dto
_livetvManager().AddInfoToRecordingDto(item, dto, user);
}
- else if (item is LiveTvProgram)
- {
- _livetvManager().AddInfoToProgramDto(item, dto, fields, user);
- }
-
return dto;
}
@@ -639,6 +654,8 @@ namespace MediaBrowser.Server.Implementations.Dto
private IEnumerable<string> GetCacheTags(BaseItem item, ImageType type, int limit)
{
return item.GetImages(type)
+ // Convert to a list now in case GetImageCacheTag is slow
+ .ToList()
.Select(p => GetImageCacheTag(item, p))
.Where(i => i != null)
.Take(limit)
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 51a104241..703096a2b 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -213,8 +213,18 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
if (userSessions.Count > 0)
{
- var info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo,
- foldersRemovedFrom, id);
+ LibraryUpdateInfo info;
+
+ try
+ {
+ info = GetLibraryUpdateInfo(itemsAdded, itemsUpdated, itemsRemoved, foldersAddedTo,
+ foldersRemovedFrom, id);
+ }
+ catch (Exception ex)
+ {
+ _logger.ErrorException("Error in GetLibraryUpdateInfo", ex);
+ return;
+ }
foreach (var userSession in userSessions)
{
diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
index be2817fd2..2473f3af6 100644
--- a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
+++ b/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs
@@ -57,7 +57,9 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
CancellationToken = cancellationToken,
// Seeing block length errors
- EnableHttpCompression = false
+ EnableHttpCompression = false,
+
+ LogRequest = false
};
options.SetPostData(data);
@@ -99,7 +101,9 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
CancellationToken = cancellationToken,
// Seeing block length errors
- EnableHttpCompression = false
+ EnableHttpCompression = false,
+
+ LogRequest = false
};
options.SetPostData(data);
diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
index 9d43dabcd..f9e167a8f 100644
--- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
+++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs
@@ -64,6 +64,13 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
FileSize = new FileInfo(path).Length
};
+ if (_libraryMonitor.IsPathLocked(path))
+ {
+ result.Status = FileSortingStatus.Failure;
+ result.StatusMessage = "Path is locked by other processes. Please try again later.";
+ return result;
+ }
+
var namingOptions = ((LibraryManager)_libraryManager).GetNamingOptions();
var resolver = new Naming.TV.EpisodeResolver(namingOptions, new PatternsLogger());
@@ -135,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
if (previousResult != null)
{
// Don't keep saving the same result over and over if nothing has changed
- if (previousResult.Status == result.Status && result.Status != FileSortingStatus.Success)
+ if (previousResult.Status == result.Status && previousResult.StatusMessage == result.StatusMessage && result.Status != FileSortingStatus.Success)
{
return previousResult;
}
@@ -150,7 +157,43 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{
var result = _organizationService.GetResult(request.ResultId);
- var series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId));
+ Series series = null;
+
+ if (request.NewSeriesProviderIds.Count > 0)
+ {
+ // We're having a new series here
+ SeriesInfo seriesRequest = new SeriesInfo();
+ seriesRequest.ProviderIds = request.NewSeriesProviderIds;
+
+ var refreshOptions = new MetadataRefreshOptions(_fileSystem);
+ series = new Series();
+ series.Id = Guid.NewGuid();
+ series.Name = request.NewSeriesName;
+
+ int year;
+ if (int.TryParse(request.NewSeriesYear, out year))
+ {
+ series.ProductionYear = year;
+ }
+
+ var seriesFolderName = series.Name;
+ if (series.ProductionYear.HasValue)
+ {
+ seriesFolderName = string.Format("{0} ({1})", seriesFolderName, series.ProductionYear);
+ }
+
+ series.Path = Path.Combine(request.TargetFolder, seriesFolderName);
+
+ series.ProviderIds = request.NewSeriesProviderIds;
+
+ await series.RefreshMetadata(refreshOptions, cancellationToken);
+ }
+
+ if (series == null)
+ {
+ // Existing Series
+ series = (Series)_libraryManager.GetItemById(new Guid(request.SeriesId));
+ }
await OrganizeEpisode(result.OriginalPath,
series,
@@ -243,16 +286,29 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
{
if (options.TvOptions.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath))
{
- _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath);
+ var msg = string.Format("File '{0}' already copied to new path '{1}', stopping organization", sourcePath, newPath);
+ _logger.Info(msg);
+ result.Status = FileSortingStatus.SkippedExisting;
+ result.StatusMessage = msg;
+ return;
+ }
+
+ if (fileExists)
+ {
+ var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, newPath);
+ _logger.Info(msg);
result.Status = FileSortingStatus.SkippedExisting;
- result.StatusMessage = string.Empty;
+ result.StatusMessage = msg;
+ result.TargetPath = newPath;
return;
}
- if (fileExists || otherDuplicatePaths.Count > 0)
+ if (otherDuplicatePaths.Count > 0)
{
+ var msg = string.Format("File '{0}' already exists as '{1}', stopping organization", sourcePath, otherDuplicatePaths);
+ _logger.Info(msg);
result.Status = FileSortingStatus.SkippedExisting;
- result.StatusMessage = string.Empty;
+ result.StatusMessage = msg;
result.DuplicatePaths = otherDuplicatePaths;
return;
}
@@ -502,7 +558,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
}
}
- return series ?? new Series();
+ return series;
}
/// <summary>
diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
index e69ff367a..c284007f7 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
@@ -348,6 +348,12 @@ namespace MediaBrowser.Server.Implementations.HttpServer
return Task.FromResult(true);
}
+ if (string.Equals(localPath, "/emby/pin", StringComparison.OrdinalIgnoreCase))
+ {
+ httpRes.RedirectToUrl("web/pin.html");
+ return Task.FromResult(true);
+ }
+
if (!string.IsNullOrWhiteSpace(GlobalResponse))
{
httpRes.StatusCode = 503;
diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
index f6b14fcab..d8f7d889c 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
@@ -134,20 +134,17 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
private bool IsExemptFromAuthenticationToken(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues)
{
- if (!_config.Configuration.IsStartupWizardCompleted &&
- authAttribtues.AllowBeforeStartupWizard)
+ if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
{
return true;
}
- return _config.Configuration.InsecureApps9.Contains(auth.Client ?? string.Empty,
- StringComparer.OrdinalIgnoreCase);
+ return false;
}
private bool IsExemptFromRoles(AuthorizationInfo auth, IAuthenticationAttributes authAttribtues, AuthenticationInfo tokenInfo)
{
- if (!_config.Configuration.IsStartupWizardCompleted &&
- authAttribtues.AllowBeforeStartupWizard)
+ if (!_config.Configuration.IsStartupWizardCompleted && authAttribtues.AllowBeforeStartupWizard)
{
return true;
}
diff --git a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
index 75d54a80a..357f5c976 100644
--- a/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
+++ b/MediaBrowser.Server.Implementations/HttpServer/Security/AuthorizationContext.cs
@@ -45,7 +45,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
{
var auth = GetAuthorizationDictionary(httpReq);
- string userId = null;
string deviceId = null;
string device = null;
string client = null;
@@ -53,9 +52,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
if (auth != null)
{
- // TODO: Remove this
- auth.TryGetValue("UserId", out userId);
-
auth.TryGetValue("DeviceId", out deviceId);
auth.TryGetValue("Device", out device);
auth.TryGetValue("Client", out client);
@@ -78,7 +74,6 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
Client = client,
Device = device,
DeviceId = deviceId,
- UserId = userId,
Version = version,
Token = token
};
diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
index 764eb7c68..0559e08ea 100644
--- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
+++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
@@ -78,6 +78,12 @@ namespace MediaBrowser.Server.Implementations.IO
TemporarilyIgnore(path);
}
+ public bool IsPathLocked(string path)
+ {
+ var lockedPaths = _tempIgnoredPaths.Keys.ToList();
+ return lockedPaths.Any(i => string.Equals(i, path, StringComparison.OrdinalIgnoreCase) || _fileSystem.ContainsSubPath(i, path));
+ }
+
public async void ReportFileSystemChangeComplete(string path, bool refreshPath)
{
if (string.IsNullOrEmpty(path))
@@ -658,7 +664,7 @@ namespace MediaBrowser.Server.Implementations.IO
while (item == null && !string.IsNullOrEmpty(path))
{
- item = LibraryManager.RootFolder.FindByPath(path);
+ item = LibraryManager.FindByPath(path);
path = Path.GetDirectoryName(path);
}
@@ -690,8 +696,21 @@ namespace MediaBrowser.Server.Implementations.IO
foreach (var watcher in _fileSystemWatchers.Values.ToList())
{
+ watcher.Created -= watcher_Changed;
+ watcher.Deleted -= watcher_Changed;
+ watcher.Renamed -= watcher_Changed;
watcher.Changed -= watcher_Changed;
- watcher.EnableRaisingEvents = false;
+
+ try
+ {
+ watcher.EnableRaisingEvents = false;
+ }
+ catch (InvalidOperationException)
+ {
+ // Seeing this under mono on linux sometimes
+ // Collection was modified; enumeration operation may not execute.
+ }
+
watcher.Dispose();
}
diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
index 333b1fbe9..721603efe 100644
--- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs
@@ -788,6 +788,29 @@ namespace MediaBrowser.Server.Implementations.Library
return _userRootFolder;
}
+ public BaseItem FindByPath(string path)
+ {
+ var query = new InternalItemsQuery
+ {
+ Path = path
+ };
+
+ // Only use the database result if there's exactly one item, otherwise we run the risk of returning old data that hasn't been cleaned yet.
+ var items = GetItemIds(query).Select(GetItemById).Where(i => i != null).ToArray();
+
+ if (items.Length == 1)
+ {
+ return items[0];
+ }
+
+ if (items.Length == 0)
+ {
+ return null;
+ }
+
+ return RootFolder.FindByPath(path);
+ }
+
/// <summary>
/// Gets a Person
/// </summary>
diff --git a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
index b132eedec..e4a085f42 100644
--- a/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
+++ b/MediaBrowser.Server.Implementations/Library/MediaSourceManager.cs
@@ -30,8 +30,9 @@ namespace MediaBrowser.Server.Implementations.Library
private IMediaSourceProvider[] _providers;
private readonly ILogger _logger;
+ private readonly IUserDataManager _userDataManager;
- public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
+ public MediaSourceManager(IItemRepository itemRepo, IUserManager userManager, ILibraryManager libraryManager, ILogger logger, IJsonSerializer jsonSerializer, IFileSystem fileSystem, IUserDataManager userDataManager)
{
_itemRepo = itemRepo;
_userManager = userManager;
@@ -39,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.Library
_logger = logger;
_jsonSerializer = jsonSerializer;
_fileSystem = fileSystem;
+ _userDataManager = userDataManager;
}
public void AddParts(IEnumerable<IMediaSourceProvider> providers)
@@ -140,7 +142,7 @@ namespace MediaBrowser.Server.Implementations.Library
{
if (user != null)
{
- SetUserProperties(source, user);
+ SetUserProperties(hasMediaSources, source, user);
}
if (source.Protocol == MediaProtocol.File)
{
@@ -257,25 +259,38 @@ namespace MediaBrowser.Server.Implementations.Library
{
foreach (var source in sources)
{
- SetUserProperties(source, user);
+ SetUserProperties(item, source, user);
}
}
return sources;
}
- private void SetUserProperties(MediaSourceInfo source, User user)
+ private void SetUserProperties(IHasUserData item, MediaSourceInfo source, User user)
{
- var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
- ? new string[] { }
- : new[] { user.Configuration.AudioLanguagePreference };
+ var userData = item == null ? new UserItemData() : _userDataManager.GetUserData(user.Id, item.GetUserDataKey());
+
+ SetDefaultAudioStreamIndex(source, userData, user);
+ SetDefaultSubtitleStreamIndex(source, userData, user);
+ }
+ private void SetDefaultSubtitleStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ {
+ if (userData.SubtitleStreamIndex.HasValue && user.Configuration.RememberSubtitleSelections)
+ {
+ var index = userData.SubtitleStreamIndex.Value;
+ // Make sure the saved index is still valid
+ if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))
+ {
+ source.DefaultSubtitleStreamIndex = index;
+ return;
+ }
+ }
+
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
? new List<string> { }
: new List<string> { user.Configuration.SubtitleLanguagePreference };
- source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
-
var defaultAudioIndex = source.DefaultAudioStreamIndex;
var audioLangage = defaultAudioIndex == null
? null
@@ -290,6 +305,26 @@ namespace MediaBrowser.Server.Implementations.Library
user.Configuration.SubtitleMode, audioLangage);
}
+ private void SetDefaultAudioStreamIndex(MediaSourceInfo source, UserItemData userData, User user)
+ {
+ if (userData.AudioStreamIndex.HasValue && user.Configuration.RememberAudioSelections)
+ {
+ var index = userData.AudioStreamIndex.Value;
+ // Make sure the saved index is still valid
+ if (source.MediaStreams.Any(i => i.Type == MediaStreamType.Audio && i.Index == index))
+ {
+ source.DefaultAudioStreamIndex = index;
+ return;
+ }
+ }
+
+ var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
+ ? new string[] { }
+ : new[] { user.Configuration.AudioLanguagePreference };
+
+ source.DefaultAudioStreamIndex = MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
+ }
+
private IEnumerable<MediaSourceInfo> SortMediaSources(IEnumerable<MediaSourceInfo> sources)
{
return sources.OrderBy(i =>
@@ -349,11 +384,14 @@ namespace MediaBrowser.Server.Implementations.Library
var json = _jsonSerializer.SerializeToString(mediaSource);
_logger.Debug("Live stream opened: " + json);
var clone = _jsonSerializer.DeserializeFromString<MediaSourceInfo>(json);
-
+
if (!string.IsNullOrWhiteSpace(request.UserId))
{
var user = _userManager.GetUserById(request.UserId);
- SetUserProperties(clone, user);
+ var item = string.IsNullOrWhiteSpace(request.ItemId)
+ ? null
+ : _libraryManager.GetItemById(request.ItemId);
+ SetUserProperties(item, clone, user);
}
return new LiveStreamResponse
diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 23424bf3c..c73470b51 100644
--- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -78,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
}
if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase) ||
- string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
+ string.Equals(collectionType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
{
return ResolveVideos<Video>(parent, files, directoryService, false);
}
@@ -137,7 +137,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
var resolverResult = resolver.Resolve(files.Select(i => new FileMetadata
{
Id = i.FullName,
- IsFolder = false
+ IsFolder = i.IsDirectory
}).ToList(), suppportMultiEditions).ToList();
@@ -169,9 +169,28 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
result.Items.Add(videoItem);
}
+ result.ExtraFiles.AddRange(files.Where(i => !ContainsFile(resolverResult, i)));
+
return result;
}
+ private bool ContainsFile(List<VideoInfo> result, FileSystemMetadata file)
+ {
+ return result.Any(i => ContainsFile(i, file));
+ }
+
+ private bool ContainsFile(VideoInfo result, FileSystemMetadata file)
+ {
+ return result.Files.Any(i => ContainsFile(i, file)) ||
+ result.AlternateVersions.Any(i => ContainsFile(i, file)) ||
+ result.Extras.Any(i => ContainsFile(i, file));
+ }
+
+ private bool ContainsFile(VideoFileInfo result, FileSystemMetadata file)
+ {
+ return string.Equals(result.Path, file.FullName, StringComparison.OrdinalIgnoreCase);
+ }
+
/// <summary>
/// Resolves the specified args.
/// </summary>
diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs
index 2161c1454..884e1e322 100644
--- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs
+++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs
@@ -125,10 +125,21 @@ namespace MediaBrowser.Server.Implementations.Library.Validators
validIds.Add(item.Id);
+ var hasMetdata = !string.IsNullOrWhiteSpace(item.Overview);
+ var performFullRefresh = !hasMetdata && (DateTime.UtcNow - item.DateLastRefreshed).TotalDays >= 30;
+
+ var defaultMetadataRefreshMode = performFullRefresh
+ ? MetadataRefreshMode.FullRefresh
+ : MetadataRefreshMode.Default;
+
+ var imageRefreshMode = performFullRefresh
+ ? ImageRefreshMode.FullRefresh
+ : ImageRefreshMode.Default;
+
var options = new MetadataRefreshOptions(_fileSystem)
{
- MetadataRefreshMode = person.Value ? MetadataRefreshMode.Default : MetadataRefreshMode.ValidationOnly,
- ImageRefreshMode = person.Value ? ImageRefreshMode.Default : ImageRefreshMode.ValidationOnly
+ MetadataRefreshMode = person.Value ? defaultMetadataRefreshMode : MetadataRefreshMode.ValidationOnly,
+ ImageRefreshMode = person.Value ? imageRefreshMode : ImageRefreshMode.ValidationOnly
};
await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false);
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
index 9ac96165f..d33b2c51d 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs
@@ -23,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_fileSystem = fileSystem;
}
- public async Task Record(MediaSourceInfo mediaSource, string targetFile, Action onStarted, CancellationToken cancellationToken)
+ public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
var httpRequestOptions = new HttpRequestOptions()
{
@@ -42,7 +42,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Copying recording stream to file stream");
- await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+ var durationToken = new CancellationTokenSource(duration);
+ var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
+
+ await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false);
}
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 9e4cb66a8..3ab08274c 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -87,7 +87,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_timerProvider.RestartTimers();
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
-
}
void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
@@ -104,8 +103,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public event EventHandler<RecordingStatusChangedEventArgs> RecordingStatusChanged;
- private readonly ConcurrentDictionary<string, CancellationTokenSource> _activeRecordings =
- new ConcurrentDictionary<string, CancellationTokenSource>(StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings =
+ new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase);
public string Name
{
@@ -210,9 +209,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- if (list.Count > 0)
+ foreach (var provider in GetListingProviders())
{
- foreach (var provider in GetListingProviders())
+ var enabledChannels = list
+ .Where(i => IsListingProviderEnabledForTuner(provider.Item2, i.TunerHostId))
+ .ToList();
+
+ if (enabledChannels.Count > 0)
{
try
{
@@ -228,6 +231,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
}
+
_channelCache = list;
return list;
}
@@ -264,11 +268,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
_timerProvider.Delete(remove);
}
- CancellationTokenSource cancellationTokenSource;
+ ActiveRecordingInfo activeRecordingInfo;
- if (_activeRecordings.TryGetValue(timerId, out cancellationTokenSource))
+ if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo))
{
- cancellationTokenSource.Cancel();
+ activeRecordingInfo.CancellationTokenSource.Cancel();
}
}
@@ -296,17 +300,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- try
- {
- _fileSystem.DeleteFile(remove.Path);
- }
- catch (DirectoryNotFoundException)
+ if (!string.IsNullOrWhiteSpace(remove.Path))
{
+ try
+ {
+ _fileSystem.DeleteFile(remove.Path);
+ }
+ catch (DirectoryNotFoundException)
+ {
- }
- catch (FileNotFoundException)
- {
+ }
+ catch (FileNotFoundException)
+ {
+ }
}
_recordingProvider.Delete(remove);
}
@@ -489,6 +496,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
+ private bool IsListingProviderEnabledForTuner(ListingsProviderInfo info, string tunerHostId)
+ {
+ return info.EnableAllTuners || info.EnabledTuners.Contains(tunerHostId ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ }
+
private async Task<IEnumerable<ProgramInfo>> GetProgramsAsyncInternal(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
{
var channels = await GetChannelsAsync(true, cancellationToken).ConfigureAwait(false);
@@ -496,6 +508,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
foreach (var provider in GetListingProviders())
{
+ if (!IsListingProviderEnabledForTuner(provider.Item2, channel.TunerHostId))
+ {
+ continue;
+ }
+
var programs = await provider.Item1.GetProgramsAsync(provider.Item2, channel.Number, channel.Name, startDateUtc, endDateUtc, cancellationToken)
.ConfigureAwait(false);
@@ -636,11 +653,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return;
}
- var cancellationTokenSource = new CancellationTokenSource();
+ var activeRecordingInfo = new ActiveRecordingInfo
+ {
+ CancellationTokenSource = new CancellationTokenSource(),
+ TimerId = timer.Id
+ };
- if (_activeRecordings.TryAdd(timer.Id, cancellationTokenSource))
+ if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo))
{
- await RecordStream(timer, recordingEndDate, cancellationTokenSource.Token).ConfigureAwait(false);
+ await RecordStream(timer, recordingEndDate, activeRecordingInfo, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false);
}
else
{
@@ -657,7 +678,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, CancellationToken cancellationToken)
+ private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken)
{
if (timer == null)
{
@@ -712,7 +733,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts";
recordPath = Path.Combine(recordPath, recordingFileName);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
var recordingId = info.Id.GetMD5().ToString("N");
var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase));
@@ -760,7 +780,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
try
{
// HDHR doesn't seem to release the tuner right away after first probing with ffmpeg
- await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
+ //await Task.Delay(3000, cancellationToken).ConfigureAwait(false);
var duration = recordingEndDate - DateTime.UtcNow;
@@ -770,6 +790,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
recordPath = Path.ChangeExtension(recordPath, ".mp4");
}
+ recordPath = EnsureFileUnique(recordPath, timer.Id);
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath));
+ activeRecordingInfo.Path = recordPath;
+
+ _libraryMonitor.ReportFileSystemChangeBeginning(recordPath);
recording.Path = recordPath;
recording.Status = RecordingStatus.InProgress;
@@ -778,9 +803,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture));
- var durationToken = new CancellationTokenSource(duration);
- var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token;
-
_logger.Info("Writing file to path: " + recordPath);
_logger.Info("Opening recording stream from tuner provider");
@@ -790,10 +812,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
isResourceOpen = false;
};
- await recorder.Record(mediaStreamInfo, recordPath, onStarted, linkedToken).ConfigureAwait(false);
+ await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false);
recording.Status = RecordingStatus.Completed;
- _logger.Info("Recording completed");
+ _logger.Info("Recording completed: {0}", recordPath);
}
finally
{
@@ -801,21 +823,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
result.Item2.Release();
}
+
+ _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
}
}
catch (OperationCanceledException)
{
- _logger.Info("Recording stopped");
+ _logger.Info("Recording stopped: {0}", recordPath);
recording.Status = RecordingStatus.Completed;
}
catch (Exception ex)
{
- _logger.ErrorException("Error recording", ex);
+ _logger.ErrorException("Error recording to {0}", ex, recordPath);
recording.Status = RecordingStatus.Error;
}
finally
{
- CancellationTokenSource removed;
+ ActiveRecordingInfo removed;
_activeRecordings.TryRemove(timer.Id, out removed);
}
@@ -841,6 +865,40 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
+ private string EnsureFileUnique(string path, string timerId)
+ {
+ var originalPath = path;
+ var index = 1;
+
+ while (FileExists(path, timerId))
+ {
+ var parent = Path.GetDirectoryName(originalPath);
+ var name = Path.GetFileNameWithoutExtension(originalPath);
+ name += "-" + index.ToString(CultureInfo.InvariantCulture);
+
+ path = Path.ChangeExtension(Path.Combine(parent, name), Path.GetExtension(originalPath));
+ index++;
+ }
+
+ return path;
+ }
+
+ private bool FileExists(string path, string timerId)
+ {
+ if (_fileSystem.FileExists(path))
+ {
+ return true;
+ }
+
+ var hasRecordingAtPath = _activeRecordings.Values.ToList().Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.TimerId, timerId, StringComparison.OrdinalIgnoreCase));
+
+ if (hasRecordingAtPath)
+ {
+ return true;
+ }
+ return false;
+ }
+
private async Task<IRecorder> GetRecorder()
{
if (GetConfiguration().EnableRecordingEncoding)
@@ -1024,7 +1082,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
foreach (var pair in _activeRecordings.ToList())
{
- pair.Value.Cancel();
+ pair.Value.CancellationTokenSource.Cancel();
}
}
@@ -1041,5 +1099,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
IsRegistered = true
});
}
+
+ class ActiveRecordingInfo
+ {
+ public string Path { get; set; }
+ public string TimerId { get; set; }
+ public CancellationTokenSource CancellationTokenSource { get; set; }
+ }
}
} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index 5f4d32732..62c9cd171 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
_json = json;
}
- public async Task Record(MediaSourceInfo mediaSource, string targetFile, Action onStarted, CancellationToken cancellationToken)
+ public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
{
_targetPath = targetFile;
_fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile));
@@ -56,7 +56,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
RedirectStandardInput = true,
FileName = _mediaEncoder.EncoderPath,
- Arguments = GetCommandLineArgs(mediaSource, targetFile),
+ Arguments = GetCommandLineArgs(mediaSource, targetFile, duration),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
@@ -88,11 +88,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
process.BeginOutputReadLine();
+ onStarted();
+
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
StartStreamingLog(process.StandardError.BaseStream, _logFileStream);
- onStarted();
-
// Wait for the file to exist before proceeeding
while (!_hasExited)
{
@@ -100,7 +100,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
- private string GetCommandLineArgs(MediaSourceInfo mediaSource, string targetFile)
+ private string GetCommandLineArgs(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration)
{
string videoArgs;
if (EncodeVideo(mediaSource))
@@ -116,14 +116,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
videoArgs = "-codec:v:0 copy";
}
- var commandLineArgs = "-fflags +genpts -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
+ var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\" -t {4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\"";
if (mediaSource.ReadAtNativeFramerate)
{
commandLineArgs = "-re " + commandLineArgs;
}
- commandLineArgs = string.Format(commandLineArgs, mediaSource.Path, targetFile, videoArgs, GetAudioArgs(mediaSource));
+ commandLineArgs = string.Format(commandLineArgs, mediaSource.Path, targetFile, videoArgs, GetAudioArgs(mediaSource), _mediaEncoder.GetTimeParameter(duration.Ticks));
return commandLineArgs;
}
@@ -143,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
audioChannels = audioStream.Channels ?? audioChannels;
}
- return "-codec:a:0 aac -strict experimental -ab 320000 -ac " + audioChannels.ToString(CultureInfo.InvariantCulture);
+ return "-codec:a:0 aac -strict experimental -ab 320000";
}
private bool EncodeVideo(MediaSourceInfo mediaSource)
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
index 12e73c1f3..268a4f751 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs
@@ -7,6 +7,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
public interface IRecorder
{
- Task Record(MediaSourceInfo mediaSource, string targetFile, Action onStarted, CancellationToken cancellationToken);
+ /// <summary>
+ /// Records the specified media source.
+ /// </summary>
+ /// <param name="mediaSource">The media source.</param>
+ /// <param name="targetFile">The target file.</param>
+ /// <param name="duration">The duration.</param>
+ /// <param name="onStarted">The on started.</param>
+ /// <param name="cancellationToken">The cancellation token.</param>
+ /// <returns>Task.</returns>
+ Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
index b29a7562c..68b3f1f71 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/ItemDataProvider.cs
@@ -13,7 +13,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
where T : class
{
private readonly object _fileDataLock = new object();
- private volatile List<T> _items;
+ private List<T> _items;
private readonly IJsonSerializer _jsonSerializer;
protected readonly ILogger Logger;
private readonly string _dataPath;
@@ -31,17 +31,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public IReadOnlyList<T> GetAll()
{
- if (_items == null)
+ lock (_fileDataLock)
{
- lock (_fileDataLock)
+ if (_items == null)
{
- if (_items == null)
- {
- _items = GetItemsFromFile(_dataPath);
- }
+ _items = GetItemsFromFile(_dataPath);
}
+ return _items;
}
- return _items;
}
private List<T> GetItemsFromFile(string path)
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
index a5c869d45..37e10d925 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs
@@ -56,13 +56,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
name += " " + info.OriginalAirDate.Value.ToString("yyyy-MM-dd");
}
- if (addHyphen)
- {
- name += " -";
- }
-
if (!string.IsNullOrWhiteSpace(info.EpisodeTitle))
{
+ if (addHyphen)
+ {
+ name += " -";
+ }
+
name += " " + info.EpisodeTitle;
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index 87d7ff3eb..449943229 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -951,6 +951,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings
public string stationID { get; set; }
public List<Program> programs { get; set; }
public MetadataSchedule metadata { get; set; }
+
+ public Day()
+ {
+ programs = new List<Program>();
+ }
}
//
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
index 04f99cdce..81ad6a387 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs
@@ -178,7 +178,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
SourceType = info.SourceType,
Status = info.Status,
ChannelName = channelName,
- Url = info.Url
+ Url = info.Url,
+ CanReset = info.CanReset
};
if (!string.IsNullOrEmpty(info.ChannelId))
diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
index 14bfcba27..abb2710e7 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -350,7 +350,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
foreach (var source in list)
{
- Normalize(source, item.ChannelType == ChannelType.TV);
+ Normalize(source, service, item.ChannelType == ChannelType.TV);
}
return list;
@@ -379,12 +379,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
MediaSourceInfo info;
bool isVideo;
+ ILiveTvService service;
if (isChannel)
{
var channel = GetInternalChannel(id);
isVideo = channel.ChannelType == ChannelType.TV;
- var service = GetService(channel);
+ service = GetService(channel);
_logger.Info("Opening channel stream from {0}, external channel Id: {1}", service.Name, channel.ExternalId);
info = await service.GetChannelStream(channel.ExternalId, mediaSourceId, cancellationToken).ConfigureAwait(false);
info.RequiresClosing = true;
@@ -400,7 +401,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
var recording = await GetInternalRecording(id, cancellationToken).ConfigureAwait(false);
isVideo = !string.Equals(recording.MediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase);
- var service = GetService(recording);
+ service = GetService(recording);
_logger.Info("Opening recording stream from {0}, external recording Id: {1}", service.Name, recording.ExternalId);
info = await service.GetRecordingStream(recording.ExternalId, null, cancellationToken).ConfigureAwait(false);
@@ -415,7 +416,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
_logger.Info("Live stream info: {0}", _jsonSerializer.SerializeToString(info));
- Normalize(info, isVideo);
+ Normalize(info, service, isVideo);
var data = new LiveStreamData
{
@@ -440,7 +441,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
}
}
- private void Normalize(MediaSourceInfo mediaSource, bool isVideo)
+ private void Normalize(MediaSourceInfo mediaSource, ILiveTvService service, bool isVideo)
{
if (mediaSource.MediaStreams.Count == 0)
{
@@ -537,6 +538,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
mediaSource.Bitrate = total;
}
}
+
+ if (!(service is EmbyTV.EmbyTV))
+ {
+ // We can't trust that we'll be able to direct stream it through emby server, no matter what the provider says
+ mediaSource.SupportsDirectStream = true;
+ }
}
private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
@@ -801,11 +808,21 @@ namespace MediaBrowser.Server.Implementations.LiveTv
{
if (!string.IsNullOrWhiteSpace(info.ImagePath))
{
- item.SetImagePath(ImageType.Primary, info.ImagePath);
+ item.SetImage(new ItemImageInfo
+ {
+ Path = info.ImagePath,
+ Type = ImageType.Primary,
+ IsPlaceholder = true
+ }, 0);
}
else if (!string.IsNullOrWhiteSpace(info.ImageUrl))
{
- item.SetImagePath(ImageType.Primary, info.ImageUrl);
+ item.SetImage(new ItemImageInfo
+ {
+ Path = info.ImageUrl,
+ Type = ImageType.Primary,
+ IsPlaceholder = true
+ }, 0);
}
}
@@ -1472,70 +1489,79 @@ namespace MediaBrowser.Server.Implementations.LiveTv
};
}
- public void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, List<ItemFields> fields, User user = null)
+ public async Task AddInfoToProgramDto(List<Tuple<BaseItem, BaseItemDto>> tuples, List<ItemFields> fields, User user = null)
{
- var program = (LiveTvProgram)item;
-
- dto.StartDate = program.StartDate;
- dto.EpisodeTitle = program.EpisodeTitle;
+ var recordingTuples = new List<Tuple<BaseItemDto, string, string>>();
- if (program.IsRepeat)
- {
- dto.IsRepeat = program.IsRepeat;
- }
- if (program.IsMovie)
- {
- dto.IsMovie = program.IsMovie;
- }
- if (program.IsSeries)
- {
- dto.IsSeries = program.IsSeries;
- }
- if (program.IsSports)
- {
- dto.IsSports = program.IsSports;
- }
- if (program.IsLive)
- {
- dto.IsLive = program.IsLive;
- }
- if (program.IsNews)
- {
- dto.IsNews = program.IsNews;
- }
- if (program.IsKids)
- {
- dto.IsKids = program.IsKids;
- }
- if (program.IsPremiere)
+ foreach (var tuple in tuples)
{
- dto.IsPremiere = program.IsPremiere;
- }
+ var program = (LiveTvProgram)tuple.Item1;
+ var dto = tuple.Item2;
- if (fields.Contains(ItemFields.ChannelInfo))
- {
- var channel = GetInternalChannel(program.ChannelId);
+ dto.StartDate = program.StartDate;
+ dto.EpisodeTitle = program.EpisodeTitle;
+
+ if (program.IsRepeat)
+ {
+ dto.IsRepeat = program.IsRepeat;
+ }
+ if (program.IsMovie)
+ {
+ dto.IsMovie = program.IsMovie;
+ }
+ if (program.IsSeries)
+ {
+ dto.IsSeries = program.IsSeries;
+ }
+ if (program.IsSports)
+ {
+ dto.IsSports = program.IsSports;
+ }
+ if (program.IsLive)
+ {
+ dto.IsLive = program.IsLive;
+ }
+ if (program.IsNews)
+ {
+ dto.IsNews = program.IsNews;
+ }
+ if (program.IsKids)
+ {
+ dto.IsKids = program.IsKids;
+ }
+ if (program.IsPremiere)
+ {
+ dto.IsPremiere = program.IsPremiere;
+ }
- if (channel != null)
+ if (fields.Contains(ItemFields.ChannelInfo))
{
- dto.ChannelName = channel.Name;
- dto.MediaType = channel.MediaType;
+ var channel = GetInternalChannel(program.ChannelId);
- if (channel.HasImage(ImageType.Primary))
+ if (channel != null)
{
- dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
+ dto.ChannelName = channel.Name;
+ dto.MediaType = channel.MediaType;
+
+ if (channel.HasImage(ImageType.Primary))
+ {
+ dto.ChannelPrimaryImageTag = _tvDtoService.GetImageTag(channel);
+ }
}
}
- }
- if (fields.Contains(ItemFields.ServiceName))
- {
var service = GetService(program);
- if (service != null)
+ var serviceName = service == null ? null : service.Name;
+
+ if (fields.Contains(ItemFields.ServiceName))
{
- dto.ServiceName = service.Name;
+ dto.ServiceName = serviceName;
}
+
+ recordingTuples.Add(new Tuple<BaseItemDto, string, string>(dto, serviceName, program.ExternalId));
}
+
+ await AddRecordingInfo(recordingTuples, CancellationToken.None).ConfigureAwait(false);
}
public void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, User user = null)
@@ -2343,7 +2369,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
throw new ResourceNotFoundException();
}
- await provider.Validate(info).ConfigureAwait(false);
+ var configurable = provider as IConfigurableTunerHost;
+ if (configurable != null)
+ {
+ await configurable.Validate(info).ConfigureAwait(false);
+ }
var config = GetConfiguration();
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
index 4ebc173b5..fb27631e5 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/BaseTunerHost.cs
@@ -64,7 +64,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return list;
}
- private List<TunerHostInfo> GetTunerHosts()
+ protected virtual List<TunerHostInfo> GetTunerHosts()
{
return GetConfiguration().TunerHosts
.Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
index 92a33993a..0a03e60fa 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunDiscovery.cs
@@ -90,7 +90,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
await _liveTvManager.SaveTunerHost(new TunerHostInfo
{
Type = HdHomerunHost.DeviceType,
- Url = url
+ Url = url,
+ DataVersion = 1
}).ConfigureAwait(false);
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 0671a9b56..bdfbee521 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -14,13 +14,14 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dlna;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
- public class HdHomerunHost : BaseTunerHost, ITunerHost
+ public class HdHomerunHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private readonly IHttpClient _httpClient;
@@ -47,6 +48,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private const string ChannelIdPrefix = "hdhr_";
+ private string GetChannelId(TunerHostInfo info, Channels i)
+ {
+ var id = ChannelIdPrefix + i.GuideNumber.ToString(CultureInfo.InvariantCulture);
+
+ if (info.DataVersion >= 1)
+ {
+ id += '_' + (i.GuideName ?? string.Empty).GetMD5().ToString("N");
+ }
+
+ return id;
+ }
+
protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{
var options = new HttpRequestOptions
@@ -64,8 +77,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
Name = i.GuideName,
Number = i.GuideNumber.ToString(CultureInfo.InvariantCulture),
- Id = ChannelIdPrefix + i.GuideNumber.ToString(CultureInfo.InvariantCulture),
- IsFavorite = i.Favorite
+ Id = GetChannelId(info, i),
+ IsFavorite = i.Favorite,
+ TunerHostId = info.Id
});
@@ -82,30 +96,19 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
private async Task<string> GetModelInfo(TunerHostInfo info, CancellationToken cancellationToken)
{
- string model = null;
-
using (var stream = await _httpClient.Get(new HttpRequestOptions()
{
- Url = string.Format("{0}/", GetApiUrl(info, false)),
+ Url = string.Format("{0}/discover.json", GetApiUrl(info, false)),
CancellationToken = cancellationToken,
CacheLength = TimeSpan.FromDays(1),
CacheMode = CacheMode.Unconditional,
TimeoutMs = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds)
}))
{
- using (var sr = new StreamReader(stream, System.Text.Encoding.UTF8))
- {
- while (!sr.EndOfStream)
- {
- string line = StripXML(sr.ReadLine());
- if (line.StartsWith("Model:")) { model = line.Replace("Model: ", ""); }
- //if (line.StartsWith("Device ID:")) { deviceID = line.Replace("Device ID: ", ""); }
- //if (line.StartsWith("Firmware:")) { firmware = line.Replace("Firmware: ", ""); }
- }
- }
- }
+ var response = JsonSerializer.DeserializeFromStream<DiscoverResponse>(stream);
- return model;
+ return response.ModelNumber;
+ }
}
public async Task<List<LiveTvTunerInfo>> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
@@ -347,6 +350,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return Config.GetConfiguration<EncodingOptions>("encoding");
}
+ private string GetHdHrIdFromChannelId(string channelId)
+ {
+ return channelId.Split('_')[1];
+ }
+
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{
var list = new List<MediaSourceInfo>();
@@ -355,9 +363,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
return list;
}
- channelId = channelId.Substring(ChannelIdPrefix.Length);
+ var hdhrId = GetHdHrIdFromChannelId(channelId);
- list.Add(GetMediaSource(info, channelId, "native"));
+ list.Add(GetMediaSource(info, hdhrId, "native"));
try
{
@@ -366,12 +374,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (model.IndexOf("hdtc", StringComparison.OrdinalIgnoreCase) != -1)
{
- list.Insert(0, GetMediaSource(info, channelId, "heavy"));
+ list.Insert(0, GetMediaSource(info, hdhrId, "heavy"));
- list.Add(GetMediaSource(info, channelId, "internet480"));
- list.Add(GetMediaSource(info, channelId, "internet360"));
- list.Add(GetMediaSource(info, channelId, "internet240"));
- list.Add(GetMediaSource(info, channelId, "mobile"));
+ list.Add(GetMediaSource(info, hdhrId, "internet480"));
+ list.Add(GetMediaSource(info, hdhrId, "internet360"));
+ list.Add(GetMediaSource(info, hdhrId, "internet240"));
+ list.Add(GetMediaSource(info, hdhrId, "mobile"));
}
}
catch (Exception ex)
@@ -400,9 +408,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
throw new ArgumentException("Channel not found");
}
- channelId = channelId.Substring(ChannelIdPrefix.Length);
+ var hdhrId = GetHdHrIdFromChannelId(channelId);
- return GetMediaSource(info, channelId, streamId);
+ return GetMediaSource(info, hdhrId, streamId);
}
public async Task Validate(TunerHostInfo info)
@@ -419,5 +427,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.HdHomerun
return info.Any(i => i.Status == LiveTvTunerStatus.Available);
}
+
+ public class DiscoverResponse
+ {
+ public string FriendlyName { get; set; }
+ public string ModelNumber { get; set; }
+ public string FirmwareName { get; set; }
+ public string FirmwareVersion { get; set; }
+ public string DeviceID { get; set; }
+ public string DeviceAuth { get; set; }
+ public string BaseURL { get; set; }
+ public string LineupURL { get; set; }
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index f87d4f43f..523f14dfc 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -8,19 +8,17 @@ using MediaBrowser.Model.Logging;
using MediaBrowser.Model.MediaInfo;
using System;
using System.Collections.Generic;
-using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
-using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
{
- public class M3UTunerHost : BaseTunerHost, ITunerHost
+ public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost
{
private readonly IFileSystem _fileSystem;
private readonly IHttpClient _httpClient;
@@ -46,65 +44,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
{
- var urlHash = info.Url.GetMD5().ToString("N");
-
- // Read the file and display it line by line.
- using (var reader = new StreamReader(await GetListingsStream(info, cancellationToken).ConfigureAwait(false)))
- {
- return GetChannels(reader, urlHash);
- }
- }
-
- private List<M3UChannel> GetChannels(StreamReader reader, string urlHash)
- {
- var channels = new List<M3UChannel>();
-
- string channnelName = null;
- string channelNumber = null;
- string line;
-
- while ((line = reader.ReadLine()) != null)
- {
- line = line.Trim();
- if (string.IsNullOrWhiteSpace(line))
- {
- continue;
- }
-
- if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
- {
- continue;
- }
-
- if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
- {
- line = line.Substring(8);
- Logger.Info("Found m3u channel: {0}", line);
- var parts = line.Split(new[] { ',' }, 2);
- channelNumber = parts[0];
- channnelName = parts[1];
- }
- else if (!string.IsNullOrWhiteSpace(channelNumber))
- {
- channels.Add(new M3UChannel
- {
- Name = channnelName,
- Number = channelNumber,
- Id = ChannelIdPrefix + urlHash + line.GetMD5().ToString("N"),
- Path = line
- });
-
- channelNumber = null;
- channnelName = null;
- }
- }
- return channels;
+ return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(info.Url, ChannelIdPrefix, info.Id, cancellationToken).ConfigureAwait(false);
}
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
{
- var list = GetConfiguration().TunerHosts
- .Where(i => i.IsEnabled && string.Equals(i.Type, Type, StringComparison.OrdinalIgnoreCase))
+ var list = GetTunerHosts()
.Select(i => new LiveTvTunerInfo()
{
Name = Name,
@@ -125,18 +70,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return sources.First();
}
- class M3UChannel : ChannelInfo
- {
- public string Path { get; set; }
-
- public M3UChannel()
- {
- }
- }
-
public async Task Validate(TunerHostInfo info)
{
- using (var stream = await GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
+ using (var stream = await new M3uParser(Logger, _fileSystem, _httpClient).GetListingsStream(info.Url, CancellationToken.None).ConfigureAwait(false))
{
}
@@ -147,15 +83,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
}
- private Task<Stream> GetListingsStream(TunerHostInfo info, CancellationToken cancellationToken)
- {
- if (info.Url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
- {
- return _httpClient.Get(info.Url, cancellationToken);
- }
- return Task.FromResult(_fileSystem.OpenRead(info.Url));
- }
-
protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo info, string channelId, CancellationToken cancellationToken)
{
var urlHash = info.Url.GetMD5().ToString("N");
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
new file mode 100644
index 000000000..f8f003fa1
--- /dev/null
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts
+{
+ public class M3uParser
+ {
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public M3uParser(ILogger logger, IFileSystem fileSystem, IHttpClient httpClient)
+ {
+ _logger = logger;
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ public async Task<List<M3UChannel>> Parse(string url, string channelIdPrefix, string tunerHostId, CancellationToken cancellationToken)
+ {
+ var urlHash = url.GetMD5().ToString("N");
+
+ // Read the file and display it line by line.
+ using (var reader = new StreamReader(await GetListingsStream(url, cancellationToken).ConfigureAwait(false)))
+ {
+ return GetChannels(reader, urlHash, channelIdPrefix, tunerHostId);
+ }
+ }
+
+ public Task<Stream> GetListingsStream(string url, CancellationToken cancellationToken)
+ {
+ if (url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ return _httpClient.Get(url, cancellationToken);
+ }
+ return Task.FromResult(_fileSystem.OpenRead(url));
+ }
+
+ private List<M3UChannel> GetChannels(StreamReader reader, string urlHash, string channelIdPrefix, string tunerHostId)
+ {
+ var channels = new List<M3UChannel>();
+ string line;
+ string extInf = "";
+ while ((line = reader.ReadLine()) != null)
+ {
+ line = line.Trim();
+ if (string.IsNullOrWhiteSpace(line))
+ {
+ continue;
+ }
+
+ if (line.StartsWith("#EXTM3U", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ if (line.StartsWith("#EXTINF:", StringComparison.OrdinalIgnoreCase))
+ {
+ extInf = line.Substring(8).Trim();
+ _logger.Info("Found m3u channel: {0}", extInf);
+ }
+ else if (!string.IsNullOrWhiteSpace(extInf))
+ {
+ var channel = GetChannelnfo(extInf, tunerHostId);
+ channel.Id = channelIdPrefix + urlHash + line.GetMD5().ToString("N");
+ channel.Path = line;
+ channels.Add(channel);
+ extInf = "";
+ }
+ }
+ return channels;
+ }
+ private M3UChannel GetChannelnfo(string extInf, string tunerHostId)
+ {
+ var titleIndex = extInf.LastIndexOf(',');
+ var channel = new M3UChannel();
+ channel.TunerHostId = tunerHostId;
+
+ 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;
+ var numberIndex = channel.Name.IndexOf('.');
+ if (numberIndex > 0)
+ {
+ if (int.TryParse(channel.Name.Substring(0, numberIndex), out number))
+ {
+ channel.Number = number.ToString();
+ channel.Name = channel.Name.Substring(numberIndex + 1);
+ }
+ }
+ channel.ImageUrl = FindProperty("tvg-logo", extInf, null);
+ channel.Number = FindProperty("tvg-id", extInf, channel.Number);
+ channel.Number = FindProperty("channel-id", extInf, channel.Number);
+ channel.Name = FindProperty("tvg-name", extInf, channel.Name);
+ channel.Name = FindProperty("tvg-id", extInf, channel.Name);
+ return channel;
+
+ }
+ private string FindProperty(string property, string properties, string defaultResult = "")
+ {
+ var reg = new Regex(@"([a-z0-9\-_]+)=\""([^""]+)\""", RegexOptions.IgnoreCase);
+ var matches = reg.Matches(properties);
+ foreach (Match match in matches)
+ {
+ if (match.Groups[1].Value == property)
+ {
+ return match.Groups[2].Value;
+ }
+ }
+ return defaultResult;
+ }
+ }
+
+
+ public class M3UChannel : ChannelInfo
+ {
+ public string Path { get; set; }
+ }
+} \ No newline at end of file
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
index 08c42bb46..6781e498a 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs
@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using System.Xml;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
@@ -13,6 +17,7 @@ using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Extensions;
using MediaBrowser.Model.LiveTv;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
@@ -24,14 +29,26 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
private readonly ILiveTvManager _liveTvManager;
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly IHttpClient _httpClient;
+ private readonly IJsonSerializer _json;
- public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient)
+ public static SatIpDiscovery Current;
+
+ private readonly List<TunerHostInfo> _discoveredHosts = new List<TunerHostInfo>();
+
+ public List<TunerHostInfo> DiscoveredHosts
+ {
+ get { return _discoveredHosts.ToList(); }
+ }
+
+ public SatIpDiscovery(IDeviceDiscovery deviceDiscovery, IServerConfigurationManager config, ILogger logger, ILiveTvManager liveTvManager, IHttpClient httpClient, IJsonSerializer json)
{
_deviceDiscovery = deviceDiscovery;
_config = config;
_logger = logger;
_liveTvManager = liveTvManager;
_httpClient = httpClient;
+ _json = json;
+ Current = this;
}
public void Run()
@@ -42,7 +59,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
void _deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e)
{
string st = null;
- if (e.Headers.TryGetValue("ST", out st) && string.Equals(st, "urn:ses-com:device:SatIPServer:1", StringComparison.OrdinalIgnoreCase))
+ string nt = null;
+ e.Headers.TryGetValue("ST", out st);
+ e.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))
@@ -61,26 +83,23 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
try
{
- var options = GetConfiguration();
-
- //if (options.TunerHosts.Any(i =>
- // string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) &&
- // UriEquals(i.Url, url)))
- //{
- // return;
- //}
+ if (_discoveredHosts.Any(i => string.Equals(i.Type, SatIpHost.DeviceType, StringComparison.OrdinalIgnoreCase) && string.Equals(location, i.Url, StringComparison.OrdinalIgnoreCase)))
+ {
+ return;
+ }
- //// Strip off the port
- //url = new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped).TrimEnd('/');
+ _logger.Debug("Will attempt to add SAT device {0}", location);
+ var info = await GetInfo(location, CancellationToken.None).ConfigureAwait(false);
- //await TestUrl(url).ConfigureAwait(false);
+ _discoveredHosts.Add(info);
+ }
+ catch (OperationCanceledException)
+ {
- //await _liveTvManager.SaveTunerHost(new TunerHostInfo
- //{
- // Type = SatIpHost.DeviceType,
- // Url = url
+ }
+ catch (NotImplementedException)
+ {
- //}).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -92,43 +111,141 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
}
}
- private async Task TestUrl(string url)
+ public void Dispose()
{
- // Test it by pulling down the lineup
- using (await _httpClient.Get(new HttpRequestOptions
+ }
+
+ public async Task<SatIpTunerHostInfo> GetInfo(string url, CancellationToken cancellationToken)
+ {
+ var result = new SatIpTunerHostInfo
{
- Url = string.Format("{0}/lineup.json", url),
- CancellationToken = CancellationToken.None
- }))
+ Url = url,
+ IsEnabled = true,
+ Type = SatIpHost.DeviceType,
+ Tuners = 1,
+ TunersAvailable = 1
+ };
+
+ using (var stream = await _httpClient.Get(url, cancellationToken).ConfigureAwait(false))
+ {
+ using (var streamReader = new StreamReader(stream))
+ {
+ // Use XmlReader for best performance
+ using (var reader = XmlReader.Create(streamReader))
+ {
+ reader.MoveToContent();
+
+ // Loop through each element
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.Name)
+ {
+ case "device":
+ using (var subtree = reader.ReadSubtree())
+ {
+ FillFromDeviceNode(result, subtree);
+ }
+ break;
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (string.IsNullOrWhiteSpace(result.Id))
{
+ throw new NotImplementedException();
}
- }
- private bool UriEquals(string savedUri, string location)
- {
- return string.Equals(NormalizeUrl(location), NormalizeUrl(savedUri), StringComparison.OrdinalIgnoreCase);
- }
+ // Device hasn't implemented an m3u list
+ if (string.IsNullOrWhiteSpace(result.M3UUrl))
+ {
+ result.IsEnabled = false;
+ }
- private string NormalizeUrl(string url)
- {
- if (!url.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ else if (!result.M3UUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
- url = "http://" + url;
+ var fullM3uUrl = url.Substring(0, url.LastIndexOf('/'));
+ result.M3UUrl = fullM3uUrl + "/" + result.M3UUrl.TrimStart('/');
}
- url = url.TrimEnd('/');
+ _logger.Debug("SAT device result: {0}", _json.SerializeToString(result));
- // Strip off the port
- return new Uri(url).GetComponents(UriComponents.AbsoluteUri & ~UriComponents.Port, UriFormat.UriEscaped);
+ return result;
}
- private LiveTvOptions GetConfiguration()
+ private void FillFromDeviceNode(SatIpTunerHostInfo info, XmlReader reader)
{
- return _config.GetConfiguration<LiveTvOptions>("livetv");
- }
+ reader.MoveToContent();
- public void Dispose()
- {
+ while (reader.Read())
+ {
+ if (reader.NodeType == XmlNodeType.Element)
+ {
+ switch (reader.LocalName)
+ {
+ case "UDN":
+ {
+ info.Id = reader.ReadElementContentAsString();
+ break;
+ }
+
+ case "friendlyName":
+ {
+ info.FriendlyName = reader.ReadElementContentAsString();
+ break;
+ }
+
+ case "satip:X_SATIPCAP":
+ case "X_SATIPCAP":
+ {
+ // <satip:X_SATIPCAP xmlns:satip="urn:ses-com:satip">DVBS2-2</satip:X_SATIPCAP>
+ var value = reader.ReadElementContentAsString() ?? string.Empty;
+ var parts = value.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
+ if (parts.Length == 2)
+ {
+ int intValue;
+ if (int.TryParse(parts[1], NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
+ {
+ info.TunersAvailable = intValue;
+ }
+
+ if (int.TryParse(parts[0].Substring(parts[0].Length - 1), NumberStyles.Any, CultureInfo.InvariantCulture, out intValue))
+ {
+ info.Tuners = intValue;
+ }
+ }
+ break;
+ }
+
+ case "satip:X_SATIPM3U":
+ case "X_SATIPM3U":
+ {
+ // <satip:X_SATIPM3U xmlns:satip="urn:ses-com:satip">/channellist.lua?select=m3u</satip:X_SATIPM3U>
+ info.M3UUrl = reader.ReadElementContentAsString();
+ break;
+ }
+
+ default:
+ reader.Skip();
+ break;
+ }
+ }
+ }
}
}
+
+ public class SatIpTunerHostInfo : TunerHostInfo
+ {
+ public int Tuners { get; set; }
+ public int TunersAvailable { get; set; }
+ public string M3UUrl { get; set; }
+ public string FriendlyName { get; set; }
+ }
}
diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
index 205cdf74e..181169e9a 100644
--- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
+++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpHost.cs
@@ -1,45 +1,171 @@
-namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using CommonIO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.LiveTv;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Serialization;
+
+namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp
{
- public class SatIpHost /*: BaseTunerHost*/
+ public class SatIpHost : BaseTunerHost, ITunerHost
{
- //public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder)
- // : base(config, logger, jsonSerializer, mediaEncoder)
- //{
- //}
+ private readonly IFileSystem _fileSystem;
+ private readonly IHttpClient _httpClient;
+
+ public SatIpHost(IConfigurationManager config, ILogger logger, IJsonSerializer jsonSerializer, IMediaEncoder mediaEncoder, IFileSystem fileSystem, IHttpClient httpClient)
+ : base(config, logger, jsonSerializer, mediaEncoder)
+ {
+ _fileSystem = fileSystem;
+ _httpClient = httpClient;
+ }
+
+ private const string ChannelIdPrefix = "sat_";
+
+ protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
+ {
+ var satInfo = (SatIpTunerHostInfo) tuner;
- //protected override Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ return await new M3uParser(Logger, _fileSystem, _httpClient).Parse(satInfo.M3UUrl, ChannelIdPrefix, tuner.Id, cancellationToken).ConfigureAwait(false);
+ }
public static string DeviceType
{
get { return "satip"; }
}
- //public override string Type
- //{
- // get { return DeviceType; }
- //}
+ public override string Type
+ {
+ get { return DeviceType; }
+ }
+
+ protected override async Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
+ {
+ var urlHash = tuner.Url.GetMD5().ToString("N");
+ var prefix = ChannelIdPrefix + urlHash;
+ if (!channelId.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ var channels = await GetChannels(tuner, true, cancellationToken).ConfigureAwait(false);
+ var m3uchannels = channels.Cast<M3UChannel>();
+ var channel = m3uchannels.FirstOrDefault(c => string.Equals(c.Id, channelId, StringComparison.OrdinalIgnoreCase));
+ if (channel != null)
+ {
+ var path = channel.Path;
+ MediaProtocol protocol = MediaProtocol.File;
+ if (path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Http;
+ }
+ else if (path.StartsWith("rtmp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Rtmp;
+ }
+ else if (path.StartsWith("rtsp", StringComparison.OrdinalIgnoreCase))
+ {
+ protocol = MediaProtocol.Rtsp;
+ }
+
+ var mediaSource = new MediaSourceInfo
+ {
+ Path = channel.Path,
+ Protocol = protocol,
+ MediaStreams = new List<MediaStream>
+ {
+ new MediaStream
+ {
+ Type = MediaStreamType.Video,
+ // Set the index to -1 because we don't know the exact index of the video stream within the container
+ Index = -1,
+ IsInterlaced = true
+ },
+ new MediaStream
+ {
+ Type = MediaStreamType.Audio,
+ // Set the index to -1 because we don't know the exact index of the audio stream within the container
+ Index = -1
+
+ }
+ },
+ RequiresOpening = false,
+ RequiresClosing = false
+ };
+
+ return new List<MediaSourceInfo> { mediaSource };
+ }
+ return new List<MediaSourceInfo> { };
+ }
+
+ protected override async Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
+ {
+ var sources = await GetChannelStreamMediaSources(tuner, channelId, cancellationToken).ConfigureAwait(false);
+
+ return sources.First();
+ }
+
+ protected override async Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
+ {
+ var updatedInfo = await SatIpDiscovery.Current.GetInfo(tuner.Url, cancellationToken).ConfigureAwait(false);
- //protected override Task<List<MediaSourceInfo>> GetChannelStreamMediaSources(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ return updatedInfo.TunersAvailable > 0;
+ }
+
+ protected override bool IsValidChannelId(string channelId)
+ {
+ return channelId.StartsWith(ChannelIdPrefix, StringComparison.OrdinalIgnoreCase);
+ }
- //protected override Task<MediaSourceInfo> GetChannelStream(TunerHostInfo tuner, string channelId, string streamId, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ protected override List<TunerHostInfo> GetTunerHosts()
+ {
+ return SatIpDiscovery.Current.DiscoveredHosts;
+ }
- //protected override Task<bool> IsAvailableInternal(TunerHostInfo tuner, string channelId, CancellationToken cancellationToken)
- //{
- // throw new NotImplementedException();
- //}
+ public string Name
+ {
+ get { return "Sat IP"; }
+ }
- //protected override bool IsValidChannelId(string channelId)
- //{
- // throw new NotImplementedException();
- //}
+ public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
+ {
+ var list = GetTunerHosts()
+ .SelectMany(i => GetTunerInfos(i, cancellationToken))
+ .ToList();
+
+ return Task.FromResult(list);
+ }
+
+ public List<LiveTvTunerInfo> GetTunerInfos(TunerHostInfo info, CancellationToken cancellationToken)
+ {
+ var satInfo = (SatIpTunerHostInfo) info;
+
+ var list = new List<LiveTvTunerInfo>();
+
+ for (var i = 0; i < satInfo.Tuners; i++)
+ {
+ list.Add(new LiveTvTunerInfo
+ {
+ Name = satInfo.FriendlyName ?? Name,
+ SourceType = Type,
+ Status = LiveTvTunerStatus.Available,
+ Id = info.Url.GetMD5().ToString("N") + i.ToString(CultureInfo.InvariantCulture),
+ Url = info.Url
+ });
+ }
+
+ return list;
+ }
}
}
diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
index 5f2fab457..a511d31b5 100644
--- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
+++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
@@ -43,7 +43,7 @@
<ItemGroup>
<Reference Include="CommonIO, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\CommonIO.1.0.0.7\lib\net45\CommonIO.dll</HintPath>
+ <HintPath>..\packages\CommonIO.1.0.0.8\lib\net45\CommonIO.dll</HintPath>
</Reference>
<Reference Include="Emby.XmlTv, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -52,9 +52,9 @@
<Reference Include="Interfaces.IO">
<HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
</Reference>
- <Reference Include="MediaBrowser.Naming, Version=1.0.5884.23751, Culture=neutral, processorArchitecture=MSIL">
+ <Reference Include="MediaBrowser.Naming, Version=1.0.5891.29179, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\MediaBrowser.Naming.1.0.0.47\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+ <HintPath>..\packages\MediaBrowser.Naming.1.0.0.48\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
</Reference>
<Reference Include="MoreLinq">
<HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
@@ -65,9 +65,9 @@
<Reference Include="ServiceStack.Api.Swagger">
<HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
</Reference>
- <Reference Include="SocketHttpListener, Version=1.0.5840.28948, Culture=neutral, processorArchitecture=MSIL">
+ <Reference Include="SocketHttpListener, Version=1.0.5900.37035, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
- <HintPath>..\packages\SocketHttpListener.1.0.0.25\lib\net45\SocketHttpListener.dll</HintPath>
+ <HintPath>..\packages\SocketHttpListener.1.0.0.27\lib\net45\SocketHttpListener.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -234,6 +234,7 @@
<Compile Include="LiveTv\TunerHosts\BaseTunerHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunHost.cs" />
<Compile Include="LiveTv\TunerHosts\HdHomerun\HdHomerunDiscovery.cs" />
+ <Compile Include="LiveTv\TunerHosts\M3uParser.cs" />
<Compile Include="LiveTv\TunerHosts\M3UTunerHost.cs" />
<Compile Include="LiveTv\ProgramImageProvider.cs" />
<Compile Include="LiveTv\RecordingImageProvider.cs" />
diff --git a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
index 6ca1bae6d..5f1bf0216 100644
--- a/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
@@ -239,7 +239,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
typeof(Year).Name,
typeof(Channel).Name,
typeof(AggregateFolder).Name,
- typeof(CollectionFolder).Name
+ typeof(CollectionFolder).Name,
+
+ // LiveTVManager handles recordings
+ typeof(LiveTvAudioRecording).Name,
+ typeof(LiveTvVideoRecording).Name
}
});
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
index a85872951..cd439d1f2 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
@@ -130,6 +130,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
"create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB, ParentId GUID)",
"create index if not exists idx_TypedBaseItems on TypedBaseItems(guid)",
+ "create index if not exists idx_PathTypedBaseItems on TypedBaseItems(Path)",
"create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
"create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))",
@@ -1014,7 +1015,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
if (!reader.IsDBNull(31))
{
- item.OfficialRating = reader.GetString(31);
+ item.OfficialRatingDescription = reader.GetString(31);
}
if (!reader.IsDBNull(32))
@@ -1804,6 +1805,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@ParentId", DbType.Guid).Value = query.ParentId.Value;
}
+ if (!string.IsNullOrWhiteSpace(query.Path))
+ {
+ whereClauses.Add("Path=@Path");
+ cmd.Parameters.Add(cmd, "@Path", DbType.String).Value = query.Path;
+ }
+
if (query.MinEndDate.HasValue)
{
whereClauses.Add("EndDate>=@MinEndDate");
diff --git a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
index 8b86d19a2..63c41c71f 100644
--- a/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
+++ b/MediaBrowser.Server.Implementations/Persistence/SqliteUserDataRepository.cs
@@ -56,6 +56,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
};
_connection.RunQueries(queries, Logger);
+
+ _connection.AddColumn(Logger, "userdata", "AudioStreamIndex", "int");
+ _connection.AddColumn(Logger, "userdata", "SubtitleStreamIndex", "int");
}
/// <summary>
@@ -127,7 +130,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate)";
+ cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -137,6 +140,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@isFavorite", DbType.Boolean).Value = userData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userData.LastPlayedDate;
+ cmd.Parameters.Add(cmd, "@AudioStreamIndex", DbType.Int32).Value = userData.AudioStreamIndex;
+ cmd.Parameters.Add(cmd, "@SubtitleStreamIndex", DbType.Int32).Value = userData.SubtitleStreamIndex;
cmd.Transaction = transaction;
@@ -199,7 +204,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
{
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate)";
+ cmd.CommandText = "replace into userdata (key, userId, rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex) values (@key, @userId, @rating,@played,@playCount,@isFavorite,@playbackPositionTicks,@lastPlayedDate,@AudioStreamIndex,@SubtitleStreamIndex)";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = userItemData.Key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -209,6 +214,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
cmd.Parameters.Add(cmd, "@isFavorite", DbType.Boolean).Value = userItemData.IsFavorite;
cmd.Parameters.Add(cmd, "@playbackPositionTicks", DbType.Int64).Value = userItemData.PlaybackPositionTicks;
cmd.Parameters.Add(cmd, "@lastPlayedDate", DbType.DateTime).Value = userItemData.LastPlayedDate;
+ cmd.Parameters.Add(cmd, "@AudioStreamIndex", DbType.Int32).Value = userItemData.AudioStreamIndex;
+ cmd.Parameters.Add(cmd, "@SubtitleStreamIndex", DbType.Int32).Value = userItemData.SubtitleStreamIndex;
cmd.Transaction = transaction;
@@ -275,7 +282,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate from userdata where key = @key and userId=@userId";
+ cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where key = @key and userId=@userId";
cmd.Parameters.Add(cmd, "@key", DbType.String).Value = key;
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -310,7 +317,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
using (var cmd = _connection.CreateCommand())
{
- cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate from userdata where userId=@userId";
+ cmd.CommandText = "select key,userid,rating,played,playCount,isFavorite,playbackPositionTicks,lastPlayedDate,AudioStreamIndex,SubtitleStreamIndex from userdata where userId=@userId";
cmd.Parameters.Add(cmd, "@userId", DbType.Guid).Value = userId;
@@ -350,6 +357,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
userData.LastPlayedDate = reader.GetDateTime(7).ToUniversalTime();
}
+ if (!reader.IsDBNull(8))
+ {
+ userData.AudioStreamIndex = reader.GetInt32(8);
+ }
+
+ if (!reader.IsDBNull(9))
+ {
+ userData.SubtitleStreamIndex = reader.GetInt32(9);
+ }
+
return userData;
}
diff --git a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
index c30d35ed2..4a69646f8 100644
--- a/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
+++ b/MediaBrowser.Server.Implementations/Photos/BaseDynamicImageProvider.cs
@@ -1,6 +1,5 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -14,6 +13,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
+using MediaBrowser.Model.Configuration;
namespace MediaBrowser.Server.Implementations.Photos
{
@@ -47,6 +47,41 @@ namespace MediaBrowser.Server.Implementations.Photos
};
}
+ private IEnumerable<ImageType> GetEnabledImages(IHasImages item)
+ {
+ //var options = ProviderManager.GetMetadataOptions(item);
+
+ return GetSupportedImages(item);
+ //return GetSupportedImages(item).Where(i => IsEnabled(options, i, item)).ToList();
+ }
+
+ private bool IsEnabled(MetadataOptions options, ImageType type, IHasImages item)
+ {
+ if (type == ImageType.Backdrop)
+ {
+ if (item.LockedFields.Contains(MetadataFields.Backdrops))
+ {
+ return false;
+ }
+ }
+ else if (type == ImageType.Screenshot)
+ {
+ if (item.LockedFields.Contains(MetadataFields.Screenshots))
+ {
+ return false;
+ }
+ }
+ else
+ {
+ if (item.LockedFields.Contains(MetadataFields.Images))
+ {
+ return false;
+ }
+ }
+
+ return options.IsEnabled(type);
+ }
+
public async Task<ItemUpdateType> FetchAsync(T item, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
if (!Supports(item))
@@ -55,7 +90,7 @@ namespace MediaBrowser.Server.Implementations.Photos
}
var updateType = ItemUpdateType.None;
- var supportedImages = GetSupportedImages(item).ToList();
+ var supportedImages = GetEnabledImages(item).ToList();
if (supportedImages.Contains(ImageType.Primary))
{
@@ -69,7 +104,6 @@ namespace MediaBrowser.Server.Implementations.Photos
updateType = updateType | thumbResult;
}
-
return updateType;
}
@@ -220,7 +254,7 @@ namespace MediaBrowser.Server.Implementations.Photos
return false;
}
- var supportedImages = GetSupportedImages(item).ToList();
+ var supportedImages = GetEnabledImages(item).ToList();
if (supportedImages.Contains(ImageType.Primary) && HasChanged(item, ImageType.Primary))
{
diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
index a9ce5ba54..08953b0be 100644
--- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs
+++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs
@@ -680,7 +680,7 @@ namespace MediaBrowser.Server.Implementations.Session
foreach (var user in users)
{
- await OnPlaybackProgress(user.Id, key, libraryItem, info.PositionTicks).ConfigureAwait(false);
+ await OnPlaybackProgress(user, key, libraryItem, info).ConfigureAwait(false);
}
}
@@ -712,15 +712,40 @@ namespace MediaBrowser.Server.Implementations.Session
StartIdleCheckTimer();
}
- private async Task OnPlaybackProgress(Guid userId, string userDataKey, BaseItem item, long? positionTicks)
+ private async Task OnPlaybackProgress(User user, string userDataKey, BaseItem item, PlaybackProgressInfo info)
{
- var data = _userDataRepository.GetUserData(userId, userDataKey);
+ var data = _userDataRepository.GetUserData(user.Id, userDataKey);
+
+ var positionTicks = info.PositionTicks;
if (positionTicks.HasValue)
{
_userDataRepository.UpdatePlayState(item, data, positionTicks.Value);
- await _userDataRepository.SaveUserData(userId, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
+ UpdatePlaybackSettings(user, info, data);
+
+ await _userDataRepository.SaveUserData(user.Id, item, data, UserDataSaveReason.PlaybackProgress, CancellationToken.None).ConfigureAwait(false);
+ }
+ }
+
+ private void UpdatePlaybackSettings(User user, PlaybackProgressInfo info, UserItemData data)
+ {
+ if (user.Configuration.RememberAudioSelections)
+ {
+ data.AudioStreamIndex = info.AudioStreamIndex;
+ }
+ else
+ {
+ data.AudioStreamIndex = null;
+ }
+
+ if (user.Configuration.RememberSubtitleSelections)
+ {
+ data.SubtitleStreamIndex = info.SubtitleStreamIndex;
+ }
+ else
+ {
+ data.SubtitleStreamIndex = null;
}
}
@@ -958,6 +983,26 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
+ if (user != null && command.ItemIds.Length == 1 && user.Configuration.EnableNextEpisodeAutoPlay)
+ {
+ var episode = _libraryManager.GetItemById(command.ItemIds[0]) as Episode;
+ if (episode != null)
+ {
+ var series = episode.Series;
+ if (series != null)
+ {
+ var episodes = series.GetEpisodes(user, false, false)
+ .SkipWhile(i => i.Id != episode.Id)
+ .ToList();
+
+ if (episodes.Count > 0)
+ {
+ command.ItemIds = episodes.Select(i => i.Id.ToString("N")).ToArray();
+ }
+ }
+ }
+ }
+
var controllingSession = GetSession(controllingSessionId);
AssertCanControl(session, controllingSession);
if (controllingSession.UserId.HasValue)
@@ -1268,7 +1313,17 @@ namespace MediaBrowser.Server.Implementations.Session
/// </summary>
/// <param name="request">The request.</param>
/// <returns>Task{SessionInfo}.</returns>
- public async Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
+ public Task<AuthenticationResult> AuthenticateNewSession(AuthenticationRequest request)
+ {
+ return AuthenticateNewSessionInternal(request, true);
+ }
+
+ public Task<AuthenticationResult> CreateNewSession(AuthenticationRequest request)
+ {
+ return AuthenticateNewSessionInternal(request, false);
+ }
+
+ private async Task<AuthenticationResult> AuthenticateNewSessionInternal(AuthenticationRequest request, bool enforcePassword)
{
var user = _userManager.Users
.FirstOrDefault(i => string.Equals(request.Username, i.Name, StringComparison.OrdinalIgnoreCase));
@@ -1281,13 +1336,16 @@ namespace MediaBrowser.Server.Implementations.Session
}
}
- var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
-
- if (!result)
+ if (enforcePassword)
{
- EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
+ var result = await _userManager.AuthenticateUser(request.Username, request.PasswordSha1, request.PasswordMd5, request.RemoteEndPoint).ConfigureAwait(false);
+
+ if (!result)
+ {
+ EventHelper.FireEventIfNotNull(AuthenticationFailed, this, new GenericEventArgs<AuthenticationRequest>(request), _logger);
- throw new SecurityException("Invalid user or password entered.");
+ throw new SecurityException("Invalid user or password entered.");
+ }
}
var token = await GetAuthorizationToken(user.Id.ToString("N"), request.DeviceId, request.App, request.AppVersion, request.DeviceName).ConfigureAwait(false);
@@ -1310,7 +1368,8 @@ namespace MediaBrowser.Server.Implementations.Session
ServerId = _appHost.SystemId
};
}
-
+
+
private async Task<string> GetAuthorizationToken(string userId, string deviceId, string app, string appVersion, string deviceName)
{
var existing = _authRepo.Get(new AuthenticationInfoQuery
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncHelper.cs b/MediaBrowser.Server.Implementations/Sync/SyncHelper.cs
index b6242950f..fb4e0c6be 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncHelper.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncHelper.cs
@@ -10,11 +10,11 @@ namespace MediaBrowser.Server.Implementations.Sync
{
if (string.Equals(quality, "medium", StringComparison.OrdinalIgnoreCase))
{
- profileBitrate = Math.Min(Convert.ToInt32(profileBitrate.Value * .7), 4000000);
+ profileBitrate = Math.Min(profileBitrate.Value, 4000000);
}
else if (string.Equals(quality, "low", StringComparison.OrdinalIgnoreCase))
{
- profileBitrate = Math.Min(Convert.ToInt32(profileBitrate.Value * .5), 1500000);
+ profileBitrate = Math.Min(profileBitrate.Value, 1500000);
}
}
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
index 95934908d..7086735c0 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs
@@ -19,6 +19,7 @@ using MediaBrowser.Model.Sync;
using MoreLinq;
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
@@ -125,7 +126,23 @@ namespace MediaBrowser.Server.Implementations.Sync
private string GetSyncJobItemName(BaseItem item)
{
- return item.Name;
+ var name = item.Name;
+ var episode = item as Episode;
+
+ if (episode != null)
+ {
+ if (episode.IndexNumber.HasValue)
+ {
+ name = "E" + episode.IndexNumber.Value.ToString(CultureInfo.InvariantCulture) + " - " + name;
+ }
+
+ if (episode.ParentIndexNumber.HasValue)
+ {
+ name = "S" + episode.ParentIndexNumber.Value.ToString(CultureInfo.InvariantCulture) + ", " + name;
+ }
+ }
+
+ return name;
}
public Task UpdateJobStatus(string id)
@@ -291,6 +308,11 @@ namespace MediaBrowser.Server.Implementations.Sync
throw new ArgumentException("Unrecognized category: " + category);
}
+ if (parent == null)
+ {
+ return new List<BaseItem>();
+ }
+
query.User = user;
var result = await parent.GetItems(query).ConfigureAwait(false);
@@ -321,6 +343,12 @@ namespace MediaBrowser.Server.Implementations.Sync
return series.GetEpisodes(user, false, false);
}
+ var season = item as Season;
+ if (season != null)
+ {
+ return season.GetEpisodes(user, false, false);
+ }
+
if (item.IsFolder)
{
var folder = (Folder)item;
@@ -360,6 +388,9 @@ namespace MediaBrowser.Server.Implementations.Sync
{
await EnsureSyncJobItems(null, cancellationToken).ConfigureAwait(false);
+ // Look job items that are supposedly transfering, but need to be requeued because the synced files have been deleted somehow
+ await HandleDeletedSyncFiles(cancellationToken).ConfigureAwait(false);
+
// If it already has a converting status then is must have been aborted during conversion
var result = _syncManager.GetJobItems(new SyncJobItemQuery
{
@@ -372,6 +403,28 @@ namespace MediaBrowser.Server.Implementations.Sync
CleanDeadSyncFiles();
}
+ private async Task HandleDeletedSyncFiles(CancellationToken cancellationToken)
+ {
+ // Look job items that are supposedly transfering, but need to be requeued because the synced files have been deleted somehow
+ var result = _syncManager.GetJobItems(new SyncJobItemQuery
+ {
+ Statuses = new[] { SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Transferring },
+ AddMetadata = false
+ });
+
+ foreach (var item in result.Items)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (string.IsNullOrWhiteSpace(item.OutputPath) || !_fileSystem.FileExists(item.OutputPath))
+ {
+ item.Status = SyncJobItemStatus.Queued;
+ await _syncManager.UpdateSyncJobItemInternal(item).ConfigureAwait(false);
+ await UpdateJobStatus(item.JobId).ConfigureAwait(false);
+ }
+ }
+ }
+
private void CleanDeadSyncFiles()
{
// TODO
@@ -699,7 +752,7 @@ namespace MediaBrowser.Server.Implementations.Sync
var path = Path.Combine(temporaryPath, filename);
- _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
+ _fileSystem.CreateDirectory(Path.GetDirectoryName(path));
using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, cancellationToken).ConfigureAwait(false))
{
diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
index 50a960956..8ebc8d91e 100644
--- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
+++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs
@@ -559,6 +559,12 @@ namespace MediaBrowser.Server.Implementations.Sync
jobItem.Status = SyncJobItemStatus.Synced;
jobItem.Progress = 100;
+ await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
+
+ var processor = GetSyncJobProcessor();
+
+ await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
+
if (!string.IsNullOrWhiteSpace(jobItem.TemporaryPath))
{
try
@@ -573,12 +579,6 @@ namespace MediaBrowser.Server.Implementations.Sync
_logger.ErrorException("Error deleting temporary job file: {0}", ex, jobItem.OutputPath);
}
}
-
- await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
-
- var processor = GetSyncJobProcessor();
-
- await processor.UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
}
private SyncJobProcessor GetSyncJobProcessor()
@@ -1015,7 +1015,7 @@ namespace MediaBrowser.Server.Implementations.Sync
{
var jobItem = _repo.GetJobItem(id);
- if (jobItem.Status != SyncJobItemStatus.Queued && jobItem.Status != SyncJobItemStatus.ReadyToTransfer && jobItem.Status != SyncJobItemStatus.Converting && jobItem.Status != SyncJobItemStatus.Failed && jobItem.Status != SyncJobItemStatus.Synced)
+ if (jobItem.Status != SyncJobItemStatus.Queued && jobItem.Status != SyncJobItemStatus.ReadyToTransfer && jobItem.Status != SyncJobItemStatus.Converting && jobItem.Status != SyncJobItemStatus.Failed && jobItem.Status != SyncJobItemStatus.Synced && jobItem.Status != SyncJobItemStatus.Transferring)
{
throw new ArgumentException("Operation is not valid for this job item");
}
diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config
index 816f85b42..772790f37 100644
--- a/MediaBrowser.Server.Implementations/packages.config
+++ b/MediaBrowser.Server.Implementations/packages.config
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="CommonIO" version="1.0.0.7" targetFramework="net45" />
+ <package id="CommonIO" version="1.0.0.8" targetFramework="net45" />
<package id="Emby.XmlTv" version="1.0.0.48" targetFramework="net45" />
<package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
- <package id="MediaBrowser.Naming" version="1.0.0.47" targetFramework="net45" />
+ <package id="MediaBrowser.Naming" version="1.0.0.48" 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="SocketHttpListener" version="1.0.0.25" targetFramework="net45" />
+ <package id="SocketHttpListener" version="1.0.0.27" targetFramework="net45" />
</packages> \ No newline at end of file