mirror of
https://github.com/jellyfin/jellyfin.git
synced 2024-11-20 04:18:51 -07:00
commit
90853a3ab1
@ -90,7 +90,6 @@ using Emby.Server.Core.Localization;
|
||||
using Emby.Server.Implementations.Migrations;
|
||||
using Emby.Server.Implementations.Security;
|
||||
using Emby.Server.Implementations.Social;
|
||||
using Emby.Server.Implementations.Sync;
|
||||
using Emby.Server.Implementations.Channels;
|
||||
using Emby.Server.Implementations.Collections;
|
||||
using Emby.Server.Implementations.Dto;
|
||||
@ -109,7 +108,6 @@ using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.ServerManager;
|
||||
using Emby.Server.Implementations.Session;
|
||||
using Emby.Server.Implementations.Social;
|
||||
using Emby.Server.Implementations.Sync;
|
||||
using Emby.Server.Implementations.TV;
|
||||
using Emby.Server.Implementations.Updates;
|
||||
using MediaBrowser.Model.Activity;
|
||||
@ -526,6 +524,7 @@ namespace Emby.Server.Core
|
||||
}
|
||||
|
||||
protected abstract IConnectManager CreateConnectManager();
|
||||
protected abstract ISyncManager CreateSyncManager();
|
||||
|
||||
/// <summary>
|
||||
/// Registers resources that classes will depend on
|
||||
@ -587,9 +586,6 @@ namespace Emby.Server.Core
|
||||
AuthenticationRepository = await GetAuthenticationRepository().ConfigureAwait(false);
|
||||
RegisterSingleInstance(AuthenticationRepository);
|
||||
|
||||
SyncRepository = GetSyncRepository();
|
||||
RegisterSingleInstance(SyncRepository);
|
||||
|
||||
UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this, JsonSerializer, FileSystemManager, CryptographyProvider, _defaultUserNameFactory());
|
||||
RegisterSingleInstance(UserManager);
|
||||
|
||||
@ -627,7 +623,7 @@ namespace Emby.Server.Core
|
||||
TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager, ServerConfigurationManager);
|
||||
RegisterSingleInstance(TVSeriesManager);
|
||||
|
||||
SyncManager = new SyncManager(LibraryManager, SyncRepository, ImageProcessor, LogManager.GetLogger("SyncManager"), UserManager, () => DtoService, this, TVSeriesManager, () => MediaEncoder, FileSystemManager, () => SubtitleEncoder, ServerConfigurationManager, UserDataManager, () => MediaSourceManager, JsonSerializer, TaskManager, MemoryStreamFactory);
|
||||
SyncManager = CreateSyncManager();
|
||||
RegisterSingleInstance(SyncManager);
|
||||
|
||||
DtoService = new DtoService(LogManager.GetLogger("DtoService"), LibraryManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager, ProviderManager, () => ChannelManager, SyncManager, this, () => DeviceManager, () => MediaSourceManager, () => LiveTvManager);
|
||||
@ -944,15 +940,6 @@ namespace Emby.Server.Core
|
||||
return repo;
|
||||
}
|
||||
|
||||
private ISyncRepository GetSyncRepository()
|
||||
{
|
||||
var repo = new SyncRepository(LogManager.GetLogger("SyncRepository"), JsonSerializer, ServerConfigurationManager.ApplicationPaths);
|
||||
|
||||
repo.Initialize();
|
||||
|
||||
return repo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures the repositories.
|
||||
/// </summary>
|
||||
|
@ -264,23 +264,6 @@
|
||||
<Compile Include="Sorting\StartDateComparer.cs" />
|
||||
<Compile Include="Sorting\StudioComparer.cs" />
|
||||
<Compile Include="StartupOptions.cs" />
|
||||
<Compile Include="Sync\AppSyncProvider.cs" />
|
||||
<Compile Include="Sync\CloudSyncProfile.cs" />
|
||||
<Compile Include="Sync\IHasSyncQuality.cs" />
|
||||
<Compile Include="Sync\MediaSync.cs" />
|
||||
<Compile Include="Sync\MultiProviderSync.cs" />
|
||||
<Compile Include="Sync\ServerSyncScheduledTask.cs" />
|
||||
<Compile Include="Sync\SyncConfig.cs" />
|
||||
<Compile Include="Sync\SyncConvertScheduledTask.cs" />
|
||||
<Compile Include="Sync\SyncedMediaSourceProvider.cs" />
|
||||
<Compile Include="Sync\SyncHelper.cs" />
|
||||
<Compile Include="Sync\SyncJobOptions.cs" />
|
||||
<Compile Include="Sync\SyncJobProcessor.cs" />
|
||||
<Compile Include="Sync\SyncManager.cs" />
|
||||
<Compile Include="Sync\SyncNotificationEntryPoint.cs" />
|
||||
<Compile Include="Sync\SyncRegistrationInfo.cs" />
|
||||
<Compile Include="Sync\SyncRepository.cs" />
|
||||
<Compile Include="Sync\TargetDataProvider.cs" />
|
||||
<Compile Include="TV\SeriesPostScanTask.cs" />
|
||||
<Compile Include="TV\TVSeriesManager.cs" />
|
||||
<Compile Include="Udp\UdpServer.cs" />
|
||||
@ -329,7 +312,7 @@
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.1\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll</HintPath>
|
||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="UniversalDetector, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
|
@ -40,8 +40,7 @@ namespace Emby.Server.Implementations.EntryPoints
|
||||
{ "serverid", _applicationHost.SystemId },
|
||||
{ "deviceid", _applicationHost.SystemId },
|
||||
{ "ver", _applicationHost.ApplicationVersion.ToString() },
|
||||
{ "platform", _applicationHost.OperatingSystemDisplayName },
|
||||
{ "isservice", _applicationHost.IsRunningAsService.ToString().ToLower()}
|
||||
{ "platform", _applicationHost.OperatingSystemDisplayName }
|
||||
};
|
||||
|
||||
var users = _userManager.Users.ToList();
|
||||
|
@ -474,17 +474,30 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
|
||||
|
||||
public ChannelInfo GetEpgChannelFromTunerChannel(List<NameValuePair> mappings, ChannelInfo tunerChannel, List<ChannelInfo> epgChannels)
|
||||
{
|
||||
var tunerChannelId = string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId)
|
||||
? tunerChannel.Id
|
||||
: tunerChannel.TunerChannelId;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannelId))
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Id))
|
||||
{
|
||||
var mappedTunerChannelId = GetMappedChannel(tunerChannelId, mappings);
|
||||
var mappedTunerChannelId = GetMappedChannel(tunerChannel.Id, mappings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
|
||||
{
|
||||
mappedTunerChannelId = tunerChannelId;
|
||||
mappedTunerChannelId = tunerChannel.Id;
|
||||
}
|
||||
|
||||
var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (channel != null)
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.TunerChannelId))
|
||||
{
|
||||
var mappedTunerChannelId = GetMappedChannel(tunerChannel.TunerChannelId, mappings);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(mappedTunerChannelId))
|
||||
{
|
||||
mappedTunerChannelId = tunerChannel.TunerChannelId;
|
||||
}
|
||||
|
||||
var channel = epgChannels.FirstOrDefault(i => string.Equals(mappedTunerChannelId, i.Id, StringComparison.OrdinalIgnoreCase));
|
||||
|
@ -2926,7 +2926,7 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
var result = new TunerChannelMapping
|
||||
{
|
||||
Name = tunerChannel.Name,
|
||||
Id = tunerChannel.TunerChannelId
|
||||
Id = tunerChannel.Id
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tunerChannel.Number))
|
||||
@ -2934,11 +2934,6 @@ namespace Emby.Server.Implementations.LiveTv
|
||||
result.Name = tunerChannel.Number + " " + result.Name;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result.Id))
|
||||
{
|
||||
result.Id = tunerChannel.Id;
|
||||
}
|
||||
|
||||
var providerChannel = EmbyTV.EmbyTV.Current.GetEpgChannelFromTunerChannel(mappings, tunerChannel, epgChannels);
|
||||
|
||||
if (providerChannel != null)
|
||||
|
@ -35,10 +35,10 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
MediaEncoder = mediaEncoder;
|
||||
}
|
||||
|
||||
protected abstract Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
|
||||
protected abstract Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo tuner, CancellationToken cancellationToken);
|
||||
public abstract string Type { get; }
|
||||
|
||||
public async Task<IEnumerable<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)
|
||||
public async Task<List<ChannelInfo>> GetChannels(TunerHostInfo tuner, bool enableCache, CancellationToken cancellationToken)
|
||||
{
|
||||
ChannelCache cache = null;
|
||||
var key = tuner.Id;
|
||||
@ -73,7 +73,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken)
|
||||
public async Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken)
|
||||
{
|
||||
var list = new List<ChannelInfo>();
|
||||
|
||||
|
@ -84,7 +84,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
var lineup = await GetLineup(info, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@ -99,7 +99,8 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
|
||||
AudioCodec = i.AudioCodec,
|
||||
VideoCodec = i.VideoCodec,
|
||||
ChannelType = ChannelType.TV
|
||||
});
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, DiscoverResponse> _modelCache = new Dictionary<string, DiscoverResponse>();
|
||||
|
@ -48,9 +48,11 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
|
||||
private const string ChannelIdPrefix = "m3u_";
|
||||
|
||||
protected override async Task<IEnumerable<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
protected override async Task<List<ChannelInfo>> GetChannelsInternal(TunerHostInfo info, CancellationToken cancellationToken)
|
||||
{
|
||||
return await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false);
|
||||
var result = await new M3uParser(Logger, _fileSystem, _httpClient, _appHost).Parse(info.Url, ChannelIdPrefix, info.Id, !info.EnableTvgId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result.Cast<ChannelInfo>().ToList();
|
||||
}
|
||||
|
||||
public Task<List<LiveTvTunerInfo>> GetTunerInfos(CancellationToken cancellationToken)
|
||||
|
@ -136,11 +136,26 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
channel.Name = GetChannelName(extInf, attributes);
|
||||
channel.Number = GetChannelNumber(extInf, attributes, mediaUrl);
|
||||
|
||||
var channelId = GetTunerChannelId(attributes);
|
||||
string tvgId;
|
||||
attributes.TryGetValue("tvg-id", out tvgId);
|
||||
|
||||
string channelId;
|
||||
attributes.TryGetValue("channel-id", out channelId);
|
||||
|
||||
channel.TunerChannelId = string.IsNullOrWhiteSpace(tvgId) ? channelId : tvgId;
|
||||
|
||||
var channelIdValues = new List<string>();
|
||||
if (!string.IsNullOrWhiteSpace(channelId))
|
||||
{
|
||||
channel.Id = channelId;
|
||||
channel.TunerChannelId = channelId;
|
||||
channelIdValues.Add(channelId);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(tvgId))
|
||||
{
|
||||
channelIdValues.Add(tvgId);
|
||||
}
|
||||
if (channelIdValues.Count > 0)
|
||||
{
|
||||
channel.Id = string.Join("_", channelIdValues.ToArray());
|
||||
}
|
||||
|
||||
return channel;
|
||||
@ -296,24 +311,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
|
||||
return name;
|
||||
}
|
||||
|
||||
private string GetTunerChannelId(Dictionary<string, string> attributes)
|
||||
{
|
||||
var values = new List<string>();
|
||||
|
||||
string result;
|
||||
if (attributes.TryGetValue("tvg-id", out result))
|
||||
{
|
||||
values.Add(result);
|
||||
}
|
||||
|
||||
if (attributes.TryGetValue("channel-id", out result))
|
||||
{
|
||||
values.Add(result);
|
||||
}
|
||||
|
||||
return values.Count == 0 ? null : string.Join("-", values.ToArray());
|
||||
}
|
||||
|
||||
private Dictionary<string, string> ParseExtInf(string line, out string remaining)
|
||||
{
|
||||
var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
@ -277,8 +277,7 @@ namespace Emby.Server.Implementations.Security
|
||||
{ "systemid", _appHost.SystemId },
|
||||
{ "mb2equiv", mb2Equivalent },
|
||||
{ "ver", version },
|
||||
{ "platform", _appHost.OperatingSystemDisplayName },
|
||||
{ "isservice", _appHost.IsRunningAsService.ToString().ToLower() }
|
||||
{ "platform", _appHost.OperatingSystemDisplayName }
|
||||
};
|
||||
|
||||
try
|
||||
|
@ -1,118 +0,0 @@
|
||||
using MediaBrowser.Controller.Devices;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.Devices;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Sync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class AppSyncProvider : ISyncProvider, IHasUniqueTargetIds, IHasSyncQuality, IHasDuplicateCheck
|
||||
{
|
||||
private readonly IDeviceManager _deviceManager;
|
||||
|
||||
public AppSyncProvider(IDeviceManager deviceManager)
|
||||
{
|
||||
_deviceManager = deviceManager;
|
||||
}
|
||||
|
||||
public IEnumerable<SyncTarget> GetSyncTargets(string userId)
|
||||
{
|
||||
return _deviceManager.GetDevices(new DeviceQuery
|
||||
{
|
||||
SupportsSync = true,
|
||||
UserId = userId
|
||||
|
||||
}).Items.Select(i => new SyncTarget
|
||||
{
|
||||
Id = i.Id,
|
||||
Name = i.Name
|
||||
});
|
||||
}
|
||||
|
||||
public DeviceProfile GetDeviceProfile(SyncTarget target, string profile, string quality)
|
||||
{
|
||||
var caps = _deviceManager.GetCapabilities(target.Id);
|
||||
|
||||
var deviceProfile = caps == null || caps.DeviceProfile == null ? new DeviceProfile() : caps.DeviceProfile;
|
||||
deviceProfile.MaxStaticBitrate = SyncHelper.AdjustBitrate(deviceProfile.MaxStaticBitrate, quality);
|
||||
|
||||
return deviceProfile;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Mobile Sync"; }
|
||||
}
|
||||
|
||||
public IEnumerable<SyncTarget> GetAllSyncTargets()
|
||||
{
|
||||
return _deviceManager.GetDevices(new DeviceQuery
|
||||
{
|
||||
SupportsSync = true
|
||||
|
||||
}).Items.Select(i => new SyncTarget
|
||||
{
|
||||
Id = i.Id,
|
||||
Name = i.Name
|
||||
});
|
||||
}
|
||||
|
||||
public IEnumerable<SyncQualityOption> GetQualityOptions(SyncTarget target)
|
||||
{
|
||||
return new List<SyncQualityOption>
|
||||
{
|
||||
new SyncQualityOption
|
||||
{
|
||||
Name = "Original",
|
||||
Id = "original",
|
||||
Description = "Syncs original files as-is, regardless of whether the device is capable of playing them or not."
|
||||
},
|
||||
new SyncQualityOption
|
||||
{
|
||||
Name = "High",
|
||||
Id = "high",
|
||||
IsDefault = true
|
||||
},
|
||||
new SyncQualityOption
|
||||
{
|
||||
Name = "Medium",
|
||||
Id = "medium"
|
||||
},
|
||||
new SyncQualityOption
|
||||
{
|
||||
Name = "Low",
|
||||
Id = "low"
|
||||
},
|
||||
new SyncQualityOption
|
||||
{
|
||||
Name = "Custom",
|
||||
Id = "custom"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<SyncProfileOption> GetProfileOptions(SyncTarget target)
|
||||
{
|
||||
return new List<SyncProfileOption>();
|
||||
}
|
||||
|
||||
public SyncJobOptions GetSyncJobOptions(SyncTarget target, string profile, string quality)
|
||||
{
|
||||
var isConverting = !string.Equals(quality, "original", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
return new SyncJobOptions
|
||||
{
|
||||
DeviceProfile = GetDeviceProfile(target, profile, quality),
|
||||
IsConverting = isConverting
|
||||
};
|
||||
}
|
||||
|
||||
public bool AllowDuplicateJobItem(SyncJobItem original, SyncJobItem duplicate)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,288 +0,0 @@
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class CloudSyncProfile : DeviceProfile
|
||||
{
|
||||
public CloudSyncProfile(bool supportsAc3, bool supportsDca)
|
||||
{
|
||||
Name = "Cloud Sync";
|
||||
|
||||
MaxStreamingBitrate = 20000000;
|
||||
MaxStaticBitrate = 20000000;
|
||||
|
||||
var mkvAudio = "aac,mp3";
|
||||
var mp4Audio = "aac";
|
||||
|
||||
if (supportsAc3)
|
||||
{
|
||||
mkvAudio += ",ac3";
|
||||
mp4Audio += ",ac3";
|
||||
}
|
||||
|
||||
if (supportsDca)
|
||||
{
|
||||
mkvAudio += ",dca,dts";
|
||||
}
|
||||
|
||||
var videoProfile = "high|main|baseline|constrained baseline";
|
||||
var videoLevel = "40";
|
||||
|
||||
DirectPlayProfiles = new[]
|
||||
{
|
||||
//new DirectPlayProfile
|
||||
//{
|
||||
// Container = "mkv",
|
||||
// VideoCodec = "h264,mpeg4",
|
||||
// AudioCodec = mkvAudio,
|
||||
// Type = DlnaProfileType.Video
|
||||
//},
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "mp4,mov,m4v",
|
||||
VideoCodec = "h264,mpeg4",
|
||||
AudioCodec = mp4Audio,
|
||||
Type = DlnaProfileType.Video
|
||||
},
|
||||
new DirectPlayProfile
|
||||
{
|
||||
Container = "mp3",
|
||||
Type = DlnaProfileType.Audio
|
||||
}
|
||||
};
|
||||
|
||||
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.Equals,
|
||||
Property = ProfileConditionValue.NumVideoStreams,
|
||||
Value = "1",
|
||||
IsRequired = false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var codecProfiles = new List<CodecProfile>
|
||||
{
|
||||
new CodecProfile
|
||||
{
|
||||
Type = CodecType.Video,
|
||||
Codec = "h264",
|
||||
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 = "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 = "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
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
codecProfiles.Add(new CodecProfile
|
||||
{
|
||||
Type = CodecType.VideoAudio,
|
||||
Codec = "ac3",
|
||||
Conditions = new[]
|
||||
{
|
||||
new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
Property = ProfileConditionValue.AudioChannels,
|
||||
Value = "6",
|
||||
IsRequired = false
|
||||
},
|
||||
new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.LessThanEqual,
|
||||
Property = ProfileConditionValue.AudioBitrate,
|
||||
Value = "320000",
|
||||
IsRequired = true
|
||||
},
|
||||
new ProfileCondition
|
||||
{
|
||||
Condition = ProfileConditionType.Equals,
|
||||
Property = ProfileConditionValue.IsSecondaryAudio,
|
||||
Value = "false",
|
||||
IsRequired = false
|
||||
}
|
||||
}
|
||||
});
|
||||
codecProfiles.Add(new CodecProfile
|
||||
{
|
||||
Type = CodecType.VideoAudio,
|
||||
Codec = "aac,mp3",
|
||||
Conditions = new[]
|
||||
{
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
TranscodingProfiles = new[]
|
||||
{
|
||||
new TranscodingProfile
|
||||
{
|
||||
Container = "mp3",
|
||||
AudioCodec = "mp3",
|
||||
Type = DlnaProfileType.Audio,
|
||||
Context = EncodingContext.Static
|
||||
},
|
||||
|
||||
new TranscodingProfile
|
||||
{
|
||||
Container = "mp4",
|
||||
Type = DlnaProfileType.Video,
|
||||
AudioCodec = "aac",
|
||||
VideoCodec = "h264",
|
||||
Context = EncodingContext.Static
|
||||
},
|
||||
|
||||
new TranscodingProfile
|
||||
{
|
||||
Container = "jpeg",
|
||||
Type = DlnaProfileType.Photo,
|
||||
Context = EncodingContext.Static
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
using MediaBrowser.Model.Sync;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public interface IHasSyncQuality
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the device profile.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="profile">The profile.</param>
|
||||
/// <param name="quality">The quality.</param>
|
||||
/// <returns>DeviceProfile.</returns>
|
||||
SyncJobOptions GetSyncJobOptions(SyncTarget target, string profile, string quality);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quality options.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <returns>IEnumerable<SyncQualityOption>.</returns>
|
||||
IEnumerable<SyncQualityOption> GetQualityOptions(SyncTarget target);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the profile options.
|
||||
/// </summary>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <returns>IEnumerable<SyncQualityOption>.</returns>
|
||||
IEnumerable<SyncProfileOption> GetProfileOptions(SyncTarget target);
|
||||
}
|
||||
}
|
@ -1,500 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Sync;
|
||||
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 Emby.Server.Implementations.IO;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class MediaSync
|
||||
{
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
|
||||
public const string PathSeparatorString = "/";
|
||||
public const char PathSeparatorChar = '/';
|
||||
|
||||
public MediaSync(ILogger logger, ISyncManager syncManager, IServerApplicationHost appHost, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_syncManager = syncManager;
|
||||
_appHost = appHost;
|
||||
_fileSystem = fileSystem;
|
||||
_config = config;
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
}
|
||||
|
||||
public async Task Sync(IServerSyncProvider provider,
|
||||
ISyncDataProvider dataProvider,
|
||||
SyncTarget target,
|
||||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var serverId = _appHost.SystemId;
|
||||
var serverName = _appHost.FriendlyName;
|
||||
|
||||
await SyncData(provider, dataProvider, serverId, target, cancellationToken).ConfigureAwait(false);
|
||||
progress.Report(3);
|
||||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(pct =>
|
||||
{
|
||||
var totalProgress = pct * .97;
|
||||
totalProgress += 1;
|
||||
progress.Report(totalProgress);
|
||||
});
|
||||
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);
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
private async Task SyncData(IServerSyncProvider provider,
|
||||
ISyncDataProvider dataProvider,
|
||||
string serverId,
|
||||
SyncTarget target,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var localItems = await dataProvider.GetLocalItems(target, serverId).ConfigureAwait(false);
|
||||
var remoteFiles = await provider.GetFiles(target, cancellationToken).ConfigureAwait(false);
|
||||
var remoteIds = remoteFiles.Items.Select(i => i.FullName).ToList();
|
||||
|
||||
var jobItemIds = new List<string>();
|
||||
|
||||
foreach (var localItem in localItems)
|
||||
{
|
||||
if (remoteIds.Contains(localItem.FileId, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
jobItemIds.Add(localItem.SyncJobItemId);
|
||||
}
|
||||
}
|
||||
|
||||
var result = await _syncManager.SyncData(new SyncDataRequest
|
||||
{
|
||||
TargetId = target.Id,
|
||||
SyncJobItemIds = jobItemIds
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
foreach (var itemIdToRemove in result.ItemIdsToRemove)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RemoveItem(provider, dataProvider, serverId, itemIdToRemove, target, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error deleting item from device. Id: {0}", ex, itemIdToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetNewMedia(IServerSyncProvider provider,
|
||||
ISyncDataProvider dataProvider,
|
||||
SyncTarget target,
|
||||
string serverId,
|
||||
string serverName,
|
||||
IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var jobItems = await _syncManager.GetReadySyncItems(target.Id).ConfigureAwait(false);
|
||||
|
||||
var numComplete = 0;
|
||||
double startingPercent = 0;
|
||||
double percentPerItem = 1;
|
||||
if (jobItems.Count > 0)
|
||||
{
|
||||
percentPerItem /= jobItems.Count;
|
||||
}
|
||||
|
||||
foreach (var jobItem in jobItems)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var currentPercent = startingPercent;
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(pct =>
|
||||
{
|
||||
var totalProgress = pct * percentPerItem;
|
||||
totalProgress += currentPercent;
|
||||
progress.Report(totalProgress);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await GetItem(provider, dataProvider, target, serverId, serverName, jobItem, innerProgress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error syncing item", ex);
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
startingPercent = numComplete;
|
||||
startingPercent /= jobItems.Count;
|
||||
startingPercent *= 100;
|
||||
progress.Report(startingPercent);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetItem(IServerSyncProvider provider,
|
||||
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 localItem = CreateLocalItem(provider, jobItem, internalSyncJob, target, libraryItem, serverId, serverName, jobItem.OriginalFileName);
|
||||
|
||||
await _syncManager.ReportSyncJobItemTransferBeginning(internalSyncJobItem.Id);
|
||||
|
||||
var transferSuccess = false;
|
||||
Exception transferException = null;
|
||||
|
||||
var options = _config.GetSyncOptions();
|
||||
|
||||
try
|
||||
{
|
||||
var fileTransferProgress = new ActionableProgress<double>();
|
||||
fileTransferProgress.RegisterAction(pct => progress.Report(pct * .92));
|
||||
|
||||
var sendFileResult = await SendFile(provider, internalSyncJobItem.OutputPath, localItem.LocalPath.Split(PathSeparatorChar), target, options, fileTransferProgress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (localItem.Item.MediaSources != null)
|
||||
{
|
||||
var mediaSource = localItem.Item.MediaSources.FirstOrDefault();
|
||||
if (mediaSource != null)
|
||||
{
|
||||
mediaSource.Path = sendFileResult.Path;
|
||||
mediaSource.Protocol = sendFileResult.Protocol;
|
||||
mediaSource.RequiredHttpHeaders = sendFileResult.RequiredHttpHeaders;
|
||||
mediaSource.SupportsTranscoding = false;
|
||||
}
|
||||
}
|
||||
|
||||
localItem.FileId = sendFileResult.Id;
|
||||
|
||||
// Create db record
|
||||
await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false);
|
||||
|
||||
if (localItem.Item.MediaSources != null)
|
||||
{
|
||||
var mediaSource = localItem.Item.MediaSources.FirstOrDefault();
|
||||
if (mediaSource != null)
|
||||
{
|
||||
await SendSubtitles(localItem, mediaSource, provider, dataProvider, target, options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
progress.Report(92);
|
||||
|
||||
transferSuccess = true;
|
||||
|
||||
progress.Report(99);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error transferring sync job file", ex);
|
||||
transferException = ex;
|
||||
}
|
||||
|
||||
if (transferSuccess)
|
||||
{
|
||||
await _syncManager.ReportSyncJobItemTransferred(jobItem.SyncJobItemId).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _syncManager.ReportSyncJobItemTransferFailed(jobItem.SyncJobItemId).ConfigureAwait(false);
|
||||
|
||||
throw transferException;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SendSubtitles(LocalItem localItem, MediaSourceInfo mediaSource, IServerSyncProvider provider, ISyncDataProvider dataProvider, SyncTarget target, SyncOptions options, 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())
|
||||
{
|
||||
try
|
||||
{
|
||||
var remotePath = GetRemoteSubtitlePath(localItem, mediaStream, provider, target);
|
||||
var sendFileResult = await SendFile(provider, mediaStream.Path, remotePath, target, options, new Progress<double>(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// This is the path that will be used when talking to the provider
|
||||
mediaStream.ExternalId = sendFileResult.Id;
|
||||
|
||||
// Keep track of all additional files for cleanup later.
|
||||
localItem.AdditionalFiles.Add(sendFileResult.Id);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (requiresSave)
|
||||
{
|
||||
await dataProvider.AddOrUpdate(target, localItem).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetRemoteSubtitlePath(LocalItem item, MediaStream stream, IServerSyncProvider provider, SyncTarget target)
|
||||
{
|
||||
var filename = GetSubtitleSaveFileName(item, stream.Language, stream.IsForced) + "." + stream.Codec.ToLower();
|
||||
|
||||
var pathParts = item.LocalPath.Split(PathSeparatorChar);
|
||||
var list = pathParts.Take(pathParts.Length - 1).ToList();
|
||||
list.Add(filename);
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
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 syncJobItemId,
|
||||
SyncTarget target,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var localItems = await dataProvider.GetItemsBySyncJobItemId(target, serverId, syncJobItemId);
|
||||
|
||||
foreach (var localItem in localItems)
|
||||
{
|
||||
var files = localItem.AdditionalFiles.ToList();
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
_logger.Debug("Removing {0} from {1}.", file, target.Name);
|
||||
await provider.DeleteFile(file, target, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
_logger.Debug("Removing {0} from {1}.", localItem.FileId, target.Name);
|
||||
await provider.DeleteFile(localItem.FileId, target, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await dataProvider.Delete(target, localItem.Id).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SyncedFileInfo> SendFile(IServerSyncProvider provider, string inputPath, string[] pathParts, SyncTarget target, SyncOptions options, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Debug("Sending {0} to {1}. Remote path: {2}", inputPath, provider.Name, string.Join("/", pathParts));
|
||||
var supportsDirectCopy = provider as ISupportsDirectCopy;
|
||||
if (supportsDirectCopy != null)
|
||||
{
|
||||
return await supportsDirectCopy.SendFile(inputPath, pathParts, target, progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using (var fileStream = _fileSystem.GetFileStream(inputPath, FileOpenMode.Open, FileAccessMode.Read, FileShareMode.Read, true))
|
||||
{
|
||||
Stream stream = fileStream;
|
||||
|
||||
if (options.UploadSpeedLimitBytes > 0 && provider is IRemoteSyncProvider)
|
||||
{
|
||||
stream = new ThrottledStream(stream, options.UploadSpeedLimitBytes);
|
||||
}
|
||||
|
||||
return await provider.SendFile(stream, pathParts, target, progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetLocalId(string jobItemId, string itemId)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(jobItemId + itemId);
|
||||
bytes = CreateMd5(bytes);
|
||||
return BitConverter.ToString(bytes, 0, bytes.Length).Replace("-", string.Empty);
|
||||
}
|
||||
|
||||
private byte[] CreateMd5(byte[] value)
|
||||
{
|
||||
return _cryptographyProvider.ComputeMD5(value);
|
||||
}
|
||||
|
||||
public LocalItem CreateLocalItem(IServerSyncProvider provider, SyncedItem syncedItem, SyncJob job, SyncTarget target, BaseItemDto libraryItem, string serverId, string serverName, string originalFileName)
|
||||
{
|
||||
var path = GetDirectoryPath(provider, job, syncedItem, libraryItem, serverName);
|
||||
path.Add(GetLocalFileName(provider, libraryItem, originalFileName));
|
||||
|
||||
var localPath = string.Join(PathSeparatorString, path.ToArray());
|
||||
|
||||
foreach (var mediaSource in libraryItem.MediaSources)
|
||||
{
|
||||
mediaSource.Path = localPath;
|
||||
mediaSource.Protocol = MediaProtocol.File;
|
||||
}
|
||||
|
||||
return new LocalItem
|
||||
{
|
||||
Item = libraryItem,
|
||||
ItemId = libraryItem.Id,
|
||||
ServerId = serverId,
|
||||
LocalPath = localPath,
|
||||
Id = GetLocalId(syncedItem.SyncJobItemId, libraryItem.Id),
|
||||
SyncJobItemId = syncedItem.SyncJobItemId
|
||||
};
|
||||
}
|
||||
|
||||
private List<string> GetDirectoryPath(IServerSyncProvider provider, SyncJob job, SyncedItem syncedItem, BaseItemDto item, string serverName)
|
||||
{
|
||||
var parts = new List<string>
|
||||
{
|
||||
serverName
|
||||
};
|
||||
|
||||
var profileOption = _syncManager.GetProfileOptions(job.TargetId)
|
||||
.FirstOrDefault(i => string.Equals(i.Id, job.Profile, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
string name;
|
||||
|
||||
if (profileOption != null && !string.IsNullOrWhiteSpace(profileOption.Name))
|
||||
{
|
||||
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"))
|
||||
{
|
||||
parts.Add("TV");
|
||||
if (!string.IsNullOrWhiteSpace(item.SeriesName))
|
||||
{
|
||||
parts.Add(item.SeriesName);
|
||||
}
|
||||
}
|
||||
else if (item.IsVideo)
|
||||
{
|
||||
parts.Add("Videos");
|
||||
parts.Add(item.Name);
|
||||
}
|
||||
else if (item.IsAudio)
|
||||
{
|
||||
parts.Add("Music");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.AlbumArtist))
|
||||
{
|
||||
parts.Add(item.AlbumArtist);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Album))
|
||||
{
|
||||
parts.Add(item.Album);
|
||||
}
|
||||
}
|
||||
else if (string.Equals(item.MediaType, MediaType.Photo, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parts.Add("Photos");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(item.Album))
|
||||
{
|
||||
parts.Add(item.Album);
|
||||
}
|
||||
}
|
||||
|
||||
return parts.Select(i => GetValidFilename(provider, i)).ToList();
|
||||
}
|
||||
|
||||
private string GetLocalFileName(IServerSyncProvider provider, BaseItemDto item, string originalFileName)
|
||||
{
|
||||
var filename = originalFileName;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
filename = item.Name;
|
||||
}
|
||||
|
||||
return GetValidFilename(provider, filename);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Sync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class MultiProviderSync
|
||||
{
|
||||
private readonly SyncManager _syncManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
|
||||
public MultiProviderSync(SyncManager syncManager, IServerApplicationHost appHost, ILogger logger, IFileSystem fileSystem, IConfigurationManager config, ICryptoProvider cryptographyProvider)
|
||||
{
|
||||
_syncManager = syncManager;
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_config = config;
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
}
|
||||
|
||||
public async Task Sync(IEnumerable<IServerSyncProvider> providers, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var targets = providers
|
||||
.SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple<IServerSyncProvider, SyncTarget>(i, t)))
|
||||
.ToList();
|
||||
|
||||
var numComplete = 0;
|
||||
double startingPercent = 0;
|
||||
double percentPerItem = 1;
|
||||
if (targets.Count > 0)
|
||||
{
|
||||
percentPerItem /= targets.Count;
|
||||
}
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var currentPercent = startingPercent;
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(pct =>
|
||||
{
|
||||
var totalProgress = pct * percentPerItem;
|
||||
totalProgress += currentPercent;
|
||||
progress.Report(totalProgress);
|
||||
});
|
||||
|
||||
var dataProvider = _syncManager.GetDataProvider(target.Item1, target.Item2);
|
||||
|
||||
await new MediaSync(_logger, _syncManager, _appHost, _fileSystem, _config, _cryptographyProvider)
|
||||
.Sync(target.Item1, dataProvider, target.Item2, innerProgress, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
numComplete++;
|
||||
startingPercent = numComplete;
|
||||
startingPercent /= targets.Count;
|
||||
startingPercent *= 100;
|
||||
progress.Report(startingPercent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
class ServerSyncScheduledTask : IScheduledTask, IConfigurableScheduledTask
|
||||
{
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly ICryptoProvider _cryptographyProvider;
|
||||
|
||||
public ServerSyncScheduledTask(ISyncManager syncManager, ILogger logger, IFileSystem fileSystem, IServerApplicationHost appHost, IConfigurationManager config, ICryptoProvider cryptographyProvider)
|
||||
{
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_fileSystem = fileSystem;
|
||||
_appHost = appHost;
|
||||
_config = config;
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Cloud & Folder Sync"; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return "Sync media to the cloud"; }
|
||||
}
|
||||
|
||||
public string Category
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Sync";
|
||||
}
|
||||
}
|
||||
|
||||
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
return new MultiProviderSync((SyncManager)_syncManager, _appHost, _logger, _fileSystem, _config, _cryptographyProvider)
|
||||
.Sync(ServerSyncProviders, progress, cancellationToken);
|
||||
}
|
||||
|
||||
public IEnumerable<IServerSyncProvider> ServerSyncProviders
|
||||
{
|
||||
get { return ((SyncManager)_syncManager).ServerSyncProviders; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// </summary>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
|
||||
// Every so often
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(3).Ticks}
|
||||
};
|
||||
}
|
||||
public bool IsHidden
|
||||
{
|
||||
get { return !IsEnabled; }
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get { return ServerSyncProviders.Any(); }
|
||||
}
|
||||
|
||||
public bool IsLogged
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get { return "ServerSync"; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Sync;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new List<ConfigurationStore>
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
ConfigurationType = typeof(SyncOptions),
|
||||
Key = "sync"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class SyncExtensions
|
||||
{
|
||||
public static SyncOptions GetSyncOptions(this IConfigurationManager config)
|
||||
{
|
||||
return config.GetConfiguration<SyncOptions>("sync");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncConvertScheduledTask : IScheduledTask
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ISyncRepository _syncRepo;
|
||||
private readonly ISyncManager _syncManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ITVSeriesManager _tvSeriesManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public SyncConvertScheduledTask(ILibraryManager libraryManager, ISyncRepository syncRepo, ISyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder, IConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_syncRepo = syncRepo;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_tvSeriesManager = tvSeriesManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_subtitleEncoder = subtitleEncoder;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Convert media"; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return "Runs scheduled sync jobs"; }
|
||||
}
|
||||
|
||||
public string Category
|
||||
{
|
||||
get
|
||||
{
|
||||
return "Sync";
|
||||
}
|
||||
}
|
||||
|
||||
public Task Execute(CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
return new SyncJobProcessor(_libraryManager, _syncRepo, (SyncManager)_syncManager, _logger, _userManager, _tvSeriesManager, _mediaEncoder, _subtitleEncoder, _config, _fileSystem, _mediaSourceManager)
|
||||
.Sync(progress, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the triggers that define when the task will run
|
||||
/// </summary>
|
||||
/// <returns>IEnumerable{BaseTaskTrigger}.</returns>
|
||||
public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
|
||||
{
|
||||
return new[] {
|
||||
|
||||
// Every so often
|
||||
new TaskTriggerInfo { Type = TaskTriggerInfo.TriggerInterval, IntervalTicks = TimeSpan.FromHours(3).Ticks}
|
||||
};
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get { return "SyncPrepare"; }
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncHelper
|
||||
{
|
||||
public static long? AdjustBitrate(long? profileBitrate, string quality)
|
||||
{
|
||||
if (profileBitrate.HasValue)
|
||||
{
|
||||
if (string.Equals(quality, "medium", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profileBitrate = Math.Min(profileBitrate.Value, 4000000);
|
||||
}
|
||||
else if (string.Equals(quality, "low", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
profileBitrate = Math.Min(profileBitrate.Value, 1500000);
|
||||
}
|
||||
}
|
||||
|
||||
return profileBitrate;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using MediaBrowser.Model.Dlna;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncJobOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the conversion options.
|
||||
/// </summary>
|
||||
/// <value>The conversion options.</value>
|
||||
public DeviceProfile DeviceProfile { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether this instance is converting.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if this instance is converting; otherwise, <c>false</c>.</value>
|
||||
public bool IsConverting { get; set; }
|
||||
}
|
||||
}
|
@ -1,998 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.IO;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Audio;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.MediaEncoding;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Controller.TV;
|
||||
using MediaBrowser.Model.Dlna;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Session;
|
||||
using MediaBrowser.Model.Sync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.IO;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncJobProcessor
|
||||
{
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly ISyncRepository _syncRepo;
|
||||
private readonly SyncManager _syncManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ITVSeriesManager _tvSeriesManager;
|
||||
private readonly IMediaEncoder _mediaEncoder;
|
||||
private readonly ISubtitleEncoder _subtitleEncoder;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public SyncJobProcessor(ILibraryManager libraryManager, ISyncRepository syncRepo, SyncManager syncManager, ILogger logger, IUserManager userManager, ITVSeriesManager tvSeriesManager, IMediaEncoder mediaEncoder, ISubtitleEncoder subtitleEncoder, IConfigurationManager config, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_libraryManager = libraryManager;
|
||||
_syncRepo = syncRepo;
|
||||
_syncManager = syncManager;
|
||||
_logger = logger;
|
||||
_userManager = userManager;
|
||||
_tvSeriesManager = tvSeriesManager;
|
||||
_mediaEncoder = mediaEncoder;
|
||||
_subtitleEncoder = subtitleEncoder;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
public async Task EnsureJobItems(SyncJob job, User user)
|
||||
{
|
||||
var items = (await GetItemsForSync(job.Category, job.ParentId, job.RequestedItemIds, user, job.UnwatchedOnly).ConfigureAwait(false))
|
||||
.ToList();
|
||||
|
||||
var jobItems = _syncManager.GetJobItems(new SyncJobItemQuery
|
||||
{
|
||||
JobId = job.Id,
|
||||
AddMetadata = false
|
||||
|
||||
}).Items.ToList();
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
// Respect ItemLimit, if set
|
||||
if (job.ItemLimit.HasValue)
|
||||
{
|
||||
if (jobItems.Count(j => j.Status != SyncJobItemStatus.RemovedFromDevice && j.Status != SyncJobItemStatus.Failed) >= job.ItemLimit.Value)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var itemId = item.Id.ToString("N");
|
||||
|
||||
var jobItem = jobItems.FirstOrDefault(i => string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (jobItem != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var index = jobItems.Count == 0 ?
|
||||
0 :
|
||||
jobItems.Select(i => i.JobItemIndex).Max() + 1;
|
||||
|
||||
jobItem = new SyncJobItem
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
ItemId = itemId,
|
||||
ItemName = GetSyncJobItemName(item),
|
||||
JobId = job.Id,
|
||||
TargetId = job.TargetId,
|
||||
DateCreated = DateTime.UtcNow,
|
||||
JobItemIndex = index
|
||||
};
|
||||
|
||||
await _syncRepo.Create(jobItem).ConfigureAwait(false);
|
||||
_syncManager.OnSyncJobItemCreated(jobItem);
|
||||
|
||||
jobItems.Add(jobItem);
|
||||
}
|
||||
|
||||
jobItems = jobItems
|
||||
.OrderBy(i => i.DateCreated)
|
||||
.ToList();
|
||||
|
||||
await UpdateJobStatus(job, jobItems).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string GetSyncJobItemName(BaseItem item)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var job = _syncRepo.GetJob(id);
|
||||
|
||||
if (job == null)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
var result = _syncManager.GetJobItems(new SyncJobItemQuery
|
||||
{
|
||||
JobId = job.Id,
|
||||
AddMetadata = false
|
||||
});
|
||||
|
||||
return UpdateJobStatus(job, result.Items.ToList());
|
||||
}
|
||||
|
||||
private async Task UpdateJobStatus(SyncJob job, List<SyncJobItem> jobItems)
|
||||
{
|
||||
job.ItemCount = jobItems.Count;
|
||||
|
||||
double pct = 0;
|
||||
|
||||
foreach (var item in jobItems)
|
||||
{
|
||||
if (item.Status == SyncJobItemStatus.Failed || item.Status == SyncJobItemStatus.Synced || item.Status == SyncJobItemStatus.RemovedFromDevice || item.Status == SyncJobItemStatus.Cancelled)
|
||||
{
|
||||
pct += 100;
|
||||
}
|
||||
else
|
||||
{
|
||||
pct += item.Progress ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (job.ItemCount > 0)
|
||||
{
|
||||
pct /= job.ItemCount;
|
||||
job.Progress = pct;
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Progress = null;
|
||||
}
|
||||
|
||||
if (jobItems.Any(i => i.Status == SyncJobItemStatus.Transferring))
|
||||
{
|
||||
job.Status = SyncJobStatus.Transferring;
|
||||
}
|
||||
else if (jobItems.Any(i => i.Status == SyncJobItemStatus.Converting))
|
||||
{
|
||||
job.Status = SyncJobStatus.Converting;
|
||||
}
|
||||
else if (jobItems.All(i => i.Status == SyncJobItemStatus.Failed))
|
||||
{
|
||||
job.Status = SyncJobStatus.Failed;
|
||||
}
|
||||
else if (jobItems.All(i => i.Status == SyncJobItemStatus.Cancelled))
|
||||
{
|
||||
job.Status = SyncJobStatus.Cancelled;
|
||||
}
|
||||
else if (jobItems.All(i => i.Status == SyncJobItemStatus.ReadyToTransfer))
|
||||
{
|
||||
job.Status = SyncJobStatus.ReadyToTransfer;
|
||||
}
|
||||
else if (jobItems.All(i => i.Status == SyncJobItemStatus.Cancelled || i.Status == SyncJobItemStatus.Failed || i.Status == SyncJobItemStatus.Synced || i.Status == SyncJobItemStatus.RemovedFromDevice))
|
||||
{
|
||||
if (jobItems.Any(i => i.Status == SyncJobItemStatus.Failed))
|
||||
{
|
||||
job.Status = SyncJobStatus.CompletedWithError;
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Status = SyncJobStatus.Completed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Status = SyncJobStatus.Queued;
|
||||
}
|
||||
|
||||
await _syncRepo.Update(job).ConfigureAwait(false);
|
||||
|
||||
_syncManager.OnSyncJobUpdated(job);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<BaseItem>> GetItemsForSync(SyncCategory? category, string parentId, IEnumerable<string> itemIds, User user, bool unwatchedOnly)
|
||||
{
|
||||
var list = new List<BaseItem>();
|
||||
|
||||
if (category.HasValue)
|
||||
{
|
||||
list = (await GetItemsForSync(category.Value, parentId, user).ConfigureAwait(false)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var itemId in itemIds)
|
||||
{
|
||||
var subList = await GetItemsForSync(itemId, user).ConfigureAwait(false);
|
||||
list.AddRange(subList);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<BaseItem> items = list;
|
||||
items = items.Where(_syncManager.SupportsSync);
|
||||
|
||||
if (unwatchedOnly)
|
||||
{
|
||||
// Avoid implicitly captured closure
|
||||
var currentUser = user;
|
||||
|
||||
items = items.Where(i =>
|
||||
{
|
||||
var video = i as Video;
|
||||
|
||||
if (video != null)
|
||||
{
|
||||
return !video.IsPlayed(currentUser);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return items.DistinctBy(i => i.Id);
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<BaseItem>> GetItemsForSync(SyncCategory category, string parentId, User user)
|
||||
{
|
||||
var parent = string.IsNullOrWhiteSpace(parentId)
|
||||
? user.RootFolder
|
||||
: (Folder)_libraryManager.GetItemById(parentId);
|
||||
|
||||
InternalItemsQuery query;
|
||||
|
||||
switch (category)
|
||||
{
|
||||
case SyncCategory.Latest:
|
||||
query = new InternalItemsQuery
|
||||
{
|
||||
IsFolder = false,
|
||||
SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName },
|
||||
SortOrder = SortOrder.Descending,
|
||||
Recursive = true
|
||||
};
|
||||
break;
|
||||
case SyncCategory.Resume:
|
||||
query = new InternalItemsQuery
|
||||
{
|
||||
IsFolder = false,
|
||||
SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName },
|
||||
SortOrder = SortOrder.Descending,
|
||||
Recursive = true,
|
||||
IsResumable = true,
|
||||
MediaTypes = new[] { MediaType.Video }
|
||||
};
|
||||
break;
|
||||
|
||||
case SyncCategory.NextUp:
|
||||
return _tvSeriesManager.GetNextUp(new NextUpQuery
|
||||
{
|
||||
ParentId = parentId,
|
||||
UserId = user.Id.ToString("N")
|
||||
}).Items;
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Unrecognized category: " + category);
|
||||
}
|
||||
|
||||
if (parent == null)
|
||||
{
|
||||
return new List<BaseItem>();
|
||||
}
|
||||
|
||||
query.User = user;
|
||||
|
||||
var result = await parent.GetItems(query).ConfigureAwait(false);
|
||||
return result.Items;
|
||||
}
|
||||
|
||||
private async Task<List<BaseItem>> GetItemsForSync(string id, User user)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
return new List<BaseItem>();
|
||||
}
|
||||
|
||||
var itemByName = item as IItemByName;
|
||||
if (itemByName != null)
|
||||
{
|
||||
return itemByName.GetTaggedItems(new InternalItemsQuery(user)
|
||||
{
|
||||
IsFolder = false,
|
||||
Recursive = true
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
if (item.IsFolder)
|
||||
{
|
||||
var folder = (Folder)item;
|
||||
var itemsResult = await folder.GetItems(new InternalItemsQuery(user)
|
||||
{
|
||||
Recursive = true,
|
||||
IsFolder = false
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
var items = itemsResult.Items;
|
||||
|
||||
if (!folder.IsPreSorted)
|
||||
{
|
||||
items = _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
return items.ToList();
|
||||
}
|
||||
|
||||
return new List<BaseItem> { item };
|
||||
}
|
||||
|
||||
private async Task EnsureSyncJobItems(string targetId, CancellationToken cancellationToken)
|
||||
{
|
||||
var jobResult = _syncRepo.GetJobs(new SyncJobQuery
|
||||
{
|
||||
SyncNewContent = true,
|
||||
TargetId = targetId
|
||||
});
|
||||
|
||||
foreach (var job in jobResult.Items)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (job.SyncNewContent)
|
||||
{
|
||||
var user = _userManager.GetUserById(job.UserId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
await _syncManager.CancelJob(job.Id).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
await EnsureJobItems(job, user).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Sync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
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
|
||||
{
|
||||
Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting },
|
||||
AddMetadata = false
|
||||
});
|
||||
|
||||
await SyncJobItems(result.Items, true, progress, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
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
|
||||
// Clean files in sync temp folder that are not linked to any sync jobs
|
||||
}
|
||||
|
||||
public async Task SyncJobItems(string targetId, bool enableConversion, IProgress<double> progress,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await EnsureSyncJobItems(targetId, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// If it already has a converting status then is must have been aborted during conversion
|
||||
var result = _syncManager.GetJobItems(new SyncJobItemQuery
|
||||
{
|
||||
Statuses = new[] { SyncJobItemStatus.Queued, SyncJobItemStatus.Converting },
|
||||
TargetId = targetId,
|
||||
AddMetadata = false
|
||||
});
|
||||
|
||||
await SyncJobItems(result.Items, enableConversion, progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SyncJobItems(SyncJobItem[] items, bool enableConversion, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (items.Length > 0)
|
||||
{
|
||||
if (!SyncRegistrationInfo.Instance.IsRegistered)
|
||||
{
|
||||
_logger.Debug("Cancelling sync job processing. Please obtain a supporter membership.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var numComplete = 0;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
double percentPerItem = 1;
|
||||
percentPerItem /= items.Length;
|
||||
var startingPercent = numComplete * percentPerItem * 100;
|
||||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(p => progress.Report(startingPercent + percentPerItem * p));
|
||||
|
||||
// Pull it fresh from the db just to make sure it wasn't deleted or cancelled while another item was converting
|
||||
var jobItem = enableConversion ? _syncRepo.GetJobItem(item.Id) : item;
|
||||
|
||||
if (jobItem != null)
|
||||
{
|
||||
if (jobItem.Status != SyncJobItemStatus.Cancelled)
|
||||
{
|
||||
await ProcessJobItem(jobItem, enableConversion, innerProgress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= items.Length;
|
||||
progress.Report(100 * percent);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessJobItem(SyncJobItem jobItem, bool enableConversion, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (jobItem == null)
|
||||
{
|
||||
throw new ArgumentNullException("jobItem");
|
||||
}
|
||||
|
||||
var item = _libraryManager.GetItemById(jobItem.ItemId);
|
||||
if (item == null)
|
||||
{
|
||||
jobItem.Status = SyncJobItemStatus.Failed;
|
||||
_logger.Error("Unable to locate library item for JobItem {0}, ItemId {1}", jobItem.Id, jobItem.ItemId);
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
jobItem.Progress = 0;
|
||||
|
||||
var job = _syncManager.GetJob(jobItem.JobId);
|
||||
if (job == null)
|
||||
{
|
||||
_logger.Error("Job not found. Cannot complete the sync job.");
|
||||
await _syncManager.CancelJobItem(jobItem.Id).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
var user = _userManager.GetUserById(job.UserId);
|
||||
if (user == null)
|
||||
{
|
||||
jobItem.Status = SyncJobItemStatus.Failed;
|
||||
_logger.Error("User not found. Cannot complete the sync job.");
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// See if there's already another active job item for the same target
|
||||
var existingJobItems = _syncManager.GetJobItems(new SyncJobItemQuery
|
||||
{
|
||||
AddMetadata = false,
|
||||
ItemId = jobItem.ItemId,
|
||||
TargetId = jobItem.TargetId,
|
||||
Statuses = new[] { SyncJobItemStatus.Converting, SyncJobItemStatus.Queued, SyncJobItemStatus.ReadyToTransfer, SyncJobItemStatus.Synced, SyncJobItemStatus.Transferring }
|
||||
});
|
||||
|
||||
var duplicateJobItems = existingJobItems.Items
|
||||
.Where(i => !string.Equals(i.Id, jobItem.Id, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
if (duplicateJobItems.Count > 0)
|
||||
{
|
||||
var syncProvider = _syncManager.GetSyncProvider(jobItem) as IHasDuplicateCheck;
|
||||
|
||||
if (!duplicateJobItems.Any(i => AllowDuplicateJobItem(syncProvider, i, jobItem)))
|
||||
{
|
||||
_logger.Debug("Cancelling sync job item because there is already another active job for the same target.");
|
||||
jobItem.Status = SyncJobItemStatus.Cancelled;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var syncOptions = _config.GetSyncOptions();
|
||||
|
||||
var video = item as Video;
|
||||
if (video != null)
|
||||
{
|
||||
await Sync(jobItem, video, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
else if (item is Audio)
|
||||
{
|
||||
await Sync(jobItem, (Audio)item, user, enableConversion, syncOptions, progress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
else if (item is Photo)
|
||||
{
|
||||
await Sync(jobItem, (Photo)item, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
await SyncGeneric(jobItem, item, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllowDuplicateJobItem(IHasDuplicateCheck provider, SyncJobItem original, SyncJobItem duplicate)
|
||||
{
|
||||
if (provider != null)
|
||||
{
|
||||
return provider.AllowDuplicateJobItem(original, duplicate);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task Sync(SyncJobItem jobItem, Video item, User user, bool enableConversion, SyncOptions syncOptions, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = _syncManager.GetJob(jobItem.JobId);
|
||||
var jobOptions = _syncManager.GetVideoOptions(jobItem, job);
|
||||
var conversionOptions = new VideoOptions
|
||||
{
|
||||
Profile = jobOptions.DeviceProfile
|
||||
};
|
||||
|
||||
conversionOptions.DeviceId = jobItem.TargetId;
|
||||
conversionOptions.Context = EncodingContext.Static;
|
||||
conversionOptions.ItemId = item.Id.ToString("N");
|
||||
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
|
||||
|
||||
var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildVideoItem(conversionOptions);
|
||||
var mediaSource = streamInfo.MediaSource;
|
||||
|
||||
// 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, 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;
|
||||
var requiresConversion = requiresVideoTranscoding || externalSubs.Any(i => RequiresExtraction(i, mediaSource));
|
||||
|
||||
if (requiresConversion && !enableConversion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
jobItem.MediaSourceId = streamInfo.MediaSourceId;
|
||||
jobItem.TemporaryPath = GetTemporaryPath(jobItem);
|
||||
|
||||
if (requiresConversion)
|
||||
{
|
||||
jobItem.Status = SyncJobItemStatus.Converting;
|
||||
}
|
||||
|
||||
if (requiresVideoTranscoding)
|
||||
{
|
||||
// Save the job item now since conversion could take a while
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var lastJobUpdate = DateTime.MinValue;
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(async pct =>
|
||||
{
|
||||
progress.Report(pct);
|
||||
|
||||
if ((DateTime.UtcNow - lastJobUpdate).TotalSeconds >= DatabaseProgressUpdateIntervalSeconds)
|
||||
{
|
||||
jobItem.Progress = pct / 2;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
|
||||
jobItem.OutputPath = await _mediaEncoder.EncodeVideo(new EncodingJobOptions(streamInfo, conversionOptions.Profile)
|
||||
{
|
||||
OutputDirectory = jobItem.TemporaryPath,
|
||||
CpuCoreLimit = syncOptions.TranscodingCpuCoreLimit,
|
||||
ReadInputAtNativeFramerate = !syncOptions.EnableFullSpeedTranscoding
|
||||
|
||||
}, innerProgress, cancellationToken);
|
||||
|
||||
jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
|
||||
_syncManager.OnConversionComplete(jobItem);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
jobItem.Status = SyncJobItemStatus.Queued;
|
||||
jobItem.Progress = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jobItem.Status = SyncJobItemStatus.Failed;
|
||||
_logger.ErrorException("Error during sync transcoding", ex);
|
||||
}
|
||||
|
||||
if (jobItem.Status == SyncJobItemStatus.Failed || jobItem.Status == SyncJobItemStatus.Queued)
|
||||
{
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
jobItem.MediaSource = await GetEncodedMediaSource(jobItem.OutputPath, user, true).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mediaSource.Protocol == MediaProtocol.File)
|
||||
{
|
||||
jobItem.OutputPath = mediaSource.Path;
|
||||
}
|
||||
else if (mediaSource.Protocol == MediaProtocol.Http)
|
||||
{
|
||||
jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
|
||||
}
|
||||
|
||||
jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
|
||||
jobItem.MediaSource = mediaSource;
|
||||
}
|
||||
|
||||
jobItem.MediaSource.SupportsTranscoding = false;
|
||||
|
||||
if (externalSubs.Count > 0)
|
||||
{
|
||||
// Save the job item now since conversion could take a while
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
|
||||
await ConvertSubtitles(jobItem, externalSubs, streamInfo, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
jobItem.Progress = 50;
|
||||
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private bool RequiresExtraction(SubtitleStreamInfo stream, MediaSourceInfo mediaSource)
|
||||
{
|
||||
var originalStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Subtitle && i.Index == stream.Index);
|
||||
|
||||
return originalStream != null && !originalStream.IsExternal;
|
||||
}
|
||||
|
||||
private async Task ConvertSubtitles(SyncJobItem jobItem,
|
||||
IEnumerable<SubtitleStreamInfo> subtitles,
|
||||
StreamInfo streamInfo,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var files = new List<ItemFileInfo>();
|
||||
|
||||
var mediaStreams = jobItem.MediaSource.MediaStreams
|
||||
.Where(i => i.Type != MediaStreamType.Subtitle || !i.IsExternal)
|
||||
.ToList();
|
||||
|
||||
var startingIndex = mediaStreams.Count == 0 ?
|
||||
0 :
|
||||
mediaStreams.Select(i => i.Index).Max() + 1;
|
||||
|
||||
foreach (var subtitle in subtitles)
|
||||
{
|
||||
var fileInfo = await ConvertSubtitles(jobItem.TemporaryPath, streamInfo, subtitle, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Reset this to a value that will be based on the output media
|
||||
fileInfo.Index = startingIndex;
|
||||
files.Add(fileInfo);
|
||||
|
||||
mediaStreams.Add(new MediaStream
|
||||
{
|
||||
Index = startingIndex,
|
||||
Codec = subtitle.Format,
|
||||
IsForced = subtitle.IsForced,
|
||||
IsExternal = true,
|
||||
Language = subtitle.Language,
|
||||
Path = fileInfo.Path,
|
||||
SupportsExternalStream = true,
|
||||
Type = MediaStreamType.Subtitle
|
||||
});
|
||||
|
||||
startingIndex++;
|
||||
}
|
||||
|
||||
jobItem.AdditionalFiles.AddRange(files);
|
||||
|
||||
jobItem.MediaSource.MediaStreams = mediaStreams;
|
||||
}
|
||||
|
||||
private async Task<ItemFileInfo> ConvertSubtitles(string temporaryPath, StreamInfo streamInfo, SubtitleStreamInfo subtitleStreamInfo, CancellationToken cancellationToken)
|
||||
{
|
||||
var subtitleStreamIndex = subtitleStreamInfo.Index;
|
||||
|
||||
var filename = Guid.NewGuid() + "." + subtitleStreamInfo.Format.ToLower();
|
||||
|
||||
var path = Path.Combine(temporaryPath, filename);
|
||||
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
using (var stream = await _subtitleEncoder.GetSubtitles(streamInfo.ItemId, streamInfo.MediaSourceId, subtitleStreamIndex, subtitleStreamInfo.Format, 0, null, false, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true))
|
||||
{
|
||||
await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new ItemFileInfo
|
||||
{
|
||||
Name = Path.GetFileName(path),
|
||||
Path = path,
|
||||
Type = ItemFileType.Subtitles,
|
||||
Index = subtitleStreamIndex
|
||||
};
|
||||
}
|
||||
|
||||
private const int DatabaseProgressUpdateIntervalSeconds = 2;
|
||||
|
||||
private async Task Sync(SyncJobItem jobItem, Audio item, User user, bool enableConversion, SyncOptions syncOptions, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = _syncManager.GetJob(jobItem.JobId);
|
||||
var jobOptions = _syncManager.GetAudioOptions(jobItem, job);
|
||||
var conversionOptions = new AudioOptions
|
||||
{
|
||||
Profile = jobOptions.DeviceProfile
|
||||
};
|
||||
|
||||
conversionOptions.DeviceId = jobItem.TargetId;
|
||||
conversionOptions.Context = EncodingContext.Static;
|
||||
conversionOptions.ItemId = item.Id.ToString("N");
|
||||
conversionOptions.MediaSources = _mediaSourceManager.GetStaticMediaSources(item, false, user).ToList();
|
||||
|
||||
var streamInfo = new StreamBuilder(_mediaEncoder, _logger).BuildAudioItem(conversionOptions);
|
||||
var mediaSource = streamInfo.MediaSource;
|
||||
|
||||
jobItem.MediaSourceId = streamInfo.MediaSourceId;
|
||||
jobItem.TemporaryPath = GetTemporaryPath(jobItem);
|
||||
|
||||
if (streamInfo.PlayMethod == PlayMethod.Transcode && jobOptions.IsConverting)
|
||||
{
|
||||
if (!enableConversion)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
jobItem.Status = SyncJobItemStatus.Converting;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var lastJobUpdate = DateTime.MinValue;
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(async pct =>
|
||||
{
|
||||
progress.Report(pct);
|
||||
|
||||
if ((DateTime.UtcNow - lastJobUpdate).TotalSeconds >= DatabaseProgressUpdateIntervalSeconds)
|
||||
{
|
||||
jobItem.Progress = pct / 2;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
await UpdateJobStatus(jobItem.JobId).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
|
||||
jobItem.OutputPath = await _mediaEncoder.EncodeAudio(new EncodingJobOptions(streamInfo, conversionOptions.Profile)
|
||||
{
|
||||
OutputDirectory = jobItem.TemporaryPath,
|
||||
CpuCoreLimit = syncOptions.TranscodingCpuCoreLimit
|
||||
|
||||
}, innerProgress, cancellationToken);
|
||||
|
||||
jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
|
||||
_syncManager.OnConversionComplete(jobItem);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
jobItem.Status = SyncJobItemStatus.Queued;
|
||||
jobItem.Progress = 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
jobItem.Status = SyncJobItemStatus.Failed;
|
||||
_logger.ErrorException("Error during sync transcoding", ex);
|
||||
}
|
||||
|
||||
if (jobItem.Status == SyncJobItemStatus.Failed || jobItem.Status == SyncJobItemStatus.Queued)
|
||||
{
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
jobItem.MediaSource = await GetEncodedMediaSource(jobItem.OutputPath, user, false).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mediaSource.Protocol == MediaProtocol.File)
|
||||
{
|
||||
jobItem.OutputPath = mediaSource.Path;
|
||||
}
|
||||
else if (mediaSource.Protocol == MediaProtocol.Http)
|
||||
{
|
||||
jobItem.OutputPath = await DownloadFile(jobItem, mediaSource, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException(string.Format("Cannot direct stream {0} protocol", mediaSource.Protocol));
|
||||
}
|
||||
|
||||
jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
|
||||
jobItem.MediaSource = mediaSource;
|
||||
}
|
||||
|
||||
jobItem.MediaSource.SupportsTranscoding = false;
|
||||
|
||||
jobItem.Progress = 50;
|
||||
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task Sync(SyncJobItem jobItem, Photo item, CancellationToken cancellationToken)
|
||||
{
|
||||
jobItem.OutputPath = item.Path;
|
||||
|
||||
jobItem.Progress = 50;
|
||||
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
|
||||
jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task SyncGeneric(SyncJobItem jobItem, BaseItem item, CancellationToken cancellationToken)
|
||||
{
|
||||
jobItem.OutputPath = item.Path;
|
||||
|
||||
jobItem.Progress = 50;
|
||||
jobItem.Status = SyncJobItemStatus.ReadyToTransfer;
|
||||
jobItem.ItemDateModifiedTicks = item.DateModified.Ticks;
|
||||
await _syncManager.UpdateSyncJobItemInternal(jobItem).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<string> DownloadFile(SyncJobItem jobItem, MediaSourceInfo mediaSource, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Download
|
||||
return mediaSource.Path;
|
||||
}
|
||||
|
||||
public string GetTemporaryPath(SyncJob job)
|
||||
{
|
||||
return GetTemporaryPath(job.Id);
|
||||
}
|
||||
|
||||
public string GetTemporaryPath(string jobId)
|
||||
{
|
||||
var basePath = _config.GetSyncOptions().TemporaryPath;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(basePath))
|
||||
{
|
||||
basePath = Path.Combine(_config.CommonApplicationPaths.ProgramDataPath, "sync");
|
||||
}
|
||||
|
||||
return Path.Combine(basePath, jobId);
|
||||
}
|
||||
|
||||
public string GetTemporaryPath(SyncJobItem jobItem)
|
||||
{
|
||||
return Path.Combine(GetTemporaryPath(jobItem.JobId), jobItem.Id);
|
||||
}
|
||||
|
||||
private async Task<MediaSourceInfo> GetEncodedMediaSource(string path, User user, bool isVideo)
|
||||
{
|
||||
var item = _libraryManager.ResolvePath(_fileSystem.GetFileSystemInfo(path));
|
||||
|
||||
await item.RefreshMetadata(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var hasMediaSources = item as IHasMediaSources;
|
||||
|
||||
var mediaSources = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false).ToList();
|
||||
|
||||
var preferredAudio = string.IsNullOrEmpty(user.Configuration.AudioLanguagePreference)
|
||||
? new string[] { }
|
||||
: new[] { user.Configuration.AudioLanguagePreference };
|
||||
|
||||
var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
|
||||
? new List<string>() : new List<string> { user.Configuration.SubtitleLanguagePreference };
|
||||
|
||||
foreach (var source in mediaSources)
|
||||
{
|
||||
if (isVideo)
|
||||
{
|
||||
source.DefaultAudioStreamIndex =
|
||||
MediaStreamSelector.GetDefaultAudioStreamIndex(source.MediaStreams, preferredAudio, user.Configuration.PlayDefaultAudioTrack);
|
||||
|
||||
var defaultAudioIndex = source.DefaultAudioStreamIndex;
|
||||
var audioLangage = defaultAudioIndex == null
|
||||
? null
|
||||
: source.MediaStreams.Where(i => i.Type == MediaStreamType.Audio && i.Index == defaultAudioIndex).Select(i => i.Language).FirstOrDefault();
|
||||
|
||||
source.DefaultAudioStreamIndex =
|
||||
MediaStreamSelector.GetDefaultSubtitleStreamIndex(source.MediaStreams, preferredSubs, user.Configuration.SubtitleMode, audioLangage);
|
||||
}
|
||||
else
|
||||
{
|
||||
var audio = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
if (audio != null)
|
||||
{
|
||||
source.DefaultAudioStreamIndex = audio.Index;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return mediaSources.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,60 +0,0 @@
|
||||
using System.Threading;
|
||||
using MediaBrowser.Controller.Plugins;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.Sync;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncNotificationEntryPoint : IServerEntryPoint
|
||||
{
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ISyncManager _syncManager;
|
||||
|
||||
public SyncNotificationEntryPoint(ISyncManager syncManager, ISessionManager sessionManager)
|
||||
{
|
||||
_syncManager = syncManager;
|
||||
_sessionManager = sessionManager;
|
||||
}
|
||||
|
||||
public void Run()
|
||||
{
|
||||
_syncManager.SyncJobItemUpdated += _syncManager_SyncJobItemUpdated;
|
||||
}
|
||||
|
||||
private async void _syncManager_SyncJobItemUpdated(object sender, GenericEventArgs<SyncJobItem> e)
|
||||
{
|
||||
var item = e.Argument;
|
||||
|
||||
if (item.Status == SyncJobItemStatus.ReadyToTransfer)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemReady", item, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (item.Status == SyncJobItemStatus.Cancelled)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _sessionManager.SendMessageToUserDeviceSessions(item.TargetId, "SyncJobItemCancelled", item, CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_syncManager.SyncJobItemUpdated -= _syncManager_SyncJobItemUpdated;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
using MediaBrowser.Common.Security;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncRegistrationInfo : IRequiresRegistration
|
||||
{
|
||||
private readonly ISecurityManager _securityManager;
|
||||
|
||||
public static SyncRegistrationInfo Instance;
|
||||
|
||||
public SyncRegistrationInfo(ISecurityManager securityManager)
|
||||
{
|
||||
_securityManager = securityManager;
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
private bool _registered;
|
||||
public bool IsRegistered
|
||||
{
|
||||
get { return _registered; }
|
||||
}
|
||||
|
||||
public async Task LoadRegistrationInfoAsync()
|
||||
{
|
||||
var info = await _securityManager.GetRegistrationStatus("sync").ConfigureAwait(false);
|
||||
|
||||
_registered = info.IsValid;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,847 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Emby.Server.Implementations.Data;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Sync;
|
||||
using SQLitePCL.pretty;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncRepository : BaseSqliteRepository, ISyncRepository
|
||||
{
|
||||
private readonly CultureInfo _usCulture = new CultureInfo("en-US");
|
||||
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public SyncRepository(ILogger logger, IJsonSerializer json, IServerApplicationPaths appPaths)
|
||||
: base(logger)
|
||||
{
|
||||
_json = json;
|
||||
DbFilePath = Path.Combine(appPaths.DataPath, "sync14.db");
|
||||
}
|
||||
|
||||
private class SyncSummary
|
||||
{
|
||||
public Dictionary<string, int> Items { get; set; }
|
||||
|
||||
public SyncSummary()
|
||||
{
|
||||
Items = new Dictionary<string, int>();
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
RunDefaultInitialization(connection);
|
||||
|
||||
string[] queries = {
|
||||
|
||||
"create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Profile TEXT, Quality TEXT, Bitrate INT, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)",
|
||||
|
||||
"create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT, ItemDateModifiedTicks BIGINT)",
|
||||
|
||||
"drop index if exists idx_SyncJobItems2",
|
||||
"drop index if exists idx_SyncJobItems3",
|
||||
"drop index if exists idx_SyncJobs1",
|
||||
"drop index if exists idx_SyncJobs",
|
||||
"drop index if exists idx_SyncJobItems1",
|
||||
"create index if not exists idx_SyncJobItems4 on SyncJobItems(TargetId,ItemId,Status,Progress,DateCreated)",
|
||||
"create index if not exists idx_SyncJobItems5 on SyncJobItems(TargetId,Status,ItemId,Progress)",
|
||||
|
||||
"create index if not exists idx_SyncJobs2 on SyncJobs(TargetId,Status,ItemIds,Progress)",
|
||||
|
||||
"pragma shrink_memory"
|
||||
};
|
||||
|
||||
connection.RunQueries(queries);
|
||||
|
||||
connection.RunInTransaction(db =>
|
||||
{
|
||||
var existingColumnNames = GetColumnNames(db, "SyncJobs");
|
||||
AddColumn(db, "SyncJobs", "Profile", "TEXT", existingColumnNames);
|
||||
AddColumn(db, "SyncJobs", "Bitrate", "INT", existingColumnNames);
|
||||
|
||||
existingColumnNames = GetColumnNames(db, "SyncJobItems");
|
||||
AddColumn(db, "SyncJobItems", "ItemDateModifiedTicks", "BIGINT", existingColumnNames);
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool EnableTempStoreMemory
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private const string BaseJobSelectText = "select Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs";
|
||||
private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks from SyncJobItems";
|
||||
|
||||
public SyncJob GetJob(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
var guid = new Guid(id);
|
||||
|
||||
if (guid == Guid.Empty)
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
using (WriteLock.Read())
|
||||
{
|
||||
using (var connection = CreateConnection(true))
|
||||
{
|
||||
var commandText = BaseJobSelectText + " where Id=?";
|
||||
var paramList = new List<object>();
|
||||
|
||||
paramList.Add(guid.ToGuidParamValue());
|
||||
|
||||
foreach (var row in connection.Query(commandText, paramList.ToArray()))
|
||||
{
|
||||
return GetJob(row);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SyncJob GetJob(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var info = new SyncJob
|
||||
{
|
||||
Id = reader[0].ReadGuid().ToString("N"),
|
||||
TargetId = reader[1].ToString(),
|
||||
Name = reader[2].ToString()
|
||||
};
|
||||
|
||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Profile = reader[3].ToString();
|
||||
}
|
||||
|
||||
if (reader[4].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Quality = reader[4].ToString();
|
||||
}
|
||||
|
||||
if (reader[5].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Bitrate = reader[5].ToInt();
|
||||
}
|
||||
|
||||
if (reader[6].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Status = (SyncJobStatus)Enum.Parse(typeof(SyncJobStatus), reader[6].ToString(), true);
|
||||
}
|
||||
|
||||
if (reader[7].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Progress = reader[7].ToDouble();
|
||||
}
|
||||
|
||||
if (reader[8].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.UserId = reader[8].ToString();
|
||||
}
|
||||
|
||||
if (reader[9].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.RequestedItemIds = reader[9].ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
}
|
||||
|
||||
if (reader[10].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Category = (SyncCategory)Enum.Parse(typeof(SyncCategory), reader[10].ToString(), true);
|
||||
}
|
||||
|
||||
if (reader[11].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.ParentId = reader[11].ToString();
|
||||
}
|
||||
|
||||
if (reader[12].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.UnwatchedOnly = reader[12].ToBool();
|
||||
}
|
||||
|
||||
if (reader[13].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.ItemLimit = reader[13].ToInt();
|
||||
}
|
||||
|
||||
info.SyncNewContent = reader[14].ToBool();
|
||||
|
||||
info.DateCreated = reader[15].ReadDateTime();
|
||||
info.DateLastModified = reader[16].ReadDateTime();
|
||||
info.ItemCount = reader[17].ToInt();
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public Task Create(SyncJob job)
|
||||
{
|
||||
return InsertOrUpdate(job, true);
|
||||
}
|
||||
|
||||
public Task Update(SyncJob job)
|
||||
{
|
||||
return InsertOrUpdate(job, false);
|
||||
}
|
||||
|
||||
private async Task InsertOrUpdate(SyncJob job, bool insert)
|
||||
{
|
||||
if (job == null)
|
||||
{
|
||||
throw new ArgumentNullException("job");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
using (WriteLock.Write())
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
string commandText;
|
||||
|
||||
if (insert)
|
||||
{
|
||||
commandText = "insert into SyncJobs (Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Profile, @Quality, @Bitrate, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)";
|
||||
}
|
||||
else
|
||||
{
|
||||
commandText = "update SyncJobs set TargetId=@TargetId,Name=@Name,Profile=@Profile,Quality=@Quality,Bitrate=@Bitrate,Status=@Status,Progress=@Progress,UserId=@UserId,ItemIds=@ItemIds,Category=@Category,ParentId=@ParentId,UnwatchedOnly=@UnwatchedOnly,ItemLimit=@ItemLimit,SyncNewContent=@SyncNewContent,DateCreated=@DateCreated,DateLastModified=@DateLastModified,ItemCount=@ItemCount where Id=@Id";
|
||||
}
|
||||
|
||||
connection.RunInTransaction(conn =>
|
||||
{
|
||||
using (var statement = PrepareStatementSafe(connection, commandText))
|
||||
{
|
||||
statement.TryBind("@TargetId", job.TargetId);
|
||||
statement.TryBind("@Name", job.Name);
|
||||
statement.TryBind("@Profile", job.Profile);
|
||||
statement.TryBind("@Quality", job.Quality);
|
||||
statement.TryBind("@Bitrate", job.Bitrate);
|
||||
statement.TryBind("@Status", job.Status.ToString());
|
||||
statement.TryBind("@Progress", job.Progress);
|
||||
statement.TryBind("@UserId", job.UserId);
|
||||
|
||||
statement.TryBind("@ItemIds", string.Join(",", job.RequestedItemIds.ToArray()));
|
||||
|
||||
if (job.Category.HasValue)
|
||||
{
|
||||
statement.TryBind("@Category", job.Category.Value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBindNull("@Category");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(job.ParentId))
|
||||
{
|
||||
statement.TryBind("@ParentId", job.ParentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBindNull("@ParentId");
|
||||
}
|
||||
|
||||
statement.TryBind("@UnwatchedOnly", job.UnwatchedOnly);
|
||||
|
||||
if (job.ItemLimit.HasValue)
|
||||
{
|
||||
statement.TryBind("@ItemLimit", job.ItemLimit);
|
||||
}
|
||||
else
|
||||
{
|
||||
statement.TryBindNull("@ItemLimit");
|
||||
}
|
||||
|
||||
statement.TryBind("@SyncNewContent", job.SyncNewContent);
|
||||
|
||||
statement.TryBind("@DateCreated", job.DateCreated.ToDateTimeParamValue());
|
||||
statement.TryBind("@DateLastModified", job.DateLastModified.ToDateTimeParamValue());
|
||||
|
||||
statement.TryBind("@ItemCount", job.ItemCount);
|
||||
statement.TryBind("@Id", job.Id.ToGuidParamValue());
|
||||
|
||||
statement.MoveNext();
|
||||
}
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteJob(string id)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
using (WriteLock.Write())
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
connection.RunInTransaction(conn =>
|
||||
{
|
||||
conn.Execute("delete from SyncJobs where Id=?", id.ToGuidParamValue());
|
||||
conn.Execute("delete from SyncJobItems where JobId=?", id);
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<SyncJob> GetJobs(SyncJobQuery query)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException("query");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
using (WriteLock.Read())
|
||||
{
|
||||
using (var connection = CreateConnection(true))
|
||||
{
|
||||
var commandText = BaseJobSelectText;
|
||||
var paramList = new List<object>();
|
||||
|
||||
var whereClauses = new List<string>();
|
||||
|
||||
if (query.Statuses.Length > 0)
|
||||
{
|
||||
var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
|
||||
|
||||
whereClauses.Add(string.Format("Status in ({0})", statuses));
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(query.TargetId))
|
||||
{
|
||||
whereClauses.Add("TargetId=?");
|
||||
paramList.Add(query.TargetId);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(query.ExcludeTargetIds))
|
||||
{
|
||||
var excludeIds = (query.ExcludeTargetIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (excludeIds.Length == 1)
|
||||
{
|
||||
whereClauses.Add("TargetId<>?");
|
||||
paramList.Add(excludeIds[0]);
|
||||
}
|
||||
else if (excludeIds.Length > 1)
|
||||
{
|
||||
whereClauses.Add("TargetId<>?");
|
||||
paramList.Add(excludeIds[0]);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(query.UserId))
|
||||
{
|
||||
whereClauses.Add("UserId=?");
|
||||
paramList.Add(query.UserId);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(query.ItemId))
|
||||
{
|
||||
whereClauses.Add("ItemIds like ?");
|
||||
paramList.Add("%" + query.ItemId + "%");
|
||||
}
|
||||
if (query.SyncNewContent.HasValue)
|
||||
{
|
||||
whereClauses.Add("SyncNewContent=?");
|
||||
paramList.Add(query.SyncNewContent.Value);
|
||||
}
|
||||
|
||||
commandText += " mainTable";
|
||||
|
||||
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
|
||||
var startIndex = query.StartIndex ?? 0;
|
||||
if (startIndex > 0)
|
||||
{
|
||||
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC LIMIT {0})",
|
||||
startIndex.ToString(_usCulture)));
|
||||
}
|
||||
|
||||
if (whereClauses.Count > 0)
|
||||
{
|
||||
commandText += " where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
}
|
||||
|
||||
commandText += " ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC";
|
||||
|
||||
if (query.Limit.HasValue)
|
||||
{
|
||||
commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
|
||||
}
|
||||
|
||||
var list = new List<SyncJob>();
|
||||
var count = connection.Query("select count (Id) from SyncJobs" + whereTextWithoutPaging, paramList.ToArray())
|
||||
.SelectScalarInt()
|
||||
.First();
|
||||
|
||||
foreach (var row in connection.Query(commandText, paramList.ToArray()))
|
||||
{
|
||||
list.Add(GetJob(row));
|
||||
}
|
||||
|
||||
return new QueryResult<SyncJob>()
|
||||
{
|
||||
Items = list.ToArray(),
|
||||
TotalRecordCount = count
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SyncJobItem GetJobItem(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new ArgumentNullException("id");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
var guid = new Guid(id);
|
||||
|
||||
using (WriteLock.Read())
|
||||
{
|
||||
using (var connection = CreateConnection(true))
|
||||
{
|
||||
var commandText = BaseJobItemSelectText + " where Id=?";
|
||||
var paramList = new List<object>();
|
||||
|
||||
paramList.Add(guid.ToGuidParamValue());
|
||||
|
||||
foreach (var row in connection.Query(commandText, paramList.ToArray()))
|
||||
{
|
||||
return GetJobItem(row);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private QueryResult<T> GetJobItemReader<T>(SyncJobItemQuery query, string baseSelectText, Func<IReadOnlyList<IResultSetValue>, T> itemFactory)
|
||||
{
|
||||
if (query == null)
|
||||
{
|
||||
throw new ArgumentNullException("query");
|
||||
}
|
||||
|
||||
using (WriteLock.Read())
|
||||
{
|
||||
using (var connection = CreateConnection(true))
|
||||
{
|
||||
var commandText = baseSelectText;
|
||||
var paramList = new List<object>();
|
||||
|
||||
var whereClauses = new List<string>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.JobId))
|
||||
{
|
||||
whereClauses.Add("JobId=?");
|
||||
paramList.Add(query.JobId);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(query.ItemId))
|
||||
{
|
||||
whereClauses.Add("ItemId=?");
|
||||
paramList.Add(query.ItemId);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(query.TargetId))
|
||||
{
|
||||
whereClauses.Add("TargetId=?");
|
||||
paramList.Add(query.TargetId);
|
||||
}
|
||||
|
||||
if (query.Statuses.Length > 0)
|
||||
{
|
||||
var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
|
||||
|
||||
whereClauses.Add(string.Format("Status in ({0})", statuses));
|
||||
}
|
||||
|
||||
var whereTextWithoutPaging = whereClauses.Count == 0 ?
|
||||
string.Empty :
|
||||
" where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
|
||||
var startIndex = query.StartIndex ?? 0;
|
||||
if (startIndex > 0)
|
||||
{
|
||||
whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY JobItemIndex, DateCreated LIMIT {0})",
|
||||
startIndex.ToString(_usCulture)));
|
||||
}
|
||||
|
||||
if (whereClauses.Count > 0)
|
||||
{
|
||||
commandText += " where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
}
|
||||
|
||||
commandText += " ORDER BY JobItemIndex, DateCreated";
|
||||
|
||||
if (query.Limit.HasValue)
|
||||
{
|
||||
commandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
|
||||
}
|
||||
|
||||
var list = new List<T>();
|
||||
var count = connection.Query("select count (Id) from SyncJobItems" + whereTextWithoutPaging, paramList.ToArray())
|
||||
.SelectScalarInt()
|
||||
.First();
|
||||
|
||||
foreach (var row in connection.Query(commandText, paramList.ToArray()))
|
||||
{
|
||||
list.Add(itemFactory(row));
|
||||
}
|
||||
|
||||
return new QueryResult<T>()
|
||||
{
|
||||
Items = list.ToArray(),
|
||||
TotalRecordCount = count
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, SyncedItemProgress> GetSyncedItemProgresses(SyncJobItemQuery query)
|
||||
{
|
||||
var result = new Dictionary<string, SyncedItemProgress>();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
using (WriteLock.Read())
|
||||
{
|
||||
using (var connection = CreateConnection(true))
|
||||
{
|
||||
var commandText = "select ItemId,Status,Progress from SyncJobItems";
|
||||
var whereClauses = new List<string>();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(query.TargetId))
|
||||
{
|
||||
whereClauses.Add("TargetId=@TargetId");
|
||||
}
|
||||
|
||||
if (query.Statuses.Length > 0)
|
||||
{
|
||||
var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
|
||||
|
||||
whereClauses.Add(string.Format("Status in ({0})", statuses));
|
||||
}
|
||||
|
||||
if (whereClauses.Count > 0)
|
||||
{
|
||||
commandText += " where " + string.Join(" AND ", whereClauses.ToArray());
|
||||
}
|
||||
|
||||
var statementTexts = new List<string>
|
||||
{
|
||||
commandText
|
||||
};
|
||||
|
||||
commandText = commandText
|
||||
.Replace("select ItemId,Status,Progress from SyncJobItems", "select ItemIds,Status,Progress from SyncJobs")
|
||||
.Replace("'Synced'", "'Completed','CompletedWithError'");
|
||||
|
||||
statementTexts.Add(commandText);
|
||||
|
||||
var statements = connection.PrepareAll(string.Join(";", statementTexts.ToArray()))
|
||||
.ToList();
|
||||
|
||||
using (var statement = statements[0])
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(query.TargetId))
|
||||
{
|
||||
statement.TryBind("@TargetId", query.TargetId);
|
||||
}
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
AddStatusResult(row, result, false);
|
||||
}
|
||||
LogQueryTime("GetSyncedItemProgresses", commandText, now);
|
||||
}
|
||||
|
||||
now = DateTime.UtcNow;
|
||||
|
||||
using (var statement = statements[1])
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(query.TargetId))
|
||||
{
|
||||
statement.TryBind("@TargetId", query.TargetId);
|
||||
}
|
||||
|
||||
foreach (var row in statement.ExecuteQuery())
|
||||
{
|
||||
AddStatusResult(row, result, true);
|
||||
}
|
||||
LogQueryTime("GetSyncedItemProgresses", commandText, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void LogQueryTime(string methodName, string commandText, DateTime startDate)
|
||||
{
|
||||
var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
|
||||
|
||||
var slowThreshold = 1000;
|
||||
|
||||
#if DEBUG
|
||||
slowThreshold = 50;
|
||||
#endif
|
||||
|
||||
if (elapsed >= slowThreshold)
|
||||
{
|
||||
Logger.Debug("{2} query time (slow): {0}ms. Query: {1}",
|
||||
Convert.ToInt32(elapsed),
|
||||
commandText,
|
||||
methodName);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Logger.Debug("{2} query time: {0}ms. Query: {1}",
|
||||
// Convert.ToInt32(elapsed),
|
||||
// cmd.CommandText,
|
||||
// methodName);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddStatusResult(IReadOnlyList<IResultSetValue> reader, Dictionary<string, SyncedItemProgress> result, bool multipleIds)
|
||||
{
|
||||
if (reader[0].SQLiteType == SQLiteType.Null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var itemIds = new List<string>();
|
||||
|
||||
var ids = reader[0].ToString();
|
||||
|
||||
if (multipleIds)
|
||||
{
|
||||
itemIds = ids.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
itemIds.Add(ids);
|
||||
}
|
||||
|
||||
if (reader[1].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
SyncJobItemStatus status;
|
||||
var statusString = reader[1].ToString();
|
||||
if (string.Equals(statusString, "Completed", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(statusString, "CompletedWithError", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
status = SyncJobItemStatus.Synced;
|
||||
}
|
||||
else
|
||||
{
|
||||
status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), statusString, true);
|
||||
}
|
||||
|
||||
if (status == SyncJobItemStatus.Synced)
|
||||
{
|
||||
foreach (var itemId in itemIds)
|
||||
{
|
||||
result[itemId] = new SyncedItemProgress
|
||||
{
|
||||
Status = SyncJobItemStatus.Synced
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double progress = reader[2].SQLiteType == SQLiteType.Null ? 0.0 : reader[2].ToDouble();
|
||||
|
||||
foreach (var itemId in itemIds)
|
||||
{
|
||||
SyncedItemProgress currentStatus;
|
||||
if (!result.TryGetValue(itemId, out currentStatus) || (currentStatus.Status != SyncJobItemStatus.Synced && progress >= currentStatus.Progress))
|
||||
{
|
||||
result[itemId] = new SyncedItemProgress
|
||||
{
|
||||
Status = status,
|
||||
Progress = progress
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public QueryResult<SyncJobItem> GetJobItems(SyncJobItemQuery query)
|
||||
{
|
||||
return GetJobItemReader(query, BaseJobItemSelectText, GetJobItem);
|
||||
}
|
||||
|
||||
public Task Create(SyncJobItem jobItem)
|
||||
{
|
||||
return InsertOrUpdate(jobItem, true);
|
||||
}
|
||||
|
||||
public Task Update(SyncJobItem jobItem)
|
||||
{
|
||||
return InsertOrUpdate(jobItem, false);
|
||||
}
|
||||
|
||||
private async Task InsertOrUpdate(SyncJobItem jobItem, bool insert)
|
||||
{
|
||||
if (jobItem == null)
|
||||
{
|
||||
throw new ArgumentNullException("jobItem");
|
||||
}
|
||||
|
||||
CheckDisposed();
|
||||
|
||||
using (WriteLock.Write())
|
||||
{
|
||||
using (var connection = CreateConnection())
|
||||
{
|
||||
string commandText;
|
||||
|
||||
if (insert)
|
||||
{
|
||||
commandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
}
|
||||
else
|
||||
{
|
||||
// cmd
|
||||
commandText = "update SyncJobItems set ItemId=?,ItemName=?,MediaSourceId=?,JobId=?,TemporaryPath=?,OutputPath=?,Status=?,TargetId=?,DateCreated=?,Progress=?,AdditionalFiles=?,MediaSource=?,IsMarkedForRemoval=?,JobItemIndex=?,ItemDateModifiedTicks=? where Id=?";
|
||||
}
|
||||
|
||||
var paramList = new List<object>();
|
||||
paramList.Add(jobItem.ItemId);
|
||||
paramList.Add(jobItem.ItemName);
|
||||
paramList.Add(jobItem.MediaSourceId);
|
||||
paramList.Add(jobItem.JobId);
|
||||
paramList.Add(jobItem.TemporaryPath);
|
||||
paramList.Add(jobItem.OutputPath);
|
||||
paramList.Add(jobItem.Status.ToString());
|
||||
|
||||
paramList.Add(jobItem.TargetId);
|
||||
paramList.Add(jobItem.DateCreated.ToDateTimeParamValue());
|
||||
paramList.Add(jobItem.Progress);
|
||||
paramList.Add(_json.SerializeToString(jobItem.AdditionalFiles));
|
||||
paramList.Add(jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource));
|
||||
paramList.Add(jobItem.IsMarkedForRemoval);
|
||||
paramList.Add(jobItem.JobItemIndex);
|
||||
paramList.Add(jobItem.ItemDateModifiedTicks);
|
||||
|
||||
if (insert)
|
||||
{
|
||||
paramList.Insert(0, jobItem.Id.ToGuidParamValue());
|
||||
}
|
||||
else
|
||||
{
|
||||
paramList.Add(jobItem.Id.ToGuidParamValue());
|
||||
}
|
||||
|
||||
connection.RunInTransaction(conn =>
|
||||
{
|
||||
conn.Execute(commandText, paramList.ToArray());
|
||||
}, TransactionMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SyncJobItem GetJobItem(IReadOnlyList<IResultSetValue> reader)
|
||||
{
|
||||
var info = new SyncJobItem
|
||||
{
|
||||
Id = reader[0].ReadGuid().ToString("N"),
|
||||
ItemId = reader[1].ToString()
|
||||
};
|
||||
|
||||
if (reader[2].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.ItemName = reader[2].ToString();
|
||||
}
|
||||
|
||||
if (reader[3].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.MediaSourceId = reader[3].ToString();
|
||||
}
|
||||
|
||||
info.JobId = reader[4].ToString();
|
||||
|
||||
if (reader[5].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.TemporaryPath = reader[5].ToString();
|
||||
}
|
||||
if (reader[6].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.OutputPath = reader[6].ToString();
|
||||
}
|
||||
|
||||
if (reader[7].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader[7].ToString(), true);
|
||||
}
|
||||
|
||||
info.TargetId = reader[8].ToString();
|
||||
|
||||
info.DateCreated = reader[9].ReadDateTime();
|
||||
|
||||
if (reader[10].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.Progress = reader[10].ToDouble();
|
||||
}
|
||||
|
||||
if (reader[11].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
var json = reader[11].ToString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
info.AdditionalFiles = _json.DeserializeFromString<List<ItemFileInfo>>(json);
|
||||
}
|
||||
}
|
||||
|
||||
if (reader[12].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
var json = reader[12].ToString();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
info.MediaSource = _json.DeserializeFromString<MediaSourceInfo>(json);
|
||||
}
|
||||
}
|
||||
|
||||
info.IsMarkedForRemoval = reader[13].ToBool();
|
||||
info.JobItemIndex = reader[14].ToInt();
|
||||
|
||||
if (reader[15].SQLiteType != SQLiteType.Null)
|
||||
{
|
||||
info.ItemDateModifiedTicks = reader[15].ToInt64();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,158 +0,0 @@
|
||||
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;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class SyncedMediaSourceProvider : IMediaSourceProvider
|
||||
{
|
||||
private readonly SyncManager _syncManager;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public SyncedMediaSourceProvider(ISyncManager syncManager, IServerApplicationHost appHost, ILogger logger)
|
||||
{
|
||||
_appHost = appHost;
|
||||
_logger = logger;
|
||||
_syncManager = (SyncManager)syncManager;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
|
||||
{
|
||||
var jobItemResult = _syncManager.GetJobItems(new SyncJobItemQuery
|
||||
{
|
||||
AddMetadata = false,
|
||||
Statuses = new[] { SyncJobItemStatus.Synced },
|
||||
ItemId = item.Id.ToString("N")
|
||||
});
|
||||
|
||||
var list = new List<MediaSourceInfo>();
|
||||
|
||||
if (jobItemResult.Items.Length > 0)
|
||||
{
|
||||
var targets = _syncManager.ServerSyncProviders
|
||||
.SelectMany(i => i.GetAllSyncTargets().Select(t => new Tuple<IServerSyncProvider, SyncTarget>(i, t)))
|
||||
.ToList();
|
||||
|
||||
var serverId = _appHost.SystemId;
|
||||
|
||||
foreach (var jobItem in jobItemResult.Items)
|
||||
{
|
||||
var targetTuple = targets.FirstOrDefault(i => string.Equals(i.Item2.Id, jobItem.TargetId, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (targetTuple != null)
|
||||
{
|
||||
var syncTarget = targetTuple.Item2;
|
||||
var syncProvider = targetTuple.Item1;
|
||||
var dataProvider = _syncManager.GetDataProvider(targetTuple.Item1, syncTarget);
|
||||
|
||||
var localItems = await dataProvider.GetItems(syncTarget, serverId, item.Id.ToString("N")).ConfigureAwait(false);
|
||||
|
||||
foreach (var localItem in localItems)
|
||||
{
|
||||
foreach (var mediaSource in localItem.Item.MediaSources)
|
||||
{
|
||||
AddMediaSource(list, localItem, mediaSource, syncProvider, syncTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(StreamIdDelimeterString, keyList.ToArray());
|
||||
}
|
||||
|
||||
list.Add(mediaSource);
|
||||
}
|
||||
|
||||
// Do not use a pipe here because Roku http requests to the server will fail, without any explicit error message.
|
||||
private const string StreamIdDelimeterString = "_";
|
||||
|
||||
public async Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
|
||||
{
|
||||
var openKeys = openToken.Split(new[] { StreamIdDelimeterString[0] }, 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 fileId = localItem.FileId;
|
||||
if (string.IsNullOrWhiteSpace(fileId))
|
||||
{
|
||||
}
|
||||
|
||||
var requiresDynamicAccess = (IHasDynamicAccess)provider;
|
||||
var dynamicInfo = await requiresDynamicAccess.GetSyncedFileInfo(fileId, 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 new Tuple<MediaSourceInfo, IDirectStreamProvider>(mediaSource, null);
|
||||
}
|
||||
|
||||
private void SetStaticMediaSourceInfo(LocalItem item, MediaSourceInfo mediaSource)
|
||||
{
|
||||
mediaSource.Id = item.Id;
|
||||
mediaSource.SupportsTranscoding = false;
|
||||
if (mediaSource.Protocol == MediaBrowser.Model.MediaInfo.MediaProtocol.File)
|
||||
{
|
||||
mediaSource.ETag = item.Id;
|
||||
}
|
||||
}
|
||||
|
||||
public Task CloseMediaSource(string liveStreamId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Sync;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
|
||||
namespace Emby.Server.Implementations.Sync
|
||||
{
|
||||
public class TargetDataProvider : ISyncDataProvider
|
||||
{
|
||||
private readonly SyncTarget _target;
|
||||
private readonly IServerSyncProvider _provider;
|
||||
|
||||
private readonly SemaphoreSlim _dataLock = new SemaphoreSlim(1, 1);
|
||||
private readonly SemaphoreSlim _remoteDataLock = new SemaphoreSlim(1, 1);
|
||||
private List<LocalItem> _items;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly IJsonSerializer _json;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
private readonly IMemoryStreamFactory _memoryStreamProvider;
|
||||
|
||||
public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamFactory memoryStreamProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_json = json;
|
||||
_provider = provider;
|
||||
_target = target;
|
||||
_fileSystem = fileSystem;
|
||||
_appPaths = appPaths;
|
||||
_memoryStreamProvider = memoryStreamProvider;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
private string[] GetRemotePath()
|
||||
{
|
||||
var parts = new List<string>
|
||||
{
|
||||
_appHost.FriendlyName,
|
||||
"data.json"
|
||||
};
|
||||
|
||||
parts = parts.Select(i => GetValidFilename(_provider, i)).ToList();
|
||||
|
||||
return parts.ToArray();
|
||||
}
|
||||
|
||||
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<List<LocalItem>> RetrieveItems(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Debug("Getting {0} from {1}", string.Join(MediaSync.PathSeparatorString, GetRemotePath().ToArray()), _provider.Name);
|
||||
|
||||
await _remoteDataLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
var fileResult = await _provider.GetFiles(GetRemotePath().ToArray(), _target, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (fileResult.Items.Length > 0)
|
||||
{
|
||||
using (var stream = await _provider.GetFile(fileResult.Items[0].FullName, _target, new Progress<double>(), cancellationToken))
|
||||
{
|
||||
return _json.DeserializeFromStream<List<LocalItem>>(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_remoteDataLock.Release();
|
||||
}
|
||||
|
||||
return new List<LocalItem>();
|
||||
}
|
||||
|
||||
private async Task EnsureData(CancellationToken cancellationToken)
|
||||
{
|
||||
if (_items == null)
|
||||
{
|
||||
_items = await RetrieveItems(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveData(List<LocalItem> items, CancellationToken cancellationToken)
|
||||
{
|
||||
using (var stream = _memoryStreamProvider.CreateNew())
|
||||
{
|
||||
_json.SerializeToStream(items, stream);
|
||||
|
||||
// Save to sync provider
|
||||
stream.Position = 0;
|
||||
var remotePath = GetRemotePath();
|
||||
|
||||
await _remoteDataLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
_logger.Debug("Saving data.json to {0}. Remote path: {1}", _provider.Name, string.Join("/", remotePath));
|
||||
|
||||
await _provider.SendFile(stream, remotePath, _target, new Progress<double>(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_remoteDataLock.Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<T> GetData<T>(bool enableCache, Func<List<LocalItem>, T> dataFactory)
|
||||
{
|
||||
if (!enableCache)
|
||||
{
|
||||
var items = await RetrieveItems(CancellationToken.None).ConfigureAwait(false);
|
||||
var newCache = items.ToList();
|
||||
var result = dataFactory(items);
|
||||
await UpdateCache(newCache).ConfigureAwait(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
await _dataLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
await EnsureData(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
return dataFactory(_items);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dataLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateData(Func<List<LocalItem>, List<LocalItem>> action)
|
||||
{
|
||||
var items = await RetrieveItems(CancellationToken.None).ConfigureAwait(false);
|
||||
items = action(items);
|
||||
await SaveData(items.ToList(), CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
await UpdateCache(null).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task UpdateCache(List<LocalItem> list)
|
||||
{
|
||||
await _dataLock.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
_items = list;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_dataLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<LocalItem>> GetLocalItems(SyncTarget target, string serverId)
|
||||
{
|
||||
return GetData(false, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase)).ToList());
|
||||
}
|
||||
|
||||
public Task AddOrUpdate(SyncTarget target, LocalItem item)
|
||||
{
|
||||
return UpdateData(items =>
|
||||
{
|
||||
var list = items.Where(i => !string.Equals(i.Id, item.Id, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
|
||||
list.Add(item);
|
||||
|
||||
return list;
|
||||
});
|
||||
}
|
||||
|
||||
public Task Delete(SyncTarget target, string id)
|
||||
{
|
||||
return UpdateData(items => items.Where(i => !string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)).ToList());
|
||||
}
|
||||
|
||||
public Task<LocalItem> Get(SyncTarget target, string id)
|
||||
{
|
||||
return GetData(true, items => items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
public Task<List<LocalItem>> GetItems(SyncTarget target, string serverId, string itemId)
|
||||
{
|
||||
return GetData(true, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.ItemId, itemId, StringComparison.OrdinalIgnoreCase)).ToList());
|
||||
}
|
||||
|
||||
public Task<List<LocalItem>> GetItemsBySyncJobItemId(SyncTarget target, string serverId, string syncJobItemId)
|
||||
{
|
||||
return GetData(false, items => items.Where(i => string.Equals(i.ServerId, serverId, StringComparison.OrdinalIgnoreCase) && string.Equals(i.SyncJobItemId, syncJobItemId, StringComparison.OrdinalIgnoreCase)).ToList());
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,6 @@
|
||||
<package id="Emby.XmlTv" version="1.0.7" targetFramework="portable45-net45+win8" />
|
||||
<package id="MediaBrowser.Naming" version="1.0.4" targetFramework="portable45-net45+win8" />
|
||||
<package id="SQLitePCL.pretty" version="1.1.0" targetFramework="portable45-net45+win8" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.1" targetFramework="portable45-net45+win8" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="portable45-net45+win8" />
|
||||
<package id="UniversalDetector" version="1.0.1" targetFramework="portable45-net45+win8" />
|
||||
</packages>
|
@ -1,177 +0,0 @@
|
||||
using MediaBrowser.Common.Extensions;
|
||||
using MediaBrowser.Controller.Connect;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Controller.Net;
|
||||
using MediaBrowser.Model.Connect;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Controller.Session;
|
||||
using MediaBrowser.Model.Services;
|
||||
|
||||
namespace MediaBrowser.Api
|
||||
{
|
||||
[Route("/Users/{Id}/Connect/Link", "POST", Summary = "Creates a Connect link for a user")]
|
||||
[Authenticated(Roles = "Admin")]
|
||||
public class CreateConnectLink : IReturn<UserLinkResult>
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string Id { get; set; }
|
||||
|
||||
[ApiMember(Name = "ConnectUsername", Description = "Connect username", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string ConnectUsername { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Users/{Id}/Connect/Link", "DELETE", Summary = "Removes a Connect link for a user")]
|
||||
[Authenticated(Roles = "Admin")]
|
||||
public class DeleteConnectLink : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Connect/Invite", "POST", Summary = "Creates a Connect link for a user")]
|
||||
[Authenticated(Roles = "Admin")]
|
||||
public class CreateConnectInvite : IReturn<UserLinkResult>
|
||||
{
|
||||
[ApiMember(Name = "ConnectUsername", Description = "Connect username", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")]
|
||||
public string ConnectUsername { get; set; }
|
||||
|
||||
[ApiMember(Name = "SendingUserId", Description = "Sending User Id", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")]
|
||||
public string SendingUserId { get; set; }
|
||||
|
||||
[ApiMember(Name = "EnabledLibraries", Description = "EnabledLibraries", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")]
|
||||
public string EnabledLibraries { get; set; }
|
||||
|
||||
[ApiMember(Name = "EnabledChannels", Description = "EnabledChannels", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")]
|
||||
public string EnabledChannels { get; set; }
|
||||
|
||||
[ApiMember(Name = "EnableLiveTv", Description = "EnableLiveTv", IsRequired = true, DataType = "string", ParameterType = "body", Verb = "POST")]
|
||||
public bool EnableLiveTv { get; set; }
|
||||
}
|
||||
|
||||
|
||||
[Route("/Connect/Pending", "GET", Summary = "Creates a Connect link for a user")]
|
||||
[Authenticated(Roles = "Admin")]
|
||||
public class GetPendingGuests : IReturn<List<ConnectAuthorization>>
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
[Route("/Connect/Pending", "DELETE", Summary = "Deletes a Connect link for a user")]
|
||||
[Authenticated(Roles = "Admin")]
|
||||
public class DeleteAuthorization : IReturnVoid
|
||||
{
|
||||
[ApiMember(Name = "Id", Description = "Authorization Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "DELETE")]
|
||||
public string Id { get; set; }
|
||||
}
|
||||
|
||||
[Route("/Connect/Exchange", "GET", Summary = "Gets the corresponding local user from a connect user id")]
|
||||
[Authenticated]
|
||||
public class GetLocalUser : IReturn<ConnectAuthenticationExchangeResult>
|
||||
{
|
||||
[ApiMember(Name = "ConnectUserId", Description = "ConnectUserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
|
||||
public string ConnectUserId { get; set; }
|
||||
}
|
||||
|
||||
public class ConnectService : BaseApiService
|
||||
{
|
||||
private readonly IConnectManager _connectManager;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly IAuthorizationContext _authContext;
|
||||
|
||||
public ConnectService(IConnectManager connectManager, ISessionManager sessionManager, IAuthorizationContext authContext)
|
||||
{
|
||||
_connectManager = connectManager;
|
||||
_sessionManager = sessionManager;
|
||||
_authContext = authContext;
|
||||
}
|
||||
|
||||
public object Post(CreateConnectLink request)
|
||||
{
|
||||
return _connectManager.LinkUser(request.Id, request.ConnectUsername);
|
||||
}
|
||||
|
||||
public object Post(CreateConnectInvite request)
|
||||
{
|
||||
var enabledLibraries = (request.EnabledLibraries ?? string.Empty)
|
||||
.Split(',')
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.ToArray();
|
||||
|
||||
var enabledChannels = (request.EnabledChannels ?? string.Empty)
|
||||
.Split(',')
|
||||
.Where(i => !string.IsNullOrWhiteSpace(i))
|
||||
.ToArray();
|
||||
|
||||
return _connectManager.InviteUser(new ConnectAuthorizationRequest
|
||||
{
|
||||
ConnectUserName = request.ConnectUsername,
|
||||
SendingUserId = request.SendingUserId,
|
||||
EnabledLibraries = enabledLibraries,
|
||||
EnabledChannels = enabledChannels,
|
||||
EnableLiveTv = request.EnableLiveTv
|
||||
});
|
||||
}
|
||||
|
||||
public void Delete(DeleteConnectLink request)
|
||||
{
|
||||
var task = _connectManager.RemoveConnect(request.Id);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetPendingGuests request)
|
||||
{
|
||||
var result = await _connectManager.GetPendingGuests().ConfigureAwait(false);
|
||||
|
||||
return ToOptimizedResult(result);
|
||||
}
|
||||
|
||||
public void Delete(DeleteAuthorization request)
|
||||
{
|
||||
var task = _connectManager.CancelAuthorization(request.Id);
|
||||
|
||||
Task.WaitAll(task);
|
||||
}
|
||||
|
||||
public async Task<object> Get(GetLocalUser request)
|
||||
{
|
||||
var user = await _connectManager.GetLocalUser(request.ConnectUserId).ConfigureAwait(false);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
var auth = _authContext.GetAuthorizationInfo(Request);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(auth.Client))
|
||||
{
|
||||
return ToOptimizedResult(new ConnectAuthenticationExchangeResult
|
||||
{
|
||||
AccessToken = user.ConnectAccessKey,
|
||||
LocalUserId = user.Id.ToString("N")
|
||||
});
|
||||
}
|
||||
|
||||
var session = await _sessionManager.CreateNewSession(new AuthenticationRequest
|
||||
{
|
||||
App = auth.Client,
|
||||
AppVersion = auth.Version,
|
||||
DeviceId = auth.DeviceId,
|
||||
DeviceName = auth.Device,
|
||||
RemoteEndPoint = Request.RemoteIp,
|
||||
Username = user.Name,
|
||||
UserId = user.Id.ToString("N")
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return ToOptimizedResult(new ConnectAuthenticationExchangeResult
|
||||
{
|
||||
AccessToken = session.AccessToken,
|
||||
LocalUserId = session.User.Id
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -791,7 +791,7 @@ namespace MediaBrowser.Api.LiveTv
|
||||
ProviderChannels = providerChannels.Select(i => new NameIdPair
|
||||
{
|
||||
Name = i.Name,
|
||||
Id = string.IsNullOrWhiteSpace(i.TunerChannelId) ? i.Id : i.TunerChannelId
|
||||
Id = i.Id
|
||||
|
||||
}).ToList(),
|
||||
|
||||
|
@ -51,7 +51,6 @@
|
||||
<Compile Include="BasePeriodicWebSocketListener.cs" />
|
||||
<Compile Include="BrandingService.cs" />
|
||||
<Compile Include="ChannelService.cs" />
|
||||
<Compile Include="ConnectService.cs" />
|
||||
<Compile Include="Devices\DeviceService.cs" />
|
||||
<Compile Include="Dlna\DlnaServerService.cs" />
|
||||
<Compile Include="Dlna\DlnaService.cs" />
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.LiveTv;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
@ -23,7 +22,7 @@ namespace MediaBrowser.Controller.LiveTv
|
||||
/// Gets the channels.
|
||||
/// </summary>
|
||||
/// <returns>Task<IEnumerable<ChannelInfo>>.</returns>
|
||||
Task<IEnumerable<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken);
|
||||
Task<List<ChannelInfo>> GetChannels(bool enableCache, CancellationToken cancellationToken);
|
||||
/// <summary>
|
||||
/// Gets the tuner infos.
|
||||
/// </summary>
|
||||
|
@ -67,12 +67,15 @@
|
||||
<Reference Include="Emby.Server.Core">
|
||||
<HintPath>..\ThirdParty\emby\Emby.Server.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Emby.Server.Sync">
|
||||
<HintPath>..\ThirdParty\emby\Emby.Server.Sync.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Mono.Posix, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
|
||||
<HintPath>..\packages\NLog.4.4.3\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ServiceStack.Text, Version=4.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
@ -83,16 +86,16 @@
|
||||
<HintPath>..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SimpleInjector, Version=3.2.4.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll</HintPath>
|
||||
<Reference Include="SimpleInjector, Version=3.3.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.1\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.1\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
|
||||
<HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
@ -4,7 +4,9 @@ using System.Reflection;
|
||||
using Emby.Server.Connect;
|
||||
using Emby.Server.Core;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Sync;
|
||||
using MediaBrowser.Controller.Connect;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.IsoMounter;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
@ -32,6 +34,11 @@ namespace MediaBrowser.Server.Mono
|
||||
return new ConnectManager();
|
||||
}
|
||||
|
||||
protected override ISyncManager CreateSyncManager()
|
||||
{
|
||||
return new SyncManager();
|
||||
}
|
||||
|
||||
protected override void RestartInternal()
|
||||
{
|
||||
MainClass.Restart(StartupOptions);
|
||||
@ -53,6 +60,7 @@ namespace MediaBrowser.Server.Mono
|
||||
|
||||
list.Add(typeof(LinuxIsoManager).Assembly);
|
||||
list.Add(typeof(ConnectManager).Assembly);
|
||||
list.Add(typeof(SyncManager).Assembly);
|
||||
|
||||
return list;
|
||||
}
|
||||
|
@ -20,6 +20,10 @@
|
||||
<assemblyIdentity name="System.IO.FileSystem.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.1.0" newVersion="4.0.1.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.3.2.0" newVersion="3.3.2.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /></startup></configuration>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" />
|
||||
<package id="NLog" version="4.4.0-betaV15" targetFramework="net46" />
|
||||
<package id="NLog" version="4.4.3" targetFramework="net46" />
|
||||
<package id="ServiceStack.Text" version="4.5.4" targetFramework="net46" />
|
||||
<package id="SharpCompress" version="0.14.0" targetFramework="net46" />
|
||||
<package id="SimpleInjector" version="3.2.4" targetFramework="net46" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.1" targetFramework="net46" />
|
||||
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.1" targetFramework="net46" />
|
||||
<package id="SimpleInjector" version="3.3.2" targetFramework="net46" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="net46" />
|
||||
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.2" targetFramework="net46" />
|
||||
</packages>
|
@ -49,7 +49,7 @@
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.3.6.0" newVersion="2.3.6.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-3.3.2.0" newVersion="3.3.2.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.IO.FileSystem.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
|
||||
|
@ -73,12 +73,15 @@
|
||||
<Reference Include="Emby.Server.Core">
|
||||
<HintPath>..\ThirdParty\emby\Emby.Server.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Emby.Server.Sync">
|
||||
<HintPath>..\ThirdParty\emby\Emby.Server.Sync.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\packages\ImageMagickSharp.1.0.0.18\lib\net45\ImageMagickSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.4.0-betaV15\lib\net45\NLog.dll</HintPath>
|
||||
<HintPath>..\packages\NLog.4.4.3\lib\net45\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="ServiceStack.Text, Version=4.5.4.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
@ -89,16 +92,16 @@
|
||||
<HintPath>..\packages\SharpCompress.0.14.0\lib\net45\SharpCompress.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SimpleInjector, Version=3.2.4.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SimpleInjector.3.2.4\lib\net45\SimpleInjector.dll</HintPath>
|
||||
<Reference Include="SimpleInjector, Version=3.3.2.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SimpleInjector.3.3.2\lib\net45\SimpleInjector.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.1\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
||||
<HintPath>..\packages\SQLitePCLRaw.core.1.1.2\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.provider.sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=62684c7b4f184e3f, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.1\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
|
||||
<HintPath>..\packages\SQLitePCLRaw.provider.sqlite3.net45.1.1.2\lib\net45\SQLitePCLRaw.provider.sqlite3.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
@ -9,7 +9,9 @@ using Emby.Server.Core;
|
||||
using Emby.Server.Implementations;
|
||||
using Emby.Server.Implementations.EntryPoints;
|
||||
using Emby.Server.Implementations.FFMpeg;
|
||||
using Emby.Server.Sync;
|
||||
using MediaBrowser.Controller.Connect;
|
||||
using MediaBrowser.Controller.Sync;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.System;
|
||||
@ -34,6 +36,11 @@ namespace MediaBrowser.ServerApplication
|
||||
return new ConnectManager();
|
||||
}
|
||||
|
||||
protected override ISyncManager CreateSyncManager()
|
||||
{
|
||||
return new SyncManager();
|
||||
}
|
||||
|
||||
protected override void RestartInternal()
|
||||
{
|
||||
MainStartup.Restart();
|
||||
@ -49,6 +56,7 @@ namespace MediaBrowser.ServerApplication
|
||||
}
|
||||
|
||||
list.Add(typeof(ConnectManager).Assembly);
|
||||
list.Add(typeof(SyncManager).Assembly);
|
||||
list.Add(GetType().Assembly);
|
||||
|
||||
return list;
|
||||
|
@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="ImageMagickSharp" version="1.0.0.18" targetFramework="net45" />
|
||||
<package id="NLog" version="4.4.0-betaV15" targetFramework="net462" />
|
||||
<package id="NLog" version="4.4.3" targetFramework="net462" />
|
||||
<package id="ServiceStack.Text" version="4.5.4" targetFramework="net462" />
|
||||
<package id="SharpCompress" version="0.14.0" targetFramework="net462" />
|
||||
<package id="SimpleInjector" version="3.2.4" targetFramework="net462" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.1" targetFramework="net462" />
|
||||
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.1" targetFramework="net462" />
|
||||
<package id="SimpleInjector" version="3.3.2" targetFramework="net462" />
|
||||
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="net462" />
|
||||
<package id="SQLitePCLRaw.provider.sqlite3.net45" version="1.1.2" targetFramework="net462" />
|
||||
</packages>
|
@ -1,3 +1,3 @@
|
||||
using System.Reflection;
|
||||
|
||||
[assembly: AssemblyVersion("3.2.1.115")]
|
||||
[assembly: AssemblyVersion("3.2.1.116")]
|
||||
|
Loading…
Reference in New Issue
Block a user