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.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using CommonIO; using Interfaces.IO; using MediaBrowser.Common.IO; using MediaBrowser.Model.IO; namespace MediaBrowser.Server.Implementations.Sync { public class TargetDataProvider : ISyncDataProvider { private readonly SyncTarget _target; private readonly IServerSyncProvider _provider; private readonly SemaphoreSlim _dataLock = new SemaphoreSlim(1, 1); private List _items; private readonly ILogger _logger; private readonly IJsonSerializer _json; private readonly IFileSystem _fileSystem; private readonly IApplicationPaths _appPaths; private readonly IServerApplicationHost _appHost; private readonly IMemoryStreamProvider _memoryStreamProvider; public TargetDataProvider(IServerSyncProvider provider, SyncTarget target, IServerApplicationHost appHost, ILogger logger, IJsonSerializer json, IFileSystem fileSystem, IApplicationPaths appPaths, IMemoryStreamProvider memoryStreamProvider) { _logger = logger; _json = json; _provider = provider; _target = target; _fileSystem = fileSystem; _appPaths = appPaths; _memoryStreamProvider = memoryStreamProvider; _appHost = appHost; } private string[] GetRemotePath() { var parts = new List { _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> RetrieveItems(CancellationToken cancellationToken) { _logger.Debug("Getting {0} from {1}", string.Join(MediaSync.PathSeparatorString, GetRemotePath().ToArray()), _provider.Name); var fileResult = await _provider.GetFiles(new FileQuery { FullPath = GetRemotePath().ToArray() }, _target, cancellationToken).ConfigureAwait(false); if (fileResult.Items.Length > 0) { using (var stream = await _provider.GetFile(fileResult.Items[0].Id, _target, new Progress(), cancellationToken)) { return _json.DeserializeFromStream>(stream); } } return new List(); } private async Task EnsureData(CancellationToken cancellationToken) { if (_items == null) { _items = await RetrieveItems(cancellationToken).ConfigureAwait(false); } } private async Task SaveData(List items, CancellationToken cancellationToken) { using (var stream = _memoryStreamProvider.CreateNew()) { _json.SerializeToStream(items, stream); // Save to sync provider stream.Position = 0; var remotePath = GetRemotePath(); _logger.Debug("Saving data.json to {0}. Remote path: {1}", _provider.Name, string.Join("/", remotePath)); await _provider.SendFile(stream, remotePath, _target, new Progress(), cancellationToken).ConfigureAwait(false); } } private async Task GetData(bool enableCache, Func, 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> 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 list) { await _dataLock.WaitAsync().ConfigureAwait(false); try { _items = list; } finally { _dataLock.Release(); } } public Task> 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 Get(SyncTarget target, string id) { return GetData(true, items => items.FirstOrDefault(i => string.Equals(i.Id, id, StringComparison.OrdinalIgnoreCase))); } public Task> 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> 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()); } } }