mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-17 15:53:42 +01:00
move classes to portable server project
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
public static class ChannelConfigurationExtension
|
||||
{
|
||||
public static ChannelOptions GetChannelsConfiguration(this IConfigurationManager manager)
|
||||
{
|
||||
return manager.GetConfiguration<ChannelOptions>("channels");
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new List<ConfigurationStore>
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
Key = "channels",
|
||||
ConfigurationType = typeof (ChannelOptions)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
|
||||
{
|
||||
private readonly ChannelManager _channelManager;
|
||||
|
||||
public ChannelDynamicMediaSourceProvider(IChannelManager channelManager)
|
||||
{
|
||||
_channelManager = (ChannelManager)channelManager;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<MediaSourceInfo>> GetMediaSources(IHasMediaSources item, CancellationToken cancellationToken)
|
||||
{
|
||||
var baseItem = (BaseItem) item;
|
||||
|
||||
if (baseItem.SourceType == SourceType.Channel)
|
||||
{
|
||||
return _channelManager.GetDynamicMediaSources(baseItem, cancellationToken);
|
||||
}
|
||||
|
||||
return Task.FromResult<IEnumerable<MediaSourceInfo>>(new List<MediaSourceInfo>());
|
||||
}
|
||||
|
||||
public Task<Tuple<MediaSourceInfo, IDirectStreamProvider>> OpenMediaSource(string openToken, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task CloseMediaSource(string liveStreamId)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Emby.Server.Implementations/Channels/ChannelImageProvider.cs
Normal file
55
Emby.Server.Implementations/Channels/ChannelImageProvider.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Providers;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
|
||||
public ChannelImageProvider(IChannelManager channelManager)
|
||||
{
|
||||
_channelManager = channelManager;
|
||||
}
|
||||
|
||||
public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
|
||||
{
|
||||
return GetChannel(item).GetSupportedChannelImages();
|
||||
}
|
||||
|
||||
public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
|
||||
{
|
||||
var channel = GetChannel(item);
|
||||
|
||||
return channel.GetChannelImage(type, cancellationToken);
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Channel Image Provider"; }
|
||||
}
|
||||
|
||||
public bool Supports(IHasImages item)
|
||||
{
|
||||
return item is Channel;
|
||||
}
|
||||
|
||||
private IChannel GetChannel(IHasImages item)
|
||||
{
|
||||
var channel = (Channel)item;
|
||||
|
||||
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
|
||||
}
|
||||
|
||||
public bool HasChanged(IHasMetadata item, IDirectoryService directoryService)
|
||||
{
|
||||
return GetSupportedImages(item).Any(i => !item.HasImage(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
1620
Emby.Server.Implementations/Channels/ChannelManager.cs
Normal file
1620
Emby.Server.Implementations/Channels/ChannelManager.cs
Normal file
File diff suppressed because it is too large
Load Diff
257
Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
Normal file
257
Emby.Server.Implementations/Channels/ChannelPostScanTask.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Channels;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
public class ChannelPostScanTask
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public ChannelPostScanTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
|
||||
{
|
||||
_channelManager = channelManager;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var users = _userManager.Users
|
||||
.DistinctBy(GetUserDistinctValue)
|
||||
.Select(i => i.Id.ToString("N"))
|
||||
.ToList();
|
||||
|
||||
var numComplete = 0;
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
double percentPerUser = 1;
|
||||
percentPerUser /= users.Count;
|
||||
var startingPercent = numComplete * percentPerUser * 100;
|
||||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(p => progress.Report(startingPercent + percentPerUser * p));
|
||||
|
||||
await DownloadContent(user, cancellationToken, innerProgress).ConfigureAwait(false);
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= users.Count;
|
||||
progress.Report(percent * 100);
|
||||
}
|
||||
|
||||
await CleanDatabase(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
public static string GetUserDistinctValue(User user)
|
||||
{
|
||||
var channels = user.Policy.EnabledChannels
|
||||
.OrderBy(i => i)
|
||||
.ToList();
|
||||
|
||||
return string.Join("|", channels.ToArray());
|
||||
}
|
||||
|
||||
private async Task DownloadContent(string user, CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var channels = await _channelManager.GetChannelsInternal(new ChannelQuery
|
||||
{
|
||||
UserId = user
|
||||
|
||||
}, cancellationToken);
|
||||
|
||||
var numComplete = 0;
|
||||
var numItems = channels.Items.Length;
|
||||
|
||||
foreach (var channel in channels.Items)
|
||||
{
|
||||
var channelId = channel.Id.ToString("N");
|
||||
|
||||
var features = _channelManager.GetChannelFeatures(channelId);
|
||||
|
||||
const int currentRefreshLevel = 1;
|
||||
var maxRefreshLevel = features.AutoRefreshLevels ?? 0;
|
||||
maxRefreshLevel = Math.Max(maxRefreshLevel, 2);
|
||||
|
||||
if (maxRefreshLevel > 0)
|
||||
{
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
|
||||
var startingNumberComplete = numComplete;
|
||||
innerProgress.RegisterAction(p =>
|
||||
{
|
||||
double innerPercent = startingNumberComplete;
|
||||
innerPercent += p / 100;
|
||||
innerPercent /= numItems;
|
||||
progress.Report(innerPercent * 100);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await GetAllItems(user, channelId, null, currentRefreshLevel, maxRefreshLevel, innerProgress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting channel content", ex);
|
||||
}
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= numItems;
|
||||
progress.Report(percent * 100);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
|
||||
private async Task CleanDatabase(CancellationToken cancellationToken)
|
||||
{
|
||||
var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
|
||||
|
||||
var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Channel).Name }
|
||||
});
|
||||
|
||||
var invalidIds = databaseIds
|
||||
.Except(installedChannelIds)
|
||||
.ToList();
|
||||
|
||||
foreach (var id in invalidIds)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await CleanChannel(id, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CleanChannel(Guid id, CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Info("Cleaning channel {0} from database", id);
|
||||
|
||||
// Delete all channel items
|
||||
var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
|
||||
{
|
||||
ChannelIds = new[] { id.ToString("N") }
|
||||
});
|
||||
|
||||
foreach (var deleteId in allIds)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
await DeleteItem(deleteId).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Finally, delete the channel itself
|
||||
await DeleteItem(id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private Task DeleteItem(Guid id)
|
||||
{
|
||||
var item = _libraryManager.GetItemById(id);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
|
||||
return _libraryManager.DeleteItem(item, new DeleteOptions
|
||||
{
|
||||
DeleteFileLocation = false
|
||||
});
|
||||
}
|
||||
|
||||
private async Task GetAllItems(string user, string channelId, string folderId, int currentRefreshLevel, int maxRefreshLevel, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
var folderItems = new List<string>();
|
||||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
innerProgress.RegisterAction(p => progress.Report(p / 2));
|
||||
|
||||
var result = await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
|
||||
{
|
||||
ChannelId = channelId,
|
||||
UserId = user,
|
||||
FolderId = folderId
|
||||
|
||||
}, innerProgress, cancellationToken);
|
||||
|
||||
folderItems.AddRange(result.Items.Where(i => i.IsFolder).Select(i => i.Id.ToString("N")));
|
||||
|
||||
var totalRetrieved = result.Items.Length;
|
||||
var totalCount = result.TotalRecordCount;
|
||||
|
||||
while (totalRetrieved < totalCount)
|
||||
{
|
||||
result = await _channelManager.GetChannelItemsInternal(new ChannelItemQuery
|
||||
{
|
||||
ChannelId = channelId,
|
||||
UserId = user,
|
||||
StartIndex = totalRetrieved,
|
||||
FolderId = folderId
|
||||
|
||||
}, new Progress<double>(), cancellationToken);
|
||||
|
||||
folderItems.AddRange(result.Items.Where(i => i.IsFolder).Select(i => i.Id.ToString("N")));
|
||||
|
||||
totalRetrieved += result.Items.Length;
|
||||
totalCount = result.TotalRecordCount;
|
||||
}
|
||||
|
||||
progress.Report(50);
|
||||
|
||||
if (currentRefreshLevel < maxRefreshLevel)
|
||||
{
|
||||
var numComplete = 0;
|
||||
var numItems = folderItems.Count;
|
||||
|
||||
foreach (var folder in folderItems)
|
||||
{
|
||||
try
|
||||
{
|
||||
innerProgress = new ActionableProgress<double>();
|
||||
|
||||
var startingNumberComplete = numComplete;
|
||||
innerProgress.RegisterAction(p =>
|
||||
{
|
||||
double innerPercent = startingNumberComplete;
|
||||
innerPercent += p / 100;
|
||||
innerPercent /= numItems;
|
||||
progress.Report(innerPercent * 50 + 50);
|
||||
});
|
||||
|
||||
await GetAllItems(user, channelId, folder, currentRefreshLevel + 1, maxRefreshLevel, innerProgress, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error getting channel content", ex);
|
||||
}
|
||||
|
||||
numComplete++;
|
||||
double percent = numComplete;
|
||||
percent /= numItems;
|
||||
progress.Report(percent * 50 + 50);
|
||||
}
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using MediaBrowser.Controller.Channels;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.Tasks;
|
||||
|
||||
namespace Emby.Server.Implementations.Channels
|
||||
{
|
||||
class RefreshChannelsScheduledTask : IScheduledTask
|
||||
{
|
||||
private readonly IChannelManager _channelManager;
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
|
||||
public RefreshChannelsScheduledTask(IChannelManager channelManager, IUserManager userManager, ILogger logger, ILibraryManager libraryManager)
|
||||
{
|
||||
_channelManager = channelManager;
|
||||
_userManager = userManager;
|
||||
_logger = logger;
|
||||
_libraryManager = libraryManager;
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Refresh Channels"; }
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get { return "Refreshes internet channel information."; }
|
||||
}
|
||||
|
||||
public string Category
|
||||
{
|
||||
get { return "Internet Channels"; }
|
||||
}
|
||||
|
||||
public async Task Execute(System.Threading.CancellationToken cancellationToken, IProgress<double> progress)
|
||||
{
|
||||
var manager = (ChannelManager)_channelManager;
|
||||
|
||||
await manager.RefreshChannels(new Progress<double>(), cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await new ChannelPostScanTask(_channelManager, _userManager, _logger, _libraryManager).Run(progress, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <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(24).Ticks}
|
||||
};
|
||||
}
|
||||
|
||||
public string Key
|
||||
{
|
||||
get { return "RefreshInternetChannels"; }
|
||||
}
|
||||
|
||||
public bool IsHidden
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{D08B8079-08B3-48F2-83C4-E9CCCE48AFF1}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Emby.Server.Implementations</RootNamespace>
|
||||
<AssemblyName>Emby.Server.Implementations</AssemblyName>
|
||||
<DefaultLanguage>en-US</DefaultLanguage>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<TargetFrameworkProfile>Profile75</TargetFrameworkProfile>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<!-- A reference to the entire .NET Framework is automatically included -->
|
||||
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
|
||||
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
|
||||
<Name>MediaBrowser.Common</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
|
||||
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
|
||||
<Name>MediaBrowser.Controller</Name>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
|
||||
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
|
||||
<Name>MediaBrowser.Model</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="..\SharedVersion.cs">
|
||||
<Link>Properties\SharedVersion.cs</Link>
|
||||
</Compile>
|
||||
<Compile Include="Channels\ChannelConfigurations.cs" />
|
||||
<Compile Include="Channels\ChannelDynamicMediaSourceProvider.cs" />
|
||||
<Compile Include="Channels\ChannelImageProvider.cs" />
|
||||
<Compile Include="Channels\ChannelManager.cs" />
|
||||
<Compile Include="Channels\ChannelPostScanTask.cs" />
|
||||
<Compile Include="Channels\RefreshChannelsScheduledTask.cs" />
|
||||
<Compile Include="Intros\DefaultIntroProvider.cs" />
|
||||
<Compile Include="News\NewsService.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="Updates\InstallationManager.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
384
Emby.Server.Implementations/Intros/DefaultIntroProvider.cs
Normal file
384
Emby.Server.Implementations/Intros/DefaultIntroProvider.cs
Normal file
@@ -0,0 +1,384 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Security;
|
||||
using MediaBrowser.Controller.Entities;
|
||||
using MediaBrowser.Controller.Entities.Movies;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using MediaBrowser.Controller.Library;
|
||||
using MediaBrowser.Model.Configuration;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Extensions;
|
||||
using MediaBrowser.Model.Globalization;
|
||||
|
||||
namespace Emby.Server.Implementations.Intros
|
||||
{
|
||||
public class DefaultIntroProvider : IIntroProvider
|
||||
{
|
||||
private readonly ISecurityManager _security;
|
||||
private readonly ILocalizationManager _localization;
|
||||
private readonly IConfigurationManager _serverConfig;
|
||||
private readonly ILibraryManager _libraryManager;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
private readonly IMediaSourceManager _mediaSourceManager;
|
||||
|
||||
public DefaultIntroProvider(ISecurityManager security, ILocalizationManager localization, IConfigurationManager serverConfig, ILibraryManager libraryManager, IFileSystem fileSystem, IMediaSourceManager mediaSourceManager)
|
||||
{
|
||||
_security = security;
|
||||
_localization = localization;
|
||||
_serverConfig = serverConfig;
|
||||
_libraryManager = libraryManager;
|
||||
_fileSystem = fileSystem;
|
||||
_mediaSourceManager = mediaSourceManager;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<IntroInfo>> GetIntros(BaseItem item, User user)
|
||||
{
|
||||
var config = GetOptions();
|
||||
|
||||
if (item is Movie)
|
||||
{
|
||||
if (!config.EnableIntrosForMovies)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
}
|
||||
else if (item is Episode)
|
||||
{
|
||||
if (!config.EnableIntrosForEpisodes)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
var ratingLevel = string.IsNullOrWhiteSpace(item.OfficialRating)
|
||||
? null
|
||||
: _localization.GetRatingLevel(item.OfficialRating);
|
||||
|
||||
var candidates = new List<ItemWithTrailer>();
|
||||
|
||||
var trailerTypes = new List<TrailerType>();
|
||||
var sourceTypes = new List<SourceType>();
|
||||
|
||||
if (config.EnableIntrosFromMoviesInLibrary)
|
||||
{
|
||||
trailerTypes.Add(TrailerType.LocalTrailer);
|
||||
sourceTypes.Add(SourceType.Library);
|
||||
}
|
||||
|
||||
if (IsSupporter)
|
||||
{
|
||||
if (config.EnableIntrosFromUpcomingTrailers)
|
||||
{
|
||||
trailerTypes.Add(TrailerType.ComingSoonToTheaters);
|
||||
sourceTypes.Clear();
|
||||
}
|
||||
if (config.EnableIntrosFromUpcomingDvdMovies)
|
||||
{
|
||||
trailerTypes.Add(TrailerType.ComingSoonToDvd);
|
||||
sourceTypes.Clear();
|
||||
}
|
||||
if (config.EnableIntrosFromUpcomingStreamingMovies)
|
||||
{
|
||||
trailerTypes.Add(TrailerType.ComingSoonToStreaming);
|
||||
sourceTypes.Clear();
|
||||
}
|
||||
if (config.EnableIntrosFromSimilarMovies)
|
||||
{
|
||||
trailerTypes.Add(TrailerType.Archive);
|
||||
sourceTypes.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (trailerTypes.Count > 0)
|
||||
{
|
||||
var trailerResult = _libraryManager.GetItemList(new InternalItemsQuery
|
||||
{
|
||||
IncludeItemTypes = new[] { typeof(Trailer).Name },
|
||||
TrailerTypes = trailerTypes.ToArray(),
|
||||
SimilarTo = item,
|
||||
IsPlayed = config.EnableIntrosForWatchedContent ? (bool?)null : false,
|
||||
MaxParentalRating = config.EnableIntrosParentalControl ? ratingLevel : null,
|
||||
BlockUnratedItems = config.EnableIntrosParentalControl ? new[] { UnratedItem.Trailer } : new UnratedItem[] { },
|
||||
|
||||
// Account for duplicates by imdb id, since the database doesn't support this yet
|
||||
Limit = config.TrailerLimit * 2,
|
||||
SourceTypes = sourceTypes.ToArray()
|
||||
|
||||
}).Where(i => string.IsNullOrWhiteSpace(i.GetProviderId(MetadataProviders.Imdb)) || !string.Equals(i.GetProviderId(MetadataProviders.Imdb), item.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).Take(config.TrailerLimit);
|
||||
|
||||
candidates.AddRange(trailerResult.Select(i => new ItemWithTrailer
|
||||
{
|
||||
Item = i,
|
||||
Type = i.SourceType == SourceType.Channel ? ItemWithTrailerType.ChannelTrailer : ItemWithTrailerType.ItemWithTrailer,
|
||||
LibraryManager = _libraryManager
|
||||
}));
|
||||
}
|
||||
|
||||
return GetResult(item, candidates, config);
|
||||
}
|
||||
|
||||
private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config)
|
||||
{
|
||||
var customIntros = !string.IsNullOrWhiteSpace(config.CustomIntroPath) ?
|
||||
GetCustomIntros(config) :
|
||||
new List<IntroInfo>();
|
||||
|
||||
var mediaInfoIntros = !string.IsNullOrWhiteSpace(config.MediaInfoIntroPath) ?
|
||||
GetMediaInfoIntros(config, item) :
|
||||
new List<IntroInfo>();
|
||||
|
||||
// Avoid implicitly captured closure
|
||||
return candidates.Select(i => i.IntroInfo)
|
||||
.Concat(customIntros.Take(1))
|
||||
.Concat(mediaInfoIntros);
|
||||
}
|
||||
|
||||
private CinemaModeConfiguration GetOptions()
|
||||
{
|
||||
return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode");
|
||||
}
|
||||
|
||||
private List<IntroInfo> GetCustomIntros(CinemaModeConfiguration options)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetCustomIntroFiles(options, true, false)
|
||||
.OrderBy(i => Guid.NewGuid())
|
||||
.Select(i => new IntroInfo
|
||||
{
|
||||
Path = i
|
||||
|
||||
}).ToList();
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IntroInfo> GetMediaInfoIntros(CinemaModeConfiguration options, BaseItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
var hasMediaSources = item as IHasMediaSources;
|
||||
|
||||
if (hasMediaSources == null)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
var mediaSource = _mediaSourceManager.GetStaticMediaSources(hasMediaSources, false)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (mediaSource == null)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
var videoStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
|
||||
var audioStream = mediaSource.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
|
||||
|
||||
var allIntros = GetCustomIntroFiles(options, false, true)
|
||||
.OrderBy(i => Guid.NewGuid())
|
||||
.Select(i => new IntroInfo
|
||||
{
|
||||
Path = i
|
||||
|
||||
}).ToList();
|
||||
|
||||
var returnResult = new List<IntroInfo>();
|
||||
|
||||
if (videoStream != null)
|
||||
{
|
||||
returnResult.AddRange(GetMediaInfoIntrosByVideoStream(allIntros, videoStream).Take(1));
|
||||
}
|
||||
|
||||
if (audioStream != null)
|
||||
{
|
||||
returnResult.AddRange(GetMediaInfoIntrosByAudioStream(allIntros, audioStream).Take(1));
|
||||
}
|
||||
|
||||
returnResult.AddRange(GetMediaInfoIntrosByTags(allIntros, item.Tags).Take(1));
|
||||
|
||||
return returnResult.DistinctBy(i => i.Path, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IntroInfo> GetMediaInfoIntrosByVideoStream(List<IntroInfo> allIntros, MediaStream stream)
|
||||
{
|
||||
var codec = stream.Codec;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(codec))
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
return allIntros
|
||||
.Where(i => IsMatch(i.Path, codec))
|
||||
.OrderBy(i => Guid.NewGuid());
|
||||
}
|
||||
|
||||
private IEnumerable<IntroInfo> GetMediaInfoIntrosByAudioStream(List<IntroInfo> allIntros, MediaStream stream)
|
||||
{
|
||||
var codec = stream.Codec;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(codec))
|
||||
{
|
||||
return new List<IntroInfo>();
|
||||
}
|
||||
|
||||
return allIntros
|
||||
.Where(i => IsAudioMatch(i.Path, stream))
|
||||
.OrderBy(i => Guid.NewGuid());
|
||||
}
|
||||
|
||||
private IEnumerable<IntroInfo> GetMediaInfoIntrosByTags(List<IntroInfo> allIntros, List<string> tags)
|
||||
{
|
||||
return allIntros
|
||||
.Where(i => tags.Any(t => IsMatch(i.Path, t)))
|
||||
.OrderBy(i => Guid.NewGuid());
|
||||
}
|
||||
|
||||
private bool IsMatch(string file, string attribute)
|
||||
{
|
||||
var filename = Path.GetFileNameWithoutExtension(file) ?? string.Empty;
|
||||
filename = Normalize(filename);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
attribute = Normalize(attribute);
|
||||
if (string.IsNullOrWhiteSpace(attribute))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(filename, attribute, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private string Normalize(string value)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
private bool IsAudioMatch(string path, MediaStream stream)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(stream.Codec))
|
||||
{
|
||||
if (IsMatch(path, stream.Codec))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(stream.Profile))
|
||||
{
|
||||
if (IsMatch(path, stream.Profile))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetCustomIntroFiles(CinemaModeConfiguration options, bool enableCustomIntros, bool enableMediaInfoIntros)
|
||||
{
|
||||
var list = new List<string>();
|
||||
|
||||
if (enableCustomIntros && !string.IsNullOrWhiteSpace(options.CustomIntroPath))
|
||||
{
|
||||
list.AddRange(_fileSystem.GetFilePaths(options.CustomIntroPath, true)
|
||||
.Where(_libraryManager.IsVideoFile));
|
||||
}
|
||||
|
||||
if (enableMediaInfoIntros && !string.IsNullOrWhiteSpace(options.MediaInfoIntroPath))
|
||||
{
|
||||
list.AddRange(_fileSystem.GetFilePaths(options.MediaInfoIntroPath, true)
|
||||
.Where(_libraryManager.IsVideoFile));
|
||||
}
|
||||
|
||||
return list.Distinct(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetAllIntroFiles()
|
||||
{
|
||||
return GetCustomIntroFiles(GetOptions(), true, true);
|
||||
}
|
||||
|
||||
private bool IsSupporter
|
||||
{
|
||||
get { return _security.IsMBSupporter; }
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return "Default"; }
|
||||
}
|
||||
|
||||
internal class ItemWithTrailer
|
||||
{
|
||||
internal BaseItem Item;
|
||||
internal ItemWithTrailerType Type;
|
||||
internal ILibraryManager LibraryManager;
|
||||
|
||||
public IntroInfo IntroInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
var id = Item.Id;
|
||||
|
||||
if (Type == ItemWithTrailerType.ItemWithTrailer)
|
||||
{
|
||||
var hasTrailers = Item as IHasTrailers;
|
||||
|
||||
if (hasTrailers != null)
|
||||
{
|
||||
id = hasTrailers.LocalTrailerIds.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
return new IntroInfo
|
||||
{
|
||||
ItemId = id
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal enum ItemWithTrailerType
|
||||
{
|
||||
ChannelTrailer,
|
||||
ItemWithTrailer
|
||||
}
|
||||
}
|
||||
|
||||
public class CinemaModeConfigurationFactory : IConfigurationFactory
|
||||
{
|
||||
public IEnumerable<ConfigurationStore> GetConfigurations()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ConfigurationStore
|
||||
{
|
||||
ConfigurationType = typeof(CinemaModeConfiguration),
|
||||
Key = "cinemamode"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
77
Emby.Server.Implementations/News/NewsService.cs
Normal file
77
Emby.Server.Implementations/News/NewsService.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Model.News;
|
||||
using MediaBrowser.Model.Querying;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Emby.Server.Implementations.News
|
||||
{
|
||||
public class NewsService : INewsService
|
||||
{
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IJsonSerializer _json;
|
||||
|
||||
public NewsService(IApplicationPaths appPaths, IJsonSerializer json)
|
||||
{
|
||||
_appPaths = appPaths;
|
||||
_json = json;
|
||||
}
|
||||
|
||||
public QueryResult<NewsItem> GetProductNews(NewsQuery query)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GetProductNewsInternal(query);
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// No biggie
|
||||
return new QueryResult<NewsItem>
|
||||
{
|
||||
Items = new NewsItem[] { }
|
||||
};
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
// No biggie
|
||||
return new QueryResult<NewsItem>
|
||||
{
|
||||
Items = new NewsItem[] { }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private QueryResult<NewsItem> GetProductNewsInternal(NewsQuery query)
|
||||
{
|
||||
var path = Path.Combine(_appPaths.CachePath, "news.json");
|
||||
|
||||
var items = GetNewsItems(path).OrderByDescending(i => i.Date);
|
||||
|
||||
var itemsArray = items.ToArray();
|
||||
var count = itemsArray.Length;
|
||||
|
||||
if (query.StartIndex.HasValue)
|
||||
{
|
||||
itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray();
|
||||
}
|
||||
|
||||
if (query.Limit.HasValue)
|
||||
{
|
||||
itemsArray = itemsArray.Take(query.Limit.Value).ToArray();
|
||||
}
|
||||
|
||||
return new QueryResult<NewsItem>
|
||||
{
|
||||
Items = itemsArray,
|
||||
TotalRecordCount = count
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<NewsItem> GetNewsItems(string path)
|
||||
{
|
||||
return _json.DeserializeFromFile<List<NewsItem>>(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Emby.Server.Implementations/Properties/AssemblyInfo.cs
Normal file
28
Emby.Server.Implementations/Properties/AssemblyInfo.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System.Resources;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Emby.Server.Implementations")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Emby.Server.Implementations")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: NeutralResourcesLanguage("en")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
687
Emby.Server.Implementations/Updates/InstallationManager.cs
Normal file
687
Emby.Server.Implementations/Updates/InstallationManager.cs
Normal file
@@ -0,0 +1,687 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediaBrowser.Common;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Common.Events;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Common.Plugins;
|
||||
using MediaBrowser.Common.Progress;
|
||||
using MediaBrowser.Common.Security;
|
||||
using MediaBrowser.Common.Updates;
|
||||
using MediaBrowser.Model.Cryptography;
|
||||
using MediaBrowser.Model.Events;
|
||||
using MediaBrowser.Model.IO;
|
||||
using MediaBrowser.Model.Logging;
|
||||
using MediaBrowser.Model.Serialization;
|
||||
using MediaBrowser.Model.Updates;
|
||||
|
||||
namespace Emby.Server.Implementations.Updates
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages all install, uninstall and update operations (both plugins and system)
|
||||
/// </summary>
|
||||
public class InstallationManager : IInstallationManager
|
||||
{
|
||||
public event EventHandler<InstallationEventArgs> PackageInstalling;
|
||||
public event EventHandler<InstallationEventArgs> PackageInstallationCompleted;
|
||||
public event EventHandler<InstallationFailedEventArgs> PackageInstallationFailed;
|
||||
public event EventHandler<InstallationEventArgs> PackageInstallationCancelled;
|
||||
|
||||
/// <summary>
|
||||
/// The current installations
|
||||
/// </summary>
|
||||
public List<Tuple<InstallationInfo, CancellationTokenSource>> CurrentInstallations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The completed installations
|
||||
/// </summary>
|
||||
public ConcurrentBag<InstallationInfo> CompletedInstallations { get; set; }
|
||||
|
||||
#region PluginUninstalled Event
|
||||
/// <summary>
|
||||
/// Occurs when [plugin uninstalled].
|
||||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<IPlugin>> PluginUninstalled;
|
||||
|
||||
/// <summary>
|
||||
/// Called when [plugin uninstalled].
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin.</param>
|
||||
private void OnPluginUninstalled(IPlugin plugin)
|
||||
{
|
||||
EventHelper.FireEventIfNotNull(PluginUninstalled, this, new GenericEventArgs<IPlugin> { Argument = plugin }, _logger);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PluginUpdated Event
|
||||
/// <summary>
|
||||
/// Occurs when [plugin updated].
|
||||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>>> PluginUpdated;
|
||||
/// <summary>
|
||||
/// Called when [plugin updated].
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin.</param>
|
||||
/// <param name="newVersion">The new version.</param>
|
||||
private void OnPluginUpdated(IPlugin plugin, PackageVersionInfo newVersion)
|
||||
{
|
||||
_logger.Info("Plugin updated: {0} {1} {2}", newVersion.name, newVersion.versionStr ?? string.Empty, newVersion.classification);
|
||||
|
||||
EventHelper.FireEventIfNotNull(PluginUpdated, this, new GenericEventArgs<Tuple<IPlugin, PackageVersionInfo>> { Argument = new Tuple<IPlugin, PackageVersionInfo>(plugin, newVersion) }, _logger);
|
||||
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region PluginInstalled Event
|
||||
/// <summary>
|
||||
/// Occurs when [plugin updated].
|
||||
/// </summary>
|
||||
public event EventHandler<GenericEventArgs<PackageVersionInfo>> PluginInstalled;
|
||||
/// <summary>
|
||||
/// Called when [plugin installed].
|
||||
/// </summary>
|
||||
/// <param name="package">The package.</param>
|
||||
private void OnPluginInstalled(PackageVersionInfo package)
|
||||
{
|
||||
_logger.Info("New plugin installed: {0} {1} {2}", package.name, package.versionStr ?? string.Empty, package.classification);
|
||||
|
||||
EventHelper.FireEventIfNotNull(PluginInstalled, this, new GenericEventArgs<PackageVersionInfo> { Argument = package }, _logger);
|
||||
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// The _logger
|
||||
/// </summary>
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly IApplicationPaths _appPaths;
|
||||
private readonly IHttpClient _httpClient;
|
||||
private readonly IJsonSerializer _jsonSerializer;
|
||||
private readonly ISecurityManager _securityManager;
|
||||
private readonly IConfigurationManager _config;
|
||||
private readonly IFileSystem _fileSystem;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the application host.
|
||||
/// </summary>
|
||||
/// <value>The application host.</value>
|
||||
private readonly IApplicationHost _applicationHost;
|
||||
|
||||
private readonly ICryptographyProvider _cryptographyProvider;
|
||||
|
||||
public InstallationManager(ILogger logger, IApplicationHost appHost, IApplicationPaths appPaths, IHttpClient httpClient, IJsonSerializer jsonSerializer, ISecurityManager securityManager, IConfigurationManager config, IFileSystem fileSystem, ICryptographyProvider cryptographyProvider)
|
||||
{
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException("logger");
|
||||
}
|
||||
|
||||
CurrentInstallations = new List<Tuple<InstallationInfo, CancellationTokenSource>>();
|
||||
CompletedInstallations = new ConcurrentBag<InstallationInfo>();
|
||||
|
||||
_applicationHost = appHost;
|
||||
_appPaths = appPaths;
|
||||
_httpClient = httpClient;
|
||||
_jsonSerializer = jsonSerializer;
|
||||
_securityManager = securityManager;
|
||||
_config = config;
|
||||
_fileSystem = fileSystem;
|
||||
_cryptographyProvider = cryptographyProvider;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private Version GetPackageVersion(PackageVersionInfo version)
|
||||
{
|
||||
return new Version(ValueOrDefault(version.versionStr, "0.0.0.1"));
|
||||
}
|
||||
|
||||
private static string ValueOrDefault(string str, string def)
|
||||
{
|
||||
return string.IsNullOrEmpty(str) ? def : str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available packages.
|
||||
/// </summary>
|
||||
/// <returns>Task{List{PackageInfo}}.</returns>
|
||||
public async Task<IEnumerable<PackageInfo>> GetAvailablePackages(CancellationToken cancellationToken,
|
||||
bool withRegistration = true,
|
||||
string packageType = null,
|
||||
Version applicationVersion = null)
|
||||
{
|
||||
var data = new Dictionary<string, string>
|
||||
{
|
||||
{ "key", _securityManager.SupporterKey },
|
||||
{ "mac", _applicationHost.SystemId },
|
||||
{ "systemid", _applicationHost.SystemId }
|
||||
};
|
||||
|
||||
if (withRegistration)
|
||||
{
|
||||
using (var json = await _httpClient.Post("https://www.mb3admin.com/admin/service/package/retrieveall", data, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(json).ToList();
|
||||
|
||||
return FilterPackages(packages, packageType, applicationVersion);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return FilterPackages(packages.ToList(), packageType, applicationVersion);
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime _lastPackageUpdateTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available packages.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{List{PackageInfo}}.</returns>
|
||||
public async Task<IEnumerable<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.Info("Opening {0}", PackageCachePath);
|
||||
try
|
||||
{
|
||||
using (var stream = _fileSystem.OpenRead(PackageCachePath))
|
||||
{
|
||||
var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(stream).ToList();
|
||||
|
||||
if (DateTime.UtcNow - _lastPackageUpdateTime > GetCacheLength())
|
||||
{
|
||||
UpdateCachedPackages(CancellationToken.None, false);
|
||||
}
|
||||
|
||||
return packages;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
_lastPackageUpdateTime = DateTime.MinValue;
|
||||
await UpdateCachedPackages(cancellationToken, true).ConfigureAwait(false);
|
||||
using (var stream = _fileSystem.OpenRead(PackageCachePath))
|
||||
{
|
||||
return _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(stream).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private string PackageCachePath
|
||||
{
|
||||
get { return Path.Combine(_appPaths.CachePath, "serverpackages.json"); }
|
||||
}
|
||||
|
||||
private readonly SemaphoreSlim _updateSemaphore = new SemaphoreSlim(1, 1);
|
||||
private async Task UpdateCachedPackages(CancellationToken cancellationToken, bool throwErrors)
|
||||
{
|
||||
await _updateSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
if (DateTime.UtcNow - _lastPackageUpdateTime < GetCacheLength())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
||||
{
|
||||
Url = "https://www.mb3admin.com/admin/service/MB3Packages.json",
|
||||
CancellationToken = cancellationToken,
|
||||
Progress = new Progress<Double>()
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(PackageCachePath));
|
||||
|
||||
_fileSystem.CopyFile(tempFile, PackageCachePath, true);
|
||||
_lastPackageUpdateTime = DateTime.UtcNow;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Error updating package cache", ex);
|
||||
|
||||
if (throwErrors)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updateSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private TimeSpan GetCacheLength()
|
||||
{
|
||||
switch (_config.CommonConfiguration.SystemUpdateLevel)
|
||||
{
|
||||
case PackageVersionClass.Beta:
|
||||
return TimeSpan.FromMinutes(30);
|
||||
case PackageVersionClass.Dev:
|
||||
return TimeSpan.FromMinutes(3);
|
||||
default:
|
||||
return TimeSpan.FromHours(24);
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerable<PackageInfo> FilterPackages(List<PackageInfo> packages)
|
||||
{
|
||||
foreach (var package in packages)
|
||||
{
|
||||
package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
|
||||
.OrderByDescending(GetPackageVersion).ToList();
|
||||
}
|
||||
|
||||
// Remove packages with no versions
|
||||
packages = packages.Where(p => p.versions.Any()).ToList();
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
protected IEnumerable<PackageInfo> FilterPackages(List<PackageInfo> packages, string packageType, Version applicationVersion)
|
||||
{
|
||||
foreach (var package in packages)
|
||||
{
|
||||
package.versions = package.versions.Where(v => !string.IsNullOrWhiteSpace(v.sourceUrl))
|
||||
.OrderByDescending(GetPackageVersion).ToList();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(packageType))
|
||||
{
|
||||
packages = packages.Where(p => string.Equals(p.type, packageType, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
}
|
||||
|
||||
// If an app version was supplied, filter the versions for each package to only include supported versions
|
||||
if (applicationVersion != null)
|
||||
{
|
||||
foreach (var package in packages)
|
||||
{
|
||||
package.versions = package.versions.Where(v => IsPackageVersionUpToDate(v, applicationVersion)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
// Remove packages with no versions
|
||||
packages = packages.Where(p => p.versions.Any()).ToList();
|
||||
|
||||
return packages;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether [is package version up to date] [the specified package version info].
|
||||
/// </summary>
|
||||
/// <param name="packageVersionInfo">The package version info.</param>
|
||||
/// <param name="currentServerVersion">The current server version.</param>
|
||||
/// <returns><c>true</c> if [is package version up to date] [the specified package version info]; otherwise, <c>false</c>.</returns>
|
||||
private bool IsPackageVersionUpToDate(PackageVersionInfo packageVersionInfo, Version currentServerVersion)
|
||||
{
|
||||
if (string.IsNullOrEmpty(packageVersionInfo.requiredVersionStr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Version requiredVersion;
|
||||
|
||||
return Version.TryParse(packageVersionInfo.requiredVersionStr, out requiredVersion) && currentServerVersion >= requiredVersion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the package.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="guid">The assembly guid</param>
|
||||
/// <param name="classification">The classification.</param>
|
||||
/// <param name="version">The version.</param>
|
||||
/// <returns>Task{PackageVersionInfo}.</returns>
|
||||
public async Task<PackageVersionInfo> GetPackage(string name, string guid, PackageVersionClass classification, Version version)
|
||||
{
|
||||
var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
var package = packages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
|
||||
?? packages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (package == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return package.versions.FirstOrDefault(v => GetPackageVersion(v).Equals(version) && v.classification == classification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest compatible version.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="guid">The assembly guid if this is a plug-in</param>
|
||||
/// <param name="currentServerVersion">The current server version.</param>
|
||||
/// <param name="classification">The classification.</param>
|
||||
/// <returns>Task{PackageVersionInfo}.</returns>
|
||||
public async Task<PackageVersionInfo> GetLatestCompatibleVersion(string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
|
||||
{
|
||||
var packages = await GetAvailablePackages(CancellationToken.None).ConfigureAwait(false);
|
||||
|
||||
return GetLatestCompatibleVersion(packages, name, guid, currentServerVersion, classification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the latest compatible version.
|
||||
/// </summary>
|
||||
/// <param name="availablePackages">The available packages.</param>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="currentServerVersion">The current server version.</param>
|
||||
/// <param name="classification">The classification.</param>
|
||||
/// <returns>PackageVersionInfo.</returns>
|
||||
public PackageVersionInfo GetLatestCompatibleVersion(IEnumerable<PackageInfo> availablePackages, string name, string guid, Version currentServerVersion, PackageVersionClass classification = PackageVersionClass.Release)
|
||||
{
|
||||
var package = availablePackages.FirstOrDefault(p => string.Equals(p.guid, guid ?? "none", StringComparison.OrdinalIgnoreCase))
|
||||
?? availablePackages.FirstOrDefault(p => p.name.Equals(name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (package == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return package.versions
|
||||
.OrderByDescending(GetPackageVersion)
|
||||
.FirstOrDefault(v => v.classification <= classification && IsPackageVersionUpToDate(v, currentServerVersion));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available plugin updates.
|
||||
/// </summary>
|
||||
/// <param name="applicationVersion">The current server version.</param>
|
||||
/// <param name="withAutoUpdateEnabled">if set to <c>true</c> [with auto update enabled].</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task{IEnumerable{PackageVersionInfo}}.</returns>
|
||||
public async Task<IEnumerable<PackageVersionInfo>> GetAvailablePluginUpdates(Version applicationVersion, bool withAutoUpdateEnabled, CancellationToken cancellationToken)
|
||||
{
|
||||
var catalog = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var plugins = _applicationHost.Plugins.ToList();
|
||||
|
||||
if (withAutoUpdateEnabled)
|
||||
{
|
||||
plugins = plugins
|
||||
.Where(p => _config.CommonConfiguration.EnableAutoUpdate)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Figure out what needs to be installed
|
||||
var packages = plugins.Select(p =>
|
||||
{
|
||||
var latestPluginInfo = GetLatestCompatibleVersion(catalog, p.Name, p.Id.ToString(), applicationVersion, _config.CommonConfiguration.SystemUpdateLevel);
|
||||
|
||||
return latestPluginInfo != null && GetPackageVersion(latestPluginInfo) > p.Version ? latestPluginInfo : null;
|
||||
|
||||
}).Where(i => i != null).ToList();
|
||||
|
||||
return packages
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p.sourceUrl) && !CompletedInstallations.Any(i => string.Equals(i.AssemblyGuid, p.guid, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs the package.
|
||||
/// </summary>
|
||||
/// <param name="package">The package.</param>
|
||||
/// <param name="isPlugin">if set to <c>true</c> [is plugin].</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
/// <exception cref="System.ArgumentNullException">package</exception>
|
||||
public async Task InstallPackage(PackageVersionInfo package, bool isPlugin, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
if (package == null)
|
||||
{
|
||||
throw new ArgumentNullException("package");
|
||||
}
|
||||
|
||||
if (progress == null)
|
||||
{
|
||||
throw new ArgumentNullException("progress");
|
||||
}
|
||||
|
||||
var installationInfo = new InstallationInfo
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Name = package.name,
|
||||
AssemblyGuid = package.guid,
|
||||
UpdateClass = package.classification,
|
||||
Version = package.versionStr
|
||||
};
|
||||
|
||||
var innerCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var tuple = new Tuple<InstallationInfo, CancellationTokenSource>(installationInfo, innerCancellationTokenSource);
|
||||
|
||||
// Add it to the in-progress list
|
||||
lock (CurrentInstallations)
|
||||
{
|
||||
CurrentInstallations.Add(tuple);
|
||||
}
|
||||
|
||||
var innerProgress = new ActionableProgress<double>();
|
||||
|
||||
// Whenever the progress updates, update the outer progress object and InstallationInfo
|
||||
innerProgress.RegisterAction(percent =>
|
||||
{
|
||||
progress.Report(percent);
|
||||
|
||||
installationInfo.PercentComplete = percent;
|
||||
});
|
||||
|
||||
var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token;
|
||||
|
||||
var installationEventArgs = new InstallationEventArgs
|
||||
{
|
||||
InstallationInfo = installationInfo,
|
||||
PackageVersionInfo = package
|
||||
};
|
||||
|
||||
EventHelper.FireEventIfNotNull(PackageInstalling, this, installationEventArgs, _logger);
|
||||
|
||||
try
|
||||
{
|
||||
await InstallPackageInternal(package, isPlugin, innerProgress, linkedToken).ConfigureAwait(false);
|
||||
|
||||
lock (CurrentInstallations)
|
||||
{
|
||||
CurrentInstallations.Remove(tuple);
|
||||
}
|
||||
|
||||
progress.Report(100);
|
||||
|
||||
CompletedInstallations.Add(installationInfo);
|
||||
|
||||
EventHelper.FireEventIfNotNull(PackageInstallationCompleted, this, installationEventArgs, _logger);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
lock (CurrentInstallations)
|
||||
{
|
||||
CurrentInstallations.Remove(tuple);
|
||||
}
|
||||
|
||||
_logger.Info("Package installation cancelled: {0} {1}", package.name, package.versionStr);
|
||||
|
||||
EventHelper.FireEventIfNotNull(PackageInstallationCancelled, this, installationEventArgs, _logger);
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.ErrorException("Package installation failed", ex);
|
||||
|
||||
lock (CurrentInstallations)
|
||||
{
|
||||
CurrentInstallations.Remove(tuple);
|
||||
}
|
||||
|
||||
EventHelper.FireEventIfNotNull(PackageInstallationFailed, this, new InstallationFailedEventArgs
|
||||
{
|
||||
InstallationInfo = installationInfo,
|
||||
Exception = ex
|
||||
|
||||
}, _logger);
|
||||
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Dispose the progress object and remove the installation from the in-progress list
|
||||
innerProgress.Dispose();
|
||||
tuple.Item2.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs the package internal.
|
||||
/// </summary>
|
||||
/// <param name="package">The package.</param>
|
||||
/// <param name="isPlugin">if set to <c>true</c> [is plugin].</param>
|
||||
/// <param name="progress">The progress.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>Task.</returns>
|
||||
private async Task InstallPackageInternal(PackageVersionInfo package, bool isPlugin, IProgress<double> progress, CancellationToken cancellationToken)
|
||||
{
|
||||
// Do the install
|
||||
await PerformPackageInstallation(progress, package, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Do plugin-specific processing
|
||||
if (isPlugin)
|
||||
{
|
||||
// Set last update time if we were installed before
|
||||
var plugin = _applicationHost.Plugins.FirstOrDefault(p => string.Equals(p.Id.ToString(), package.guid, StringComparison.OrdinalIgnoreCase))
|
||||
?? _applicationHost.Plugins.FirstOrDefault(p => p.Name.Equals(package.name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
if (plugin != null)
|
||||
{
|
||||
OnPluginUpdated(plugin, package);
|
||||
}
|
||||
else
|
||||
{
|
||||
OnPluginInstalled(package);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PerformPackageInstallation(IProgress<double> progress, PackageVersionInfo package, CancellationToken cancellationToken)
|
||||
{
|
||||
// Target based on if it is an archive or single assembly
|
||||
// zip archives are assumed to contain directory structures relative to our ProgramDataPath
|
||||
var extension = Path.GetExtension(package.targetFilename);
|
||||
var isArchive = string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".rar", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".7z", StringComparison.OrdinalIgnoreCase);
|
||||
var target = Path.Combine(isArchive ? _appPaths.TempUpdatePath : _appPaths.PluginsPath, package.targetFilename);
|
||||
|
||||
// Download to temporary file so that, if interrupted, it won't destroy the existing installation
|
||||
var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
|
||||
{
|
||||
Url = package.sourceUrl,
|
||||
CancellationToken = cancellationToken,
|
||||
Progress = progress
|
||||
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Validate with a checksum
|
||||
var packageChecksum = string.IsNullOrWhiteSpace(package.checksum) ? Guid.Empty : new Guid(package.checksum);
|
||||
if (packageChecksum != Guid.Empty) // support for legacy uploads for now
|
||||
{
|
||||
using (var stream = _fileSystem.OpenRead(tempFile))
|
||||
{
|
||||
var check = Guid.Parse(BitConverter.ToString(_cryptographyProvider.GetMD5Bytes(stream)).Replace("-", String.Empty));
|
||||
if (check != packageChecksum)
|
||||
{
|
||||
throw new Exception(string.Format("Download validation failed for {0}. Probably corrupted during transfer.", package.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Success - move it to the real target
|
||||
try
|
||||
{
|
||||
_fileSystem.CreateDirectory(Path.GetDirectoryName(target));
|
||||
_fileSystem.CopyFile(tempFile, target, true);
|
||||
//If it is an archive - write out a version file so we know what it is
|
||||
if (isArchive)
|
||||
{
|
||||
_fileSystem.WriteAllText(target + ".ver", package.versionStr);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
_logger.ErrorException("Error attempting to move file from {0} to {1}", e, tempFile, target);
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_fileSystem.DeleteFile(tempFile);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// Don't fail because of this
|
||||
_logger.ErrorException("Error deleting temp file {0]", e, tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalls a plugin
|
||||
/// </summary>
|
||||
/// <param name="plugin">The plugin.</param>
|
||||
/// <exception cref="System.ArgumentException"></exception>
|
||||
public void UninstallPlugin(IPlugin plugin)
|
||||
{
|
||||
plugin.OnUninstalling();
|
||||
|
||||
// Remove it the quick way for now
|
||||
_applicationHost.RemovePlugin(plugin);
|
||||
|
||||
_fileSystem.DeleteFile(plugin.AssemblyFilePath);
|
||||
|
||||
OnPluginUninstalled(plugin);
|
||||
|
||||
_applicationHost.NotifyPendingRestart();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and - optionally - managed resources.
|
||||
/// </summary>
|
||||
/// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool dispose)
|
||||
{
|
||||
if (dispose)
|
||||
{
|
||||
lock (CurrentInstallations)
|
||||
{
|
||||
foreach (var tuple in CurrentInstallations)
|
||||
{
|
||||
tuple.Item2.Dispose();
|
||||
}
|
||||
|
||||
CurrentInstallations.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user