diff options
| author | T. Adams <t.adams88@gmail.com> | 2015-04-03 11:04:25 -0700 |
|---|---|---|
| committer | T. Adams <t.adams88@gmail.com> | 2015-04-03 11:04:25 -0700 |
| commit | abf12569ba2aa31ea3a00e4faf3adad2f740cbd9 (patch) | |
| tree | 47c57c6361825491d38e3def6b716926ddd9aa59 /MediaBrowser.Server.Implementations/Sync | |
| parent | 46c92107490263f8e6abefbd2259780013fa195d (diff) | |
| parent | ef505c8e9e2b8f348aeaa89be6bc446014b72996 (diff) | |
Merging in latest dev
Diffstat (limited to 'MediaBrowser.Server.Implementations/Sync')
6 files changed, 490 insertions, 103 deletions
diff --git a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs index e13042538..73400f834 100644 --- a/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs +++ b/MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Dlna; +using System.Collections.Generic; +using MediaBrowser.Model.Dlna; namespace MediaBrowser.Server.Implementations.Sync { @@ -25,6 +26,9 @@ namespace MediaBrowser.Server.Implementations.Sync mkvAudio += ",dca"; } + var videoProfile = "high|main|baseline|constrained baseline"; + var videoLevel = "41"; + DirectPlayProfiles = new[] { new DirectPlayProfile @@ -48,13 +52,37 @@ namespace MediaBrowser.Server.Implementations.Sync } }; - ContainerProfiles = new ContainerProfile[] { }; + ContainerProfiles = new[] + { + new ContainerProfile + { + Type = DlnaProfileType.Video, + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.NotEquals, + Property = ProfileConditionValue.NumAudioStreams, + Value = "0", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.EqualsAny, + Property = ProfileConditionValue.NumVideoStreams, + Value = "1", + IsRequired = false + } + } + } + }; - CodecProfiles = new[] + var codecProfiles = new List<CodecProfile> { new CodecProfile { Type = CodecType.Video, + Codec = "h264", Conditions = new [] { new ProfileCondition @@ -67,27 +95,142 @@ namespace MediaBrowser.Server.Implementations.Sync new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Width, + Value = "1920", + IsRequired = true + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.Height, Value = "1080", + IsRequired = true + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.RefFrames, + Value = "4", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoFramerate, + Value = "30", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.IsAnamorphic, + Value = "false", IsRequired = false }, new ProfileCondition { Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoLevel, + Value = videoLevel, + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.EqualsAny, + Property = ProfileConditionValue.VideoProfile, + Value = videoProfile, + IsRequired = false + } + } + }, + new CodecProfile + { + Type = CodecType.Video, + Codec = "mpeg4", + Conditions = new [] + { + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoBitDepth, + Value = "8", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Width, + Value = "1920", + IsRequired = true + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.Height, + Value = "1080", + IsRequired = true + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, Property = ProfileConditionValue.RefFrames, - Value = "12", + Value = "4", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.VideoFramerate, + Value = "30", + IsRequired = false + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.IsAnamorphic, + Value = "false", IsRequired = false } } } }; + var maxAudioChannels = supportsAc3 || supportsDca ? "5" : "2"; + codecProfiles.Add(new CodecProfile + { + Type = CodecType.VideoAudio, + Conditions = new[] + { + new ProfileCondition + { + Condition = ProfileConditionType.LessThanEqual, + Property = ProfileConditionValue.AudioChannels, + Value = maxAudioChannels, + IsRequired = true + }, + new ProfileCondition + { + Condition = ProfileConditionType.Equals, + Property = ProfileConditionValue.IsSecondaryAudio, + Value = "false", + IsRequired = false + } + } + }); + + CodecProfiles = codecProfiles.ToArray(); + SubtitleProfiles = new[] { new SubtitleProfile { Format = "srt", Method = SubtitleDeliveryMethod.External + }, + new SubtitleProfile + { + Format = "vtt", + Method = SubtitleDeliveryMethod.External } }; diff --git a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs index 429b72489..5bc8b8088 100644 --- a/MediaBrowser.Server.Implementations/Sync/MediaSync.cs +++ b/MediaBrowser.Server.Implementations/Sync/MediaSync.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.Globalization; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; @@ -40,6 +41,7 @@ namespace MediaBrowser.Server.Implementations.Sync CancellationToken cancellationToken) { var serverId = _appHost.SystemId; + var serverName = _appHost.FriendlyName; await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); progress.Report(3); @@ -51,7 +53,7 @@ namespace MediaBrowser.Server.Implementations.Sync totalProgress += 1; progress.Report(totalProgress); }); - await GetNewMedia(provider, dataProvider, target, serverId, innerProgress, cancellationToken); + await GetNewMedia(provider, dataProvider, target, serverId, serverName, innerProgress, cancellationToken); // Do the data sync twice so the server knows what was removed from the device await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false); @@ -65,12 +67,12 @@ namespace MediaBrowser.Server.Implementations.Sync SyncTarget target, CancellationToken cancellationToken) { - var localIds = await dataProvider.GetServerItemIds(target, serverId).ConfigureAwait(false); + var jobItemIds = await dataProvider.GetSyncJobItemIds(target, serverId).ConfigureAwait(false); var result = await _syncManager.SyncData(new SyncDataRequest { TargetId = target.Id, - LocalItemIds = localIds + SyncJobItemIds = jobItemIds }).ConfigureAwait(false); @@ -93,6 +95,7 @@ namespace MediaBrowser.Server.Implementations.Sync ISyncDataProvider dataProvider, SyncTarget target, string serverId, + string serverName, IProgress<double> progress, CancellationToken cancellationToken) { @@ -119,7 +122,7 @@ namespace MediaBrowser.Server.Implementations.Sync progress.Report(totalProgress); }); - await GetItem(provider, dataProvider, target, serverId, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); + await GetItem(provider, dataProvider, target, serverId, serverName, jobItem, innerProgress, cancellationToken).ConfigureAwait(false); numComplete++; startingPercent = numComplete; @@ -133,17 +136,16 @@ namespace MediaBrowser.Server.Implementations.Sync ISyncDataProvider dataProvider, SyncTarget target, string serverId, + string serverName, SyncedItem jobItem, IProgress<double> progress, CancellationToken cancellationToken) { var libraryItem = jobItem.Item; var internalSyncJobItem = _syncManager.GetJobItem(jobItem.SyncJobItemId); + var internalSyncJob = _syncManager.GetJob(jobItem.SyncJobId); - var fileTransferProgress = new ActionableProgress<double>(); - fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); - - var localItem = CreateLocalItem(provider, jobItem, target, libraryItem, serverId, jobItem.OriginalFileName); + var localItem = CreateLocalItem(provider, jobItem, internalSyncJob, target, libraryItem, serverId, serverName, jobItem.OriginalFileName); await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id); @@ -152,7 +154,10 @@ namespace MediaBrowser.Server.Implementations.Sync try { - var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem, target, cancellationToken).ConfigureAwait(false); + var fileTransferProgress = new ActionableProgress<double>(); + fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92)); + + var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath, target, fileTransferProgress, cancellationToken).ConfigureAwait(false); if (localItem.Item.MediaSources != null) { @@ -161,6 +166,7 @@ namespace MediaBrowser.Server.Implementations.Sync { mediaSource.Path = sendFileResult.Path; mediaSource.Protocol = sendFileResult.Protocol; + mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders; mediaSource.SupportsTranscoding = false; } } @@ -173,9 +179,7 @@ namespace MediaBrowser.Server.Implementations.Sync var mediaSource = localItem.Item.MediaSources.FirstOrDefault(); if (mediaSource != null) { - mediaSource.Path = sendFileResult.Path; - mediaSource.Protocol = sendFileResult.Protocol; - mediaSource.SupportsTranscoding = false; + await SendSubtitles(localItem, mediaSource, provider, dataProvider, target, cancellationToken).ConfigureAwait(false); } } @@ -205,48 +209,110 @@ namespace MediaBrowser.Server.Implementations.Sync private async Task SendSubtitles(LocalItem localItem, MediaSourceInfo mediaSource, IServerSyncProvider provider, ISyncDataProvider dataProvider, SyncTarget target, CancellationToken cancellationToken) { + var failedSubtitles = new List<MediaStream>(); + var requiresSave = false; + foreach (var mediaStream in mediaSource.MediaStreams .Where(i => i.Type == MediaStreamType.Subtitle && i.IsExternal) .ToList()) { - var sendFileResult = await SendFile(provider, mediaStream.Path, localItem, target, cancellationToken).ConfigureAwait(false); + try + { + var remotePath = GetRemoteSubtitlePath(localItem, mediaStream, provider, target); + var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, new Progress<double>(), cancellationToken).ConfigureAwait(false); + + // This is the path that will be used when talking to the provider + mediaStream.ExternalId = remotePath; + + // Keep track of all additional files for cleanup later. + localItem.AdditionalFiles.Add(remotePath); + + // This is the public path clients will use + mediaStream.Path = sendFileResult.Path; + requiresSave = true; + } + catch (Exception ex) + { + _logger.ErrorException("Error sending subtitle stream", ex); + failedSubtitles.Add(mediaStream); + } + } + + if (failedSubtitles.Count > 0) + { + mediaSource.MediaStreams = mediaSource.MediaStreams.Except(failedSubtitles).ToList(); + requiresSave = true; + } - mediaStream.Path = sendFileResult.Path; - + if (requiresSave) + { await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false); - } + } + } + + private string GetRemoteSubtitlePath(LocalItem item, MediaStream stream, IServerSyncProvider provider, SyncTarget target) + { + var path = item.LocalPath; + + var filename = GetSubtitleSaveFileName(item, stream.Language, stream.IsForced) + "." + stream.Codec.ToLower(); + + var parentPath = provider.GetParentDirectoryPath(path, target); + + path = Path.Combine(parentPath, filename); + + return path; + } + + private string GetSubtitleSaveFileName(LocalItem item, string language, bool isForced) + { + var path = item.LocalPath; + + var name = Path.GetFileNameWithoutExtension(path); + + if (!string.IsNullOrWhiteSpace(language)) + { + name += "." + language.ToLower(); + } + + if (isForced) + { + name += ".foreign"; + } + + return name; } private async Task RemoveItem(IServerSyncProvider provider, ISyncDataProvider dataProvider, string serverId, - string itemId, + string syncJobItemId, SyncTarget target, CancellationToken cancellationToken) { - var localItems = await dataProvider.GetCachedItems(target, serverId, itemId); + var localItems = await dataProvider.GetCachedItemsBySyncJobItemId(target, serverId, syncJobItemId); foreach (var localItem in localItems) { - var files = await GetFiles(provider, localItem, target, cancellationToken); + var files = localItem.AdditionalFiles.ToList(); + files.Insert(0, localItem.LocalPath); foreach (var file in files) { - _logger.Debug("Removing {0} from {1}.", file.Path, target.Name); + _logger.Debug("Removing {0} from {1}.", file, target.Name); - await provider.DeleteFile(file.Path, target, cancellationToken).ConfigureAwait(false); + await provider.DeleteFile(file, target, cancellationToken).ConfigureAwait(false); } await dataProvider.Delete(target, localItem.Id).ConfigureAwait(false); } } - private async Task<SendFileResult> SendFile(IServerSyncProvider provider, string inputPath, LocalItem item, SyncTarget target, CancellationToken cancellationToken) + private async Task<SyncedFileInfo> SendFile(IServerSyncProvider provider, string inputPath, string remotePath, SyncTarget target, IProgress<double> progress, CancellationToken cancellationToken) { - _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, item.LocalPath); + _logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, remotePath); using (var stream = _fileSystem.GetFileStream(inputPath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { - return await provider.SendFile(stream, item.LocalPath, target, new Progress<double>(), cancellationToken).ConfigureAwait(false); + return await provider.SendFile(stream, remotePath, target, progress, cancellationToken).ConfigureAwait(false); } } @@ -265,9 +331,9 @@ namespace MediaBrowser.Server.Implementations.Sync } } - public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncTarget target, BaseItemDto libraryItem, string serverId, string originalFileName) + public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncJob job, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName) { - var path = GetDirectoryPath(provider, syncedItem, libraryItem, serverId); + var path = GetDirectoryPath(provider, job, syncedItem, libraryItem, serverName); path.Add(GetLocalFileName(provider, libraryItem, originalFileName)); var localPath = provider.GetFullPath(path, target); @@ -284,26 +350,52 @@ namespace MediaBrowser.Server.Implementations.Sync ItemId = libraryItem.Id, ServerId = serverId, LocalPath = localPath, - Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id) + Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id), + SyncJobItemId = syncedItem.SyncJobItemId }; } - private string GetSyncJobFolderName(SyncedItem syncedItem, IServerSyncProvider provider) + private List<string> GetDirectoryPath(IServerSyncProvider provider, SyncJob job, SyncedItem syncedItem, BaseItemDto item, string serverName) { - var name = syncedItem.SyncJobName + syncedItem.SyncJobDateCreated.ToLocalTime().ToString("g"); + var parts = new List<string> + { + serverName + }; - name = GetValidFilename(provider, name); + var profileOption = _syncManager.GetProfileOptions(job.TargetId) + .FirstOrDefault(i => string.Equals(i.Id, job.Profile, StringComparison.OrdinalIgnoreCase)); - return name; - } + string name; - private List<string> GetDirectoryPath(IServerSyncProvider provider, SyncedItem syncedItem, BaseItemDto item, string serverId) - { - var parts = new List<string> + if (profileOption != null && !string.IsNullOrWhiteSpace(profileOption.Name)) { - serverId, - GetSyncJobFolderName(syncedItem, provider) - }; + name = profileOption.Name; + + if (job.Bitrate.HasValue) + { + name += "-" + job.Bitrate.Value.ToString(CultureInfo.InvariantCulture); + } + else + { + var qualityOption = _syncManager.GetQualityOptions(job.TargetId) + .FirstOrDefault(i => string.Equals(i.Id, job.Quality, StringComparison.OrdinalIgnoreCase)); + + if (qualityOption != null && !string.IsNullOrWhiteSpace(qualityOption.Name)) + { + name += "-" + qualityOption.Name; + } + } + } + else + { + name = syncedItem.SyncJobName + "-" + syncedItem.SyncJobDateCreated + .ToLocalTime() + .ToString("g") + .Replace(" ", "-"); + } + + name = GetValidFilename(provider, name); + parts.Add(name); if (item.IsType("episode")) { @@ -362,43 +454,5 @@ namespace MediaBrowser.Server.Implementations.Sync // We can always add this method to the sync provider if it's really needed return _fileSystem.GetValidFilename(filename); } - - private async Task<List<ItemFileInfo>> GetFiles(IServerSyncProvider provider, LocalItem item, SyncTarget target, CancellationToken cancellationToken) - { - var path = item.LocalPath; - path = provider.GetParentDirectoryPath(path, target); - - var list = await provider.GetFileSystemEntries(path, target, cancellationToken).ConfigureAwait(false); - - var itemFiles = new List<ItemFileInfo>(); - - var name = Path.GetFileNameWithoutExtension(item.LocalPath); - - foreach (var file in list.Where(f => f.Name.Contains(name))) - { - var itemFile = new ItemFileInfo - { - Path = file.Path, - Name = file.Name - }; - - if (IsSubtitleFile(file.Name)) - { - itemFile.Type = ItemFileType.Subtitles; - } - - itemFiles.Add(itemFile); - } - - return itemFiles; - } - - private static readonly string[] SupportedSubtitleExtensions = { ".srt", ".vtt" }; - private bool IsSubtitleFile(string path) - { - var ext = Path.GetExtension(path) ?? string.Empty; - - return SupportedSubtitleExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase); - } } } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs index 22c59610b..271b2bb39 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncJobProcessor.cs @@ -495,7 +495,7 @@ namespace MediaBrowser.Server.Implementations.Sync // No sense creating external subs if we're already burning one into the video var externalSubs = streamInfo.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode ? new List<SubtitleStreamInfo>() : - streamInfo.GetExternalSubtitles(false); + streamInfo.GetExternalSubtitles(false, true, null, null); // Mark as requiring conversion if transcoding the video, or if any subtitles need to be extracted var requiresVideoTranscoding = streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting; @@ -632,7 +632,8 @@ namespace MediaBrowser.Server.Implementations.Sync IsExternal = true, Language = subtitle.Language, Path = fileInfo.Path, - SupportsExternalStream = true + SupportsExternalStream = true, + Type = MediaStreamType.Subtitle }); startingIndex++; @@ -822,7 +823,7 @@ namespace MediaBrowser.Server.Implementations.Sync var hasMediaSources = item as IHasMediaSources; - var mediaSources = hasMediaSources.GetMediaSources(false).ToList(); + var mediaSources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false).ToList(); var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference) ? new string[] { } diff --git a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs index 2cf6c6853..102218979 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncManager.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncManager.cs @@ -3,6 +3,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Events; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -42,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly ILogger _logger; private readonly IUserManager _userManager; private readonly Func<IDtoService> _dtoService; - private readonly IApplicationHost _appHost; + private readonly IServerApplicationHost _appHost; private readonly ITVSeriesManager _tvSeriesManager; private readonly Func<IMediaEncoder> _mediaEncoder; private readonly IFileSystem _fileSystem; @@ -60,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.Sync public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemUpdated; public event EventHandler<GenericEventArgs<SyncJobItem>> SyncJobItemCreated; - public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder, IFileSystem fileSystem, Func<ISubtitleEncoder> subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func<IMediaSourceManager> mediaSourceManager, IJsonSerializer json) + public SyncManager(ILibraryManager libraryManager, ISyncRepository repo, IImageProcessor imageProcessor, ILogger logger, IUserManager userManager, Func<IDtoService> dtoService, IServerApplicationHost appHost, ITVSeriesManager tvSeriesManager, Func<IMediaEncoder> mediaEncoder, IFileSystem fileSystem, Func<ISubtitleEncoder> subtitleEncoder, IConfigurationManager config, IUserDataManager userDataManager, Func<IMediaSourceManager> mediaSourceManager, IJsonSerializer json) { _libraryManager = libraryManager; _repo = repo; @@ -94,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Sync public ISyncDataProvider GetDataProvider(IServerSyncProvider provider, SyncTarget target) { - return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost.SystemId, _logger, _json, _fileSystem, _config.CommonApplicationPaths)); + return _dataProviders.GetOrAdd(target.Id, key => new TargetDataProvider(provider, target, _appHost, _logger, _json, _fileSystem, _config.CommonApplicationPaths)); } public async Task<SyncJobCreationResult> CreateJob(SyncJobRequest request) @@ -676,14 +677,16 @@ namespace MediaBrowser.Server.Implementations.Sync syncedItem.Item.MediaSources = new List<MediaSourceInfo>(); - // This will be null for items that are not audio/video - if (mediaSource == null) + syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path); + if (string.IsNullOrWhiteSpace(syncedItem.OriginalFileName)) { - syncedItem.OriginalFileName = Path.GetFileName(libraryItem.Path); + syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path); } - else + + // This will be null for items that are not audio/video + if (mediaSource != null) { - syncedItem.OriginalFileName = Path.GetFileName(mediaSource.Path); + syncedItem.OriginalFileName = Path.ChangeExtension(syncedItem.OriginalFileName, Path.GetExtension(mediaSource.Path)); syncedItem.Item.MediaSources.Add(mediaSource); } @@ -721,7 +724,7 @@ namespace MediaBrowser.Server.Implementations.Sync var jobItemResult = GetJobItems(new SyncJobItemQuery { TargetId = targetId, - Statuses = new SyncJobItemStatus[] + Statuses = new[] { SyncJobItemStatus.ReadyToTransfer } @@ -735,10 +738,15 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task<SyncDataResponse> SyncData(SyncDataRequest request) { + if (request.SyncJobItemIds != null) + { + return await SyncDataUsingSyncJobItemIds(request).ConfigureAwait(false); + } + var jobItemResult = GetJobItems(new SyncJobItemQuery { TargetId = request.TargetId, - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Synced } + Statuses = new[] { SyncJobItemStatus.Synced } }); var response = new SyncDataResponse(); @@ -814,6 +822,87 @@ namespace MediaBrowser.Server.Implementations.Sync return response; } + private async Task<SyncDataResponse> SyncDataUsingSyncJobItemIds(SyncDataRequest request) + { + var jobItemResult = GetJobItems(new SyncJobItemQuery + { + TargetId = request.TargetId, + Statuses = new[] { SyncJobItemStatus.Synced } + }); + + var response = new SyncDataResponse(); + + foreach (var jobItem in jobItemResult.Items) + { + if (request.SyncJobItemIds.Contains(jobItem.Id, StringComparer.OrdinalIgnoreCase)) + { + var job = _repo.GetJob(jobItem.JobId); + var user = _userManager.GetUserById(job.UserId); + + if (jobItem.IsMarkedForRemoval) + { + // Tell the device to remove it since it has been marked for removal + response.ItemIdsToRemove.Add(jobItem.Id); + } + else if (user == null) + { + // Tell the device to remove it since the user is gone now + response.ItemIdsToRemove.Add(jobItem.Id); + } + else if (job.UnwatchedOnly) + { + var libraryItem = _libraryManager.GetItemById(jobItem.ItemId); + + if (IsLibraryItemAvailable(libraryItem)) + { + if (libraryItem.IsPlayed(user) && libraryItem is Video) + { + // Tell the device to remove it since it has been played + response.ItemIdsToRemove.Add(jobItem.Id); + } + } + else + { + // Tell the device to remove it since it's no longer available + response.ItemIdsToRemove.Add(jobItem.Id); + } + } + } + else + { + // Content is no longer on the device + jobItem.Status = SyncJobItemStatus.RemovedFromDevice; + await UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false); + } + } + + // Now check each item that's on the device + foreach (var syncJobItemId in request.SyncJobItemIds) + { + // See if it's already marked for removal + if (response.ItemIdsToRemove.Contains(syncJobItemId, StringComparer.OrdinalIgnoreCase)) + { + continue; + } + + // If there isn't a sync job for this item, mark it for removal + if (!jobItemResult.Items.Any(i => string.Equals(syncJobItemId, i.Id, StringComparison.OrdinalIgnoreCase))) + { + response.ItemIdsToRemove.Add(syncJobItemId); + } + } + + response.ItemIdsToRemove = response.ItemIdsToRemove.Distinct(StringComparer.OrdinalIgnoreCase).ToList(); + + var itemsOnDevice = request.LocalItemIds + .Except(response.ItemIdsToRemove) + .ToList(); + + SetUserAccess(request, response, itemsOnDevice); + + return response; + } + private void SetUserAccess(SyncDataRequest request, SyncDataResponse response, List<string> itemIds) { var users = request.OfflineUserIds diff --git a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs index 893b16b14..d1ef523e1 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncedMediaSourceProvider.cs @@ -1,8 +1,10 @@ -using MediaBrowser.Controller; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Logging; using MediaBrowser.Model.Sync; using System; using System.Collections.Generic; @@ -16,10 +18,12 @@ namespace MediaBrowser.Server.Implementations.Sync { private readonly SyncManager _syncManager; private readonly IServerApplicationHost _appHost; + private readonly ILogger _logger; - public SyncedMediaSourceProvider(ISyncManager syncManager, IServerApplicationHost appHost) + public SyncedMediaSourceProvider(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger) { _appHost = appHost; + _logger = logger; _syncManager = (SyncManager)syncManager; } @@ -28,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.Sync var jobItemResult = _syncManager.GetJobItems(new SyncJobItemQuery { AddMetadata = false, - Statuses = new SyncJobItemStatus[] { SyncJobItemStatus.Synced }, + Statuses = new[] { SyncJobItemStatus.Synced }, ItemId = item.Id.ToString("N") }); @@ -49,14 +53,17 @@ namespace MediaBrowser.Server.Implementations.Sync if (targetTuple != null) { var syncTarget = targetTuple.Item2; - + var syncProvider = targetTuple.Item1; var dataProvider = _syncManager.GetDataProvider(targetTuple.Item1, syncTarget); var localItems = await dataProvider.GetCachedItems(syncTarget, serverId, item.Id.ToString("N")).ConfigureAwait(false); foreach (var localItem in localItems) { - list.AddRange(localItem.Item.MediaSources); + foreach (var mediaSource in localItem.Item.MediaSources) + { + AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget); + } } } } @@ -64,5 +71,76 @@ namespace MediaBrowser.Server.Implementations.Sync return list; } + + private void AddMediaSource(List<MediaSourceInfo> list, + LocalItem item, + MediaSourceInfo mediaSource, + IServerSyncProvider provider, + SyncTarget target) + { + SetStaticMediaSourceInfo(item, mediaSource); + + var requiresDynamicAccess = provider as IHasDynamicAccess; + + if (requiresDynamicAccess != null) + { + mediaSource.RequiresOpening = true; + + var keyList = new List<string>(); + keyList.Add(provider.GetType().FullName.GetMD5().ToString("N")); + keyList.Add(target.Id.GetMD5().ToString("N")); + keyList.Add(item.Id); + mediaSource.OpenToken = string.Join("|", keyList.ToArray()); + } + + list.Add(mediaSource); + } + + public async Task<MediaSourceInfo> OpenMediaSource(string openToken, CancellationToken cancellationToken) + { + var openKeys = openToken.Split(new[] { '|' }, 3); + + var provider = _syncManager.ServerSyncProviders + .FirstOrDefault(i => string.Equals(openKeys[0], i.GetType().FullName.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); + + var target = provider.GetAllSyncTargets() + .FirstOrDefault(i => string.Equals(openKeys[1], i.Id.GetMD5().ToString("N"), StringComparison.OrdinalIgnoreCase)); + + var dataProvider = _syncManager.GetDataProvider(provider, target); + var localItem = await dataProvider.Get(target, openKeys[2]).ConfigureAwait(false); + + var requiresDynamicAccess = (IHasDynamicAccess)provider; + var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(localItem.LocalPath, target, cancellationToken).ConfigureAwait(false); + + var mediaSource = localItem.Item.MediaSources.First(); + mediaSource.LiveStreamId = Guid.NewGuid().ToString(); + SetStaticMediaSourceInfo(localItem, mediaSource); + + foreach (var stream in mediaSource.MediaStreams) + { + if (!string.IsNullOrWhiteSpace(stream.ExternalId)) + { + var dynamicStreamInfo = await requiresDynamicAccess.GetSyncedFileInfo(stream.ExternalId, target, cancellationToken).ConfigureAwait(false); + stream.Path = dynamicStreamInfo.Path; + } + } + + mediaSource.Path = dynamicInfo.Path; + mediaSource.Protocol = dynamicInfo.Protocol; + mediaSource.RequiredHttpHeaders = dynamicInfo.RequiredHttpHeaders; + + return mediaSource; + } + + private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource) + { + mediaSource.Id = item.Id; + mediaSource.SupportsTranscoding = false; + } + + public Task CloseMediaSource(string liveStreamId, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs index ca9d96c12..dea868848 100644 --- a/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs +++ b/MediaBrowser.Server.Implementations/Sync/TargetDataProvider.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller; using MediaBrowser.Controller.Sync; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Serialization; @@ -26,11 +27,11 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly IJsonSerializer _json; private readonly IFileSystem _fileSystem; private readonly IApplicationPaths _appPaths; - private readonly string _serverId; + private readonly IServerApplicationHost _appHost; private readonly SemaphoreSlim _cacheFileLock = new SemaphoreSlim(1, 1); - public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, string serverId, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths) + public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths) { _logger = logger; _json = json; @@ -38,7 +39,7 @@ namespace MediaBrowser.Server.Implementations.Sync _target = target; _fileSystem = fileSystem; _appPaths = appPaths; - _serverId = serverId; + _appHost = appHost; } private string GetCachePath() @@ -50,13 +51,21 @@ namespace MediaBrowser.Server.Implementations.Sync { var parts = new List<string> { - _serverId, + _appHost.FriendlyName, "data.json" }; + parts = parts.Select(i => GetValidFilename(_provider, i)).ToList(); + return _provider.GetFullPath(parts, _target); } + private string GetValidFilename(IServerSyncProvider provider, string filename) + { + // We can always add this method to the sync provider if it's really needed + return _fileSystem.GetValidFilename(filename); + } + private async Task CacheData(Stream stream) { var cachePath = GetCachePath(); @@ -167,6 +176,11 @@ namespace MediaBrowser.Server.Implementations.Sync return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).Select(i => i.ItemId).ToList()); } + public Task<List<string>> GetSyncJobItemIds(SyncTarget target, string serverId) + { + return GetData(items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).Select(i => i.SyncJobItemId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList()); + } + public Task AddOrUpdate(SyncTarget target, LocalItem item) { return UpdateData(items => @@ -239,5 +253,13 @@ namespace MediaBrowser.Server.Implementations.Sync return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)) .ToList(); } + + public async Task<List<LocalItem>> GetCachedItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId) + { + var items = await GetCachedData().ConfigureAwait(false); + + return items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } } } |
