better caching of remote data

This commit is contained in:
Luke Pulverenti 2016-01-02 16:54:37 -05:00
parent 4bf13f7754
commit 821e824687
13 changed files with 79 additions and 294 deletions

View File

@ -78,12 +78,6 @@ namespace MediaBrowser.Api.System
public string Name { get; set; }
}
[Route("/System/SupporterInfo", "GET")]
[Authenticated]
public class GetSupporterInfo : IReturn<SupporterInfo>
{
}
/// <summary>
/// Class SystemInfoService
/// </summary>
@ -116,13 +110,6 @@ namespace MediaBrowser.Api.System
_security = security;
}
public async Task<object> Get(GetSupporterInfo request)
{
var result = await _security.GetSupporterInfo().ConfigureAwait(false);
return ToOptimizedResult(result);
}
public object Post(PingSystem request)
{
return _appHost.Name;

View File

@ -153,66 +153,6 @@ namespace MediaBrowser.Common.Implementations.Security
}
}
public async Task<SupporterInfo> GetSupporterInfo()
{
var key = SupporterKey;
if (string.IsNullOrWhiteSpace(key))
{
return new SupporterInfo();
}
var data = new Dictionary<string, string>
{
{ "key", key },
};
var url = MbAdmin.HttpsUrl + "/service/supporter/retrieve";
using (var stream = await _httpClient.Post(url, data, CancellationToken.None).ConfigureAwait(false))
{
var response = _jsonSerializer.DeserializeFromStream<SuppporterInfoResponse>(stream);
var info = new SupporterInfo
{
Email = response.email,
PlanType = response.planType,
SupporterKey = response.supporterKey,
IsActiveSupporter = IsMBSupporter
};
if (!string.IsNullOrWhiteSpace(response.expDate))
{
DateTime parsedDate;
if (DateTime.TryParse(response.expDate, out parsedDate))
{
info.ExpirationDate = parsedDate;
}
else
{
_logger.Error("Failed to parse expDate: {0}", response.expDate);
}
}
if (!string.IsNullOrWhiteSpace(response.regDate))
{
DateTime parsedDate;
if (DateTime.TryParse(response.regDate, out parsedDate))
{
info.RegistrationDate = parsedDate;
}
else
{
_logger.Error("Failed to parse regDate: {0}", response.regDate);
}
}
info.IsExpiredSupporter = info.ExpirationDate.HasValue && info.ExpirationDate < DateTime.UtcNow && !string.IsNullOrWhiteSpace(info.SupporterKey);
return info;
}
}
/// <summary>
/// Register an app store sale with our back-end. It will validate the transaction with the store
/// and then register the proper feature and then fill in the supporter key on success.

View File

@ -185,7 +185,7 @@ namespace MediaBrowser.Common.Implementations.Updates
}
}
private Tuple<List<PackageInfo>, DateTime> _lastPackageListResult;
private DateTime _lastPackageUpdateTime;
/// <summary>
/// Gets all available packages.
@ -194,40 +194,89 @@ namespace MediaBrowser.Common.Implementations.Updates
/// <returns>Task{List{PackageInfo}}.</returns>
public async Task<IEnumerable<PackageInfo>> GetAvailablePackagesWithoutRegistrationInfo(CancellationToken cancellationToken)
{
if (_lastPackageListResult != null)
using (var stream = await GetCachedPackages(cancellationToken).ConfigureAwait(false))
{
TimeSpan cacheLength;
var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(stream).ToList();
switch (_config.CommonConfiguration.SystemUpdateLevel)
if ((DateTime.UtcNow - _lastPackageUpdateTime) > GetCacheLength())
{
case PackageVersionClass.Beta:
cacheLength = TimeSpan.FromMinutes(30);
break;
case PackageVersionClass.Dev:
cacheLength = TimeSpan.FromMinutes(3);
break;
default:
cacheLength = TimeSpan.FromHours(24);
break;
UpdateCachedPackages(CancellationToken.None, false);
}
if ((DateTime.UtcNow - _lastPackageListResult.Item2) < cacheLength)
{
return _lastPackageListResult.Item1;
}
return packages;
}
}
private string PackageCachePath
{
get { return Path.Combine(_appPaths.CachePath, "serverpackages.json"); }
}
private async Task<Stream> GetCachedPackages(CancellationToken cancellationToken)
{
try
{
return _fileSystem.OpenRead(PackageCachePath);
}
catch (Exception)
{
}
using (var json = await _httpClient.Get(MbAdmin.HttpUrl + "service/MB3Packages.json", cancellationToken).ConfigureAwait(false))
await UpdateCachedPackages(cancellationToken, true).ConfigureAwait(false);
return _fileSystem.OpenRead(PackageCachePath);
}
private readonly SemaphoreSlim _updateSemaphore = new SemaphoreSlim(1, 1);
private async Task UpdateCachedPackages(CancellationToken cancellationToken, bool throwErrors)
{
await _updateSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
try
{
cancellationToken.ThrowIfCancellationRequested();
if ((DateTime.UtcNow - _lastPackageUpdateTime) < GetCacheLength())
{
return;
}
var packages = _jsonSerializer.DeserializeFromStream<List<PackageInfo>>(json).ToList();
var tempFile = await _httpClient.GetTempFile(new HttpRequestOptions
{
Url = MbAdmin.HttpUrl + "service/MB3Packages.json",
CancellationToken = cancellationToken,
Progress = new Progress<Double>()
packages = FilterPackages(packages).ToList();
}).ConfigureAwait(false);
_lastPackageListResult = new Tuple<List<PackageInfo>, DateTime>(packages, DateTime.UtcNow);
_fileSystem.CreateDirectory(Path.GetDirectoryName(PackageCachePath));
return _lastPackageListResult.Item1;
_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);
}
}
@ -554,7 +603,7 @@ namespace MediaBrowser.Common.Implementations.Updates
if (packageChecksum != Guid.Empty) // support for legacy uploads for now
{
using (var crypto = new MD5CryptoServiceProvider())
using (var stream = new BufferedStream(_fileSystem.OpenRead(tempFile), 100000))
using (var stream = new BufferedStream(_fileSystem.OpenRead(tempFile), 100000))
{
var check = Guid.Parse(BitConverter.ToString(crypto.ComputeHash(stream)).Replace("-", String.Empty));
if (check != packageChecksum)
@ -569,12 +618,12 @@ namespace MediaBrowser.Common.Implementations.Updates
// Success - move it to the real target
try
{
_fileSystem.CreateDirectory(Path.GetDirectoryName(target));
_fileSystem.CopyFile(tempFile, target, true);
_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)
{
File.WriteAllText(target + ".ver", package.versionStr);
File.WriteAllText(target + ".ver", package.versionStr);
}
}
catch (IOException e)

View File

@ -41,12 +41,6 @@ namespace MediaBrowser.Common.Security
/// <returns></returns>
Task LoadAllRegistrationInfo();
/// <summary>
/// Gets the supporter information.
/// </summary>
/// <returns>Task&lt;SupporterInfo&gt;.</returns>
Task<SupporterInfo> GetSupporterInfo();
/// <summary>
/// Register and app store sale with our back-end
/// </summary>

View File

@ -608,9 +608,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\SortOrder.cs">
<Link>Entities\SortOrder.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Entities\SupporterInfo.cs">
<Link>Entities\SupporterInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Entities\TrailerType.cs">
<Link>Entities\TrailerType.cs</Link>
</Compile>

View File

@ -573,9 +573,6 @@
<Compile Include="..\MediaBrowser.Model\Entities\SortOrder.cs">
<Link>Entities\SortOrder.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Entities\SupporterInfo.cs">
<Link>Entities\SupporterInfo.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.Model\Entities\TrailerType.cs">
<Link>Entities\TrailerType.cs</Link>
</Compile>

View File

@ -1,15 +0,0 @@
using System;
namespace MediaBrowser.Model.Entities
{
public class SupporterInfo
{
public string Email { get; set; }
public string SupporterKey { get; set; }
public DateTime? ExpirationDate { get; set; }
public DateTime RegistrationDate { get; set; }
public string PlanType { get; set; }
public bool IsActiveSupporter { get; set; }
public bool IsExpiredSupporter { get; set; }
}
}

View File

@ -144,7 +144,6 @@
<Compile Include="Dto\MediaSourceType.cs" />
<Compile Include="Configuration\DynamicDayOfWeek.cs" />
<Compile Include="Entities\ExtraType.cs" />
<Compile Include="Entities\SupporterInfo.cs" />
<Compile Include="Entities\TrailerType.cs" />
<Compile Include="Extensions\BoolHelper.cs" />
<Compile Include="Extensions\FloatHelper.cs" />

View File

@ -10,6 +10,7 @@ using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
using MediaBrowser.Common.IO;
@ -40,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Connect
public void Run()
{
LoadCachedAddress();
Task.Run(() => LoadCachedAddress());
_timer = new Timer(TimerCallback, null, TimeSpan.FromSeconds(5), TimeSpan.FromHours(3));
}

View File

@ -1073,11 +1073,6 @@ namespace MediaBrowser.Server.Implementations.Connect
public async Task<ConnectSupporterSummary> GetConnectSupporterSummary()
{
if (!_securityManager.IsMBSupporter)
{
return new ConnectSupporterSummary();
}
var url = GetConnectUrl("keyAssociation");
var options = new HttpRequestOptions
@ -1106,11 +1101,6 @@ namespace MediaBrowser.Server.Implementations.Connect
public async Task AddConnectSupporter(string id)
{
if (!_securityManager.IsMBSupporter)
{
throw new InvalidOperationException();
}
var url = GetConnectUrl("keyAssociation");
var options = new HttpRequestOptions
@ -1139,11 +1129,6 @@ namespace MediaBrowser.Server.Implementations.Connect
public async Task RemoveConnectSupporter(string id)
{
if (!_securityManager.IsMBSupporter)
{
throw new InvalidOperationException();
}
var url = GetConnectUrl("keyAssociation");
var options = new HttpRequestOptions

View File

@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
/// </summary>
public void Run()
{
_timer = new Timer(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(24));
_timer = new Timer(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12));
}
private async Task LoadAllRegistrations()

View File

@ -1,148 +0,0 @@
using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.IO;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Notifications;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Model.Logging;
using MediaBrowser.Model.Notifications;
using MediaBrowser.Model.Serialization;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using CommonIO;
namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
{
public class RemoteNotifications : IServerEntryPoint
{
private const string Url = "http://www.mb3admin.com/admin/service/MB3ServerNotifications.json";
private Timer _timer;
private readonly IHttpClient _httpClient;
private readonly IApplicationPaths _appPaths;
private readonly ILogger _logger;
private readonly IJsonSerializer _json;
private readonly IUserManager _userManager;
private readonly IFileSystem _fileSystem;
private readonly TimeSpan _frequency = TimeSpan.FromHours(6);
private readonly TimeSpan _maxAge = TimeSpan.FromDays(31);
private readonly INotificationManager _notificationManager;
public RemoteNotifications(IApplicationPaths appPaths, ILogger logger, IHttpClient httpClient, IJsonSerializer json, IUserManager userManager, IFileSystem fileSystem, INotificationManager notificationManager)
{
_appPaths = appPaths;
_logger = logger;
_httpClient = httpClient;
_json = json;
_userManager = userManager;
_fileSystem = fileSystem;
_notificationManager = notificationManager;
}
/// <summary>
/// Runs this instance.
/// </summary>
public void Run()
{
_timer = new Timer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency);
}
/// <summary>
/// Called when [timer fired].
/// </summary>
/// <param name="state">The state.</param>
private async void OnTimerFired(object state)
{
var dataPath = Path.Combine(_appPaths.DataPath, "remotenotifications.json");
var lastRunTime = _fileSystem.FileExists(dataPath) ? _fileSystem.GetLastWriteTimeUtc(dataPath) : DateTime.MinValue;
try
{
await DownloadNotifications(dataPath, lastRunTime).ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.ErrorException("Error downloading remote notifications", ex);
}
}
/// <summary>
/// Downloads the notifications.
/// </summary>
/// <param name="dataPath">The data path.</param>
/// <param name="lastRunTime">The last run time.</param>
/// <returns>Task.</returns>
private async Task DownloadNotifications(string dataPath, DateTime lastRunTime)
{
using (var stream = await _httpClient.Get(new HttpRequestOptions
{
Url = Url
}).ConfigureAwait(false))
{
var notifications = _json.DeserializeFromStream<RemoteNotification[]>(stream);
_fileSystem.WriteAllText(dataPath, string.Empty);
await CreateNotifications(notifications, lastRunTime).ConfigureAwait(false);
}
}
/// <summary>
/// Creates the notifications.
/// </summary>
/// <param name="notifications">The notifications.</param>
/// <param name="lastRunTime">The last run time.</param>
/// <returns>Task.</returns>
private async Task CreateNotifications(IEnumerable<RemoteNotification> notifications, DateTime lastRunTime)
{
// Only show notifications that are active, new since last download, and not older than max age
var notificationList = notifications
.Where(i => string.Equals(i.active, "1") && i.date.ToUniversalTime() > lastRunTime && (DateTime.UtcNow - i.date.ToUniversalTime()) <= _maxAge)
.ToList();
var userIds = _userManager.Users.Select(i => i.Id.ToString("N")).ToList();
foreach (var notification in notificationList)
{
await _notificationManager.SendNotification(new NotificationRequest
{
Date = notification.date,
Name = notification.name,
Description = notification.description,
Url = notification.url,
UserIds = userIds
}, CancellationToken.None).ConfigureAwait(false);
}
}
public void Dispose()
{
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
}
private class RemoteNotification
{
public string id { get; set; }
public DateTime date { get; set; }
public string name { get; set; }
public string description { get; set; }
public string category { get; set; }
public string url { get; set; }
public object imageUrl { get; set; }
public string active { get; set; }
}
}
}

View File

@ -131,7 +131,6 @@
<Compile Include="EntryPoints\LibraryChangedNotifier.cs" />
<Compile Include="EntryPoints\LoadRegistrations.cs" />
<Compile Include="EntryPoints\Notifications\Notifications.cs" />
<Compile Include="EntryPoints\Notifications\RemoteNotifications.cs" />
<Compile Include="EntryPoints\Notifications\WebSocketNotifier.cs" />
<Compile Include="EntryPoints\RefreshUsersMetadata.cs" />
<Compile Include="EntryPoints\UsageEntryPoint.cs" />