From ea9e8b957cdf5bb335967eeb1a018c4fc2a1db53 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 11 Dec 2014 01:20:28 -0500 Subject: update sync objects --- .../Sync/SyncRepository.cs | 115 ++++++++++++++++++--- 1 file changed, 99 insertions(+), 16 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Sync/SyncRepository.cs') diff --git a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs index 65da74f9e..338529043 100644 --- a/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs +++ b/MediaBrowser.Server.Implementations/Sync/SyncRepository.cs @@ -23,6 +23,7 @@ namespace MediaBrowser.Server.Implementations.Sync private readonly IServerApplicationPaths _appPaths; private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + private IDbCommand _deleteJobCommand; private IDbCommand _saveJobCommand; private IDbCommand _saveJobItemCommand; @@ -34,13 +35,13 @@ namespace MediaBrowser.Server.Implementations.Sync public async Task Initialize() { - var dbFile = Path.Combine(_appPaths.DataPath, "sync.db"); + var dbFile = Path.Combine(_appPaths.DataPath, "sync2.db"); _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false); string[] queries = { - "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Quality TEXT NOT NULL, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, UnwatchedOnly BIT, SyncLimit BigInt, LimitType TEXT, IsDynamic BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", + "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Quality TEXT NOT NULL, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, UnwatchedOnly BIT, ItemLimit INT, RemoveWhenWatched BIT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)", "create index if not exists idx_SyncJobs on SyncJobs(Id)", "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, JobId TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT)", @@ -59,8 +60,12 @@ namespace MediaBrowser.Server.Implementations.Sync private void PrepareStatements() { + _deleteJobCommand = _connection.CreateCommand(); + _deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id; delete from SyncJobItems where JobId=@Id"; + _deleteJobCommand.Parameters.Add(_deleteJobCommand, "@Id"); + _saveJobCommand = _connection.CreateCommand(); - _saveJobCommand.CommandText = "replace into SyncJobs (Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, UnwatchedOnly, SyncLimit, LimitType, IsDynamic, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Quality, @Status, @Progress, @UserId, @ItemIds, @UnwatchedOnly, @SyncLimit, @LimitType, @IsDynamic, @DateCreated, @DateLastModified, @ItemCount)"; + _saveJobCommand.CommandText = "replace into SyncJobs (Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, UnwatchedOnly, ItemLimit, RemoveWhenWatched, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Quality, @Status, @Progress, @UserId, @ItemIds, @UnwatchedOnly, @ItemLimit, @RemoveWhenWatched, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)"; _saveJobCommand.Parameters.Add(_saveJobCommand, "@Id"); _saveJobCommand.Parameters.Add(_saveJobCommand, "@TargetId"); @@ -71,9 +76,9 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobCommand.Parameters.Add(_saveJobCommand, "@UserId"); _saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemIds"); _saveJobCommand.Parameters.Add(_saveJobCommand, "@UnwatchedOnly"); - _saveJobCommand.Parameters.Add(_saveJobCommand, "@SyncLimit"); - _saveJobCommand.Parameters.Add(_saveJobCommand, "@LimitType"); - _saveJobCommand.Parameters.Add(_saveJobCommand, "@IsDynamic"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemLimit"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@RemoveWhenWatched"); + _saveJobCommand.Parameters.Add(_saveJobCommand, "@SyncNewContent"); _saveJobCommand.Parameters.Add(_saveJobCommand, "@DateCreated"); _saveJobCommand.Parameters.Add(_saveJobCommand, "@DateLastModified"); _saveJobCommand.Parameters.Add(_saveJobCommand, "@ItemCount"); @@ -88,7 +93,7 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobItemCommand.Parameters.Add(_saveJobCommand, "@Status"); } - private const string BaseJobSelectText = "select Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, UnwatchedOnly, SyncLimit, LimitType, IsDynamic, DateCreated, DateLastModified, ItemCount from SyncJobs"; + private const string BaseJobSelectText = "select Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, UnwatchedOnly, ItemLimit, RemoveWhenWatched, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs"; private const string BaseJobItemSelectText = "select Id, ItemId, JobId, OutputPath, Status, TargetId from SyncJobItems"; public SyncJob GetJob(string id) @@ -159,15 +164,12 @@ namespace MediaBrowser.Server.Implementations.Sync if (!reader.IsDBNull(9)) { - info.Limit = reader.GetInt64(9); + info.ItemLimit = reader.GetInt32(9); } - if (!reader.IsDBNull(10)) - { - info.LimitType = (SyncLimitType)Enum.Parse(typeof(SyncLimitType), reader.GetString(10), true); - } + info.RemoveWhenWatched = reader.GetBoolean(10); + info.SyncNewContent = reader.GetBoolean(11); - info.IsDynamic = reader.GetBoolean(11); info.DateCreated = reader.GetDateTime(12).ToUniversalTime(); info.DateLastModified = reader.GetDateTime(13).ToUniversalTime(); info.ItemCount = reader.GetInt32(14); @@ -206,9 +208,9 @@ namespace MediaBrowser.Server.Implementations.Sync _saveJobCommand.GetParameter(index++).Value = job.UserId; _saveJobCommand.GetParameter(index++).Value = string.Join(",", job.RequestedItemIds.ToArray()); _saveJobCommand.GetParameter(index++).Value = job.UnwatchedOnly; - _saveJobCommand.GetParameter(index++).Value = job.Limit; - _saveJobCommand.GetParameter(index++).Value = job.LimitType; - _saveJobCommand.GetParameter(index++).Value = job.IsDynamic; + _saveJobCommand.GetParameter(index++).Value = job.ItemLimit; + _saveJobCommand.GetParameter(index++).Value = job.RemoveWhenWatched; + _saveJobCommand.GetParameter(index++).Value = job.SyncNewContent; _saveJobCommand.GetParameter(index++).Value = job.DateCreated; _saveJobCommand.GetParameter(index++).Value = job.DateLastModified; _saveJobCommand.GetParameter(index++).Value = job.ItemCount; @@ -250,6 +252,62 @@ namespace MediaBrowser.Server.Implementations.Sync } } + public async Task DeleteJob(string id) + { + if (string.IsNullOrWhiteSpace(id)) + { + throw new ArgumentNullException("id"); + } + + await _writeLock.WaitAsync().ConfigureAwait(false); + + IDbTransaction transaction = null; + + try + { + transaction = _connection.BeginTransaction(); + + var index = 0; + + _deleteJobCommand.GetParameter(index++).Value = new Guid(id); + + _deleteJobCommand.Transaction = transaction; + + _deleteJobCommand.ExecuteNonQuery(); + + transaction.Commit(); + } + catch (OperationCanceledException) + { + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + catch (Exception e) + { + _logger.ErrorException("Failed to save record:", e); + + if (transaction != null) + { + transaction.Rollback(); + } + + throw; + } + finally + { + if (transaction != null) + { + transaction.Dispose(); + } + + _writeLock.Release(); + } + } + public QueryResult GetJobs(SyncJobQuery query) { if (query == null) @@ -336,6 +394,31 @@ namespace MediaBrowser.Server.Implementations.Sync return null; } + public IEnumerable GetJobItems(string jobId) + { + if (string.IsNullOrEmpty(jobId)) + { + throw new ArgumentNullException("jobId"); + } + + var guid = new Guid(jobId); + + using (var cmd = _connection.CreateCommand()) + { + cmd.CommandText = BaseJobItemSelectText + " where JobId=@Id"; + + cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid; + + using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult)) + { + while (reader.Read()) + { + yield return GetSyncJobItem(reader); + } + } + } + } + public Task Create(SyncJobItem jobItem) { return Update(jobItem); -- cgit v1.2.3 From ab3da461130b0db2f77e7e848c4bbd1280e5524a Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 12 Dec 2014 22:56:30 -0500 Subject: more sync movement --- MediaBrowser.Api/Devices/DeviceService.cs | 28 +- MediaBrowser.Api/Music/InstantMixService.cs | 17 ++ MediaBrowser.Api/Session/SessionsService.cs | 11 +- .../Collections/CollectionCreationOptions.cs | 2 + MediaBrowser.Controller/Devices/IDeviceManager.cs | 4 +- MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 20 +- MediaBrowser.Controller/Entities/Share.cs | 15 + MediaBrowser.Controller/Library/IMusicManager.cs | 8 + .../MediaBrowser.Controller.csproj | 1 + .../Persistence/IUserRepository.cs | 6 - MediaBrowser.Controller/Playlists/Playlist.cs | 19 +- .../Providers/BaseItemXmlParser.cs | 78 +++++ MediaBrowser.Controller/Sync/ISyncManager.cs | 10 +- MediaBrowser.Controller/Sync/ISyncRepository.cs | 5 +- .../Parsers/PlaylistXmlParser.cs | 12 +- .../Savers/PlaylistXmlSaver.cs | 5 - .../Savers/XmlSaverHelpers.cs | 23 ++ .../MediaBrowser.Model.Portable.csproj | 9 + .../MediaBrowser.Model.net35.csproj | 9 + MediaBrowser.Model/Devices/DeviceQuery.cs | 17 ++ MediaBrowser.Model/Dto/BaseItemDto.cs | 6 + MediaBrowser.Model/MediaBrowser.Model.csproj | 3 + MediaBrowser.Model/Querying/ItemFields.cs | 5 + MediaBrowser.Model/Session/ClientCapabilities.cs | 5 +- MediaBrowser.Model/Sync/SyncJobItem.cs | 13 +- MediaBrowser.Model/Sync/SyncJobItemQuery.cs | 27 ++ MediaBrowser.Model/Sync/SyncJobItemStatus.cs | 12 + MediaBrowser.Model/Sync/SyncJobQuery.cs | 5 + MediaBrowser.Model/Sync/SyncJobStatus.cs | 7 +- .../BoxSets/BoxSetMetadataService.cs | 5 + .../Playlists/PlaylistMetadataService.cs | 11 +- .../Collections/CollectionManager.cs | 9 +- .../Devices/DeviceManager.cs | 28 +- .../Dto/DtoService.cs | 9 + .../Library/LibraryManager.cs | 19 +- .../Library/MusicManager.cs | 13 + .../Library/Resolvers/Movies/MovieResolver.cs | 2 +- .../Localization/JavaScript/javascript.json | 11 +- .../Localization/Server/server.json | 4 +- .../MediaBrowser.Server.Implementations.csproj | 1 + .../Playlists/ManualPlaylistsFolder.cs | 2 +- .../Playlists/PlaylistManager.cs | 9 +- .../Sync/SyncJobProcessor.cs | 334 ++++++++++++++++++++- .../Sync/SyncManager.cs | 101 +++++-- .../Sync/SyncRepository.cs | 125 ++++++-- .../Sync/SyncScheduledTask.cs | 62 ++++ .../ApplicationHost.cs | 2 +- MediaBrowser.WebDashboard/Api/PackageCreator.cs | 3 +- .../MediaBrowser.WebDashboard.csproj | 10 +- 49 files changed, 997 insertions(+), 145 deletions(-) create mode 100644 MediaBrowser.Controller/Entities/Share.cs create mode 100644 MediaBrowser.Model/Devices/DeviceQuery.cs create mode 100644 MediaBrowser.Model/Sync/SyncJobItemQuery.cs create mode 100644 MediaBrowser.Model/Sync/SyncJobItemStatus.cs create mode 100644 MediaBrowser.Server.Implementations/Sync/SyncScheduledTask.cs (limited to 'MediaBrowser.Server.Implementations/Sync/SyncRepository.cs') diff --git a/MediaBrowser.Api/Devices/DeviceService.cs b/MediaBrowser.Api/Devices/DeviceService.cs index 135397308..ab0a4a4b2 100644 --- a/MediaBrowser.Api/Devices/DeviceService.cs +++ b/MediaBrowser.Api/Devices/DeviceService.cs @@ -1,25 +1,19 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Devices; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using ServiceStack; using ServiceStack.Web; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading.Tasks; namespace MediaBrowser.Api.Devices { [Route("/Devices", "GET", Summary = "Gets all devices")] [Authenticated(Roles = "Admin")] - public class GetDevices : IReturn> + public class GetDevices : DeviceQuery, IReturn> { - [ApiMember(Name = "SupportsContentUploading", Description = "SupportsContentUploading", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? SupportsContentUploading { get; set; } - - [ApiMember(Name = "SupportsDeviceId", Description = "SupportsDeviceId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? SupportsDeviceId { get; set; } } [Route("/Devices", "DELETE", Summary = "Deletes a device")] @@ -112,23 +106,7 @@ namespace MediaBrowser.Api.Devices public object Get(GetDevices request) { - var devices = _deviceManager.GetDevices(); - - if (request.SupportsContentUploading.HasValue) - { - var val = request.SupportsContentUploading.Value; - - devices = devices.Where(i => _deviceManager.GetCapabilities(i.Id).SupportsContentUploading == val); - } - - if (request.SupportsDeviceId.HasValue) - { - var val = request.SupportsDeviceId.Value; - - devices = devices.Where(i => _deviceManager.GetCapabilities(i.Id).SupportsDeviceId == val); - } - - return ToOptimizedResult(devices.ToList()); + return ToOptimizedResult(_deviceManager.GetDevices(request)); } public object Get(GetCameraUploads request) diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index f34242242..43fd0894b 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -3,6 +3,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Querying; using ServiceStack; using System.Collections.Generic; @@ -20,6 +21,11 @@ namespace MediaBrowser.Api.Music { } + [Route("/Playlists/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given playlist")] + public class GetInstantMixFromPlaylist : BaseGetSimilarItemsFromItem + { + } + [Route("/Artists/{Name}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given artist")] public class GetInstantMixFromArtist : BaseGetSimilarItems { @@ -109,6 +115,17 @@ namespace MediaBrowser.Api.Music return GetResult(items, user, request); } + public object Get(GetInstantMixFromPlaylist request) + { + var playlist = (Playlist)_libraryManager.GetItemById(request.Id); + + var user = _userManager.GetUserById(request.UserId.Value); + + var items = _musicManager.GetInstantMixFromPlaylist(playlist, user); + + return GetResult(items, user, request); + } + public object Get(GetInstantMixFromMusicGenre request) { var user = _userManager.GetUserById(request.UserId.Value); diff --git a/MediaBrowser.Api/Session/SessionsService.cs b/MediaBrowser.Api/Session/SessionsService.cs index 551771338..df50255ab 100644 --- a/MediaBrowser.Api/Session/SessionsService.cs +++ b/MediaBrowser.Api/Session/SessionsService.cs @@ -239,8 +239,11 @@ namespace MediaBrowser.Api.Session [ApiMember(Name = "SupportsContentUploading", Description = "Determines whether camera upload is supported.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] public bool SupportsContentUploading { get; set; } - [ApiMember(Name = "SupportsDeviceId", Description = "Determines whether the device supports a unique identifier.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] - public bool SupportsDeviceId { get; set; } + [ApiMember(Name = "SupportsSync", Description = "Determines whether sync is supported.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool SupportsSync { get; set; } + + [ApiMember(Name = "SupportsUniqueIdentifier", Description = "Determines whether the device supports a unique identifier.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool SupportsUniqueIdentifier { get; set; } } [Route("/Sessions/Logout", "POST", Summary = "Reports that a session has ended")] @@ -521,7 +524,9 @@ namespace MediaBrowser.Api.Session SupportsContentUploading = request.SupportsContentUploading, - SupportsDeviceId = request.SupportsDeviceId + SupportsSync = request.SupportsSync, + + SupportsUniqueIdentifier = request.SupportsUniqueIdentifier }); } } diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index 74ae42095..4a2d39066 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -15,11 +15,13 @@ namespace MediaBrowser.Controller.Collections public Dictionary ProviderIds { get; set; } public List ItemIdList { get; set; } + public List UserIds { get; set; } public CollectionCreationOptions() { ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); ItemIdList = new List(); + UserIds = new List(); } } } diff --git a/MediaBrowser.Controller/Devices/IDeviceManager.cs b/MediaBrowser.Controller/Devices/IDeviceManager.cs index af184e6e9..efd24336a 100644 --- a/MediaBrowser.Controller/Devices/IDeviceManager.cs +++ b/MediaBrowser.Controller/Devices/IDeviceManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Model.Devices; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using System; using System.Collections.Generic; @@ -58,8 +59,9 @@ namespace MediaBrowser.Controller.Devices /// /// Gets the devices. /// + /// The query. /// IEnumerable<DeviceInfo>. - IEnumerable GetDevices(); + QueryResult GetDevices(DeviceQuery query); /// /// Deletes the device. diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 731226ede..9dc600675 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -15,8 +15,10 @@ namespace MediaBrowser.Controller.Entities.Movies /// /// Class BoxSet /// - public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo, IMetadataContainer + public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo, IMetadataContainer, IHasShares { + public List Shares { get; set; } + public BoxSet() { RemoteTrailers = new List(); @@ -25,6 +27,7 @@ namespace MediaBrowser.Controller.Entities.Movies DisplayOrder = ItemSortBy.PremiereDate; Keywords = new List(); + Shares = new List(); } protected override bool FilterLinkedChildrenPerUser @@ -160,5 +163,20 @@ namespace MediaBrowser.Controller.Entities.Movies progress.Report(100); } + + public override bool IsVisible(User user) + { + if (base.IsVisible(user)) + { + var userId = user.Id.ToString("N"); + + return Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)) || + + // Need to support this for boxsets created prior to the creation of Shares + Shares.Count == 0; + } + + return false; + } } } diff --git a/MediaBrowser.Controller/Entities/Share.cs b/MediaBrowser.Controller/Entities/Share.cs new file mode 100644 index 000000000..e194f6238 --- /dev/null +++ b/MediaBrowser.Controller/Entities/Share.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Entities +{ + public interface IHasShares + { + List Shares { get; set; } + } + + public class Share + { + public string UserId { get; set; } + public bool CanEdit { get; set; } + } +} diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs index 192ce2e83..f66f18401 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -1,5 +1,6 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Playlists; using System.Collections.Generic; namespace MediaBrowser.Controller.Library @@ -28,6 +29,13 @@ namespace MediaBrowser.Controller.Library /// IEnumerable{Audio}. IEnumerable public interface IUserRepository : IRepository { - /// - /// Opens the connection to the repository - /// - /// Task. - Task Initialize(); - /// /// Deletes the user. /// diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 75e1bbde7..e48cddaaa 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -11,10 +11,17 @@ using System.Runtime.Serialization; namespace MediaBrowser.Controller.Playlists { - public class Playlist : Folder + public class Playlist : Folder, IHasShares { public string OwnerUserId { get; set; } + public List Shares { get; set; } + + public Playlist() + { + Shares = new List(); + } + [IgnoreDataMember] protected override bool FilterLinkedChildrenPerUser { @@ -166,7 +173,15 @@ namespace MediaBrowser.Controller.Playlists public override bool IsVisible(User user) { - return base.IsVisible(user) && string.Equals(user.Id.ToString("N"), OwnerUserId); + if (base.IsVisible(user)) + { + var userId = user.Id.ToString("N"); + + return Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)) || + string.Equals(OwnerUserId, userId, StringComparison.OrdinalIgnoreCase); + } + + return false; } } } diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index 307ab3cb8..a37f7eb8a 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -819,6 +819,19 @@ namespace MediaBrowser.Controller.Providers break; } + case "Shares": + { + using (var subtree = reader.ReadSubtree()) + { + var hasShares = item as IHasShares; + if (hasShares != null) + { + FetchFromSharesNode(subtree, hasShares); + } + } + break; + } + case "Format3D": { var video = item as Video; @@ -853,6 +866,71 @@ namespace MediaBrowser.Controller.Providers } } + private void FetchFromSharesNode(XmlReader reader, IHasShares item) + { + reader.MoveToContent(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Share": + { + using (var subtree = reader.ReadSubtree()) + { + var share = GetShareFromNode(subtree); + if (share != null) + { + item.Shares.Add(share); + } + } + break; + } + + default: + reader.Skip(); + break; + } + } + } + } + + private Share GetShareFromNode(XmlReader reader) + { + var share = new Share(); + + reader.MoveToContent(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "UserId": + { + share.UserId = reader.ReadElementContentAsString(); + break; + } + + case "CanEdit": + { + share.CanEdit = string.Equals(reader.ReadElementContentAsString(), true.ToString(), StringComparison.OrdinalIgnoreCase); + break; + } + + default: + reader.Skip(); + break; + } + } + } + + return share; + } + private void FetchFromCountriesNode(XmlReader reader, T item) { reader.MoveToContent(); diff --git a/MediaBrowser.Controller/Sync/ISyncManager.cs b/MediaBrowser.Controller/Sync/ISyncManager.cs index 1d5ab7d3e..31c3c0c6d 100644 --- a/MediaBrowser.Controller/Sync/ISyncManager.cs +++ b/MediaBrowser.Controller/Sync/ISyncManager.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; using System.Collections.Generic; @@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Sync /// The identifier. /// SyncJob. SyncJob GetJob(string id); - + /// /// Cancels the job. /// @@ -51,5 +52,12 @@ namespace MediaBrowser.Controller.Sync /// The item. /// true if XXXX, false otherwise. bool SupportsSync(BaseItem item); + + /// + /// Gets the device profile. + /// + /// The target identifier. + /// DeviceProfile. + DeviceProfile GetDeviceProfile(string targetId); } } diff --git a/MediaBrowser.Controller/Sync/ISyncRepository.cs b/MediaBrowser.Controller/Sync/ISyncRepository.cs index d0cf87182..f1bcd7f07 100644 --- a/MediaBrowser.Controller/Sync/ISyncRepository.cs +++ b/MediaBrowser.Controller/Sync/ISyncRepository.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Querying; using MediaBrowser.Model.Sync; -using System.Collections.Generic; using System.Threading.Tasks; namespace MediaBrowser.Controller.Sync @@ -66,8 +65,8 @@ namespace MediaBrowser.Controller.Sync /// /// Gets the job items. /// - /// The job identifier. + /// The query. /// IEnumerable<SyncJobItem>. - IEnumerable GetJobItems(string jobId); + QueryResult GetJobItems(SyncJobItemQuery query); } } diff --git a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs index c7f974200..a12724ff7 100644 --- a/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs +++ b/MediaBrowser.LocalMetadata/Parsers/PlaylistXmlParser.cs @@ -2,7 +2,9 @@ using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; +using System; using System.Collections.Generic; +using System.Linq; using System.Xml; namespace MediaBrowser.LocalMetadata.Parsers @@ -20,7 +22,15 @@ namespace MediaBrowser.LocalMetadata.Parsers { case "OwnerUserId": { - item.OwnerUserId = reader.ReadElementContentAsString(); + var userId = reader.ReadElementContentAsString(); + if (!item.Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase))) + { + item.Shares.Add(new Share + { + UserId = userId, + CanEdit = true + }); + } break; } diff --git a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs index 16c437381..76ef4d4bf 100644 --- a/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs +++ b/MediaBrowser.LocalMetadata/Savers/PlaylistXmlSaver.cs @@ -57,11 +57,6 @@ namespace MediaBrowser.LocalMetadata.Savers builder.Append(""); - if (!string.IsNullOrEmpty(playlist.OwnerUserId)) - { - builder.Append("" + SecurityElement.Escape(playlist.OwnerUserId) + ""); - } - if (!string.IsNullOrEmpty(playlist.PlaylistMediaType)) { builder.Append("" + SecurityElement.Escape(playlist.PlaylistMediaType) + ""); diff --git a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs index 93876f474..3e11c994b 100644 --- a/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.LocalMetadata/Savers/XmlSaverHelpers.cs @@ -645,6 +645,29 @@ namespace MediaBrowser.LocalMetadata.Savers { AddLinkedChildren(playlist, builder, "PlaylistItems", "PlaylistItem"); } + + var hasShares = item as IHasShares; + if (hasShares != null) + { + + } + } + + public static void AddShares(IHasShares item, StringBuilder builder) + { + builder.Append(""); + + foreach (var share in item.Shares) + { + builder.Append(""); + + builder.Append("" + SecurityElement.Escape(share.UserId) + ""); + builder.Append("" + SecurityElement.Escape(share.CanEdit.ToString().ToLower()) + ""); + + builder.Append(""); + } + + builder.Append(""); } public static void AddChapters(Video item, StringBuilder builder, IItemRepository repository) diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index bcd6e08f1..701d7d70b 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -275,6 +275,9 @@ Devices\DeviceOptions.cs + + Devices\DeviceQuery.cs + Devices\DevicesOptions.cs @@ -1034,6 +1037,12 @@ Sync\SyncJobItem.cs + + SyncJobItemQuery.cs + + + Sync\SyncJobItemStatus.cs + Sync\SyncJobQuery.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 55d18c1b3..9affbb199 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -240,6 +240,9 @@ Devices\DeviceOptions.cs + + Devices\DeviceQuery.cs + Devices\DevicesOptions.cs @@ -993,6 +996,12 @@ Sync\SyncJobItem.cs + + Sync\SyncJobItemQuery.cs + + + Sync\SyncJobItemStatus.cs + Sync\SyncJobQuery.cs diff --git a/MediaBrowser.Model/Devices/DeviceQuery.cs b/MediaBrowser.Model/Devices/DeviceQuery.cs new file mode 100644 index 000000000..76f7117b6 --- /dev/null +++ b/MediaBrowser.Model/Devices/DeviceQuery.cs @@ -0,0 +1,17 @@ + +namespace MediaBrowser.Model.Devices +{ + public class DeviceQuery + { + /// + /// Gets or sets a value indicating whether [supports content uploading]. + /// + /// null if [supports content uploading] contains no value, true if [supports content uploading]; otherwise, false. + public bool? SupportsContentUploading { get; set; } + /// + /// Gets or sets a value indicating whether [supports unique identifier]. + /// + /// null if [supports unique identifier] contains no value, true if [supports unique identifier]; otherwise, false. + public bool? SupportsUniqueIdentifier { get; set; } + } +} diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index b83243ba8..45f681066 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -186,6 +186,12 @@ namespace MediaBrowser.Model.Dto /// /// The genres. public List Genres { get; set; } + + /// + /// Gets or sets the series genres. + /// + /// The series genres. + public List SeriesGenres { get; set; } /// /// Gets or sets the community rating. diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 4825cb4cc..5aebe42eb 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -113,6 +113,7 @@ + @@ -365,6 +366,8 @@ + + diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index a5a906f95..19e30cd8a 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -161,6 +161,11 @@ namespace MediaBrowser.Model.Querying /// ScreenshotImageTags, + /// + /// The series genres + /// + SeriesGenres, + /// /// The series studio /// diff --git a/MediaBrowser.Model/Session/ClientCapabilities.cs b/MediaBrowser.Model/Session/ClientCapabilities.cs index fc0d3a1fb..f2faa0545 100644 --- a/MediaBrowser.Model/Session/ClientCapabilities.cs +++ b/MediaBrowser.Model/Session/ClientCapabilities.cs @@ -13,13 +13,14 @@ namespace MediaBrowser.Model.Session public string MessageCallbackUrl { get; set; } public bool SupportsContentUploading { get; set; } - public bool SupportsDeviceId { get; set; } + public bool SupportsUniqueIdentifier { get; set; } + public bool SupportsSync { get; set; } public ClientCapabilities() { PlayableMediaTypes = new List(); SupportedCommands = new List(); - SupportsDeviceId = true; + SupportsUniqueIdentifier = true; } } } \ No newline at end of file diff --git a/MediaBrowser.Model/Sync/SyncJobItem.cs b/MediaBrowser.Model/Sync/SyncJobItem.cs index 141546eb5..063f7feb2 100644 --- a/MediaBrowser.Model/Sync/SyncJobItem.cs +++ b/MediaBrowser.Model/Sync/SyncJobItem.cs @@ -1,4 +1,5 @@ - +using System; + namespace MediaBrowser.Model.Sync { public class SyncJobItem @@ -37,12 +38,18 @@ namespace MediaBrowser.Model.Sync /// Gets or sets the status. /// /// The status. - public SyncJobStatus Status { get; set; } + public SyncJobItemStatus Status { get; set; } /// /// Gets or sets the current progress. /// /// The current progress. - public double? CurrentProgress { get; set; } + public double? Progress { get; set; } + + /// + /// Gets or sets the date created. + /// + /// The date created. + public DateTime DateCreated { get; set; } } } diff --git a/MediaBrowser.Model/Sync/SyncJobItemQuery.cs b/MediaBrowser.Model/Sync/SyncJobItemQuery.cs new file mode 100644 index 000000000..e9af642ac --- /dev/null +++ b/MediaBrowser.Model/Sync/SyncJobItemQuery.cs @@ -0,0 +1,27 @@ + +namespace MediaBrowser.Model.Sync +{ + public class SyncJobItemQuery + { + /// + /// Gets or sets the start index. + /// + /// The start index. + public int? StartIndex { get; set; } + /// + /// Gets or sets the limit. + /// + /// The limit. + public int? Limit { get; set; } + /// + /// Gets or sets the job identifier. + /// + /// The job identifier. + public string JobId { get; set; } + /// + /// Gets or sets a value indicating whether this instance is completed. + /// + /// null if [is completed] contains no value, true if [is completed]; otherwise, false. + public bool? IsCompleted { get; set; } + } +} diff --git a/MediaBrowser.Model/Sync/SyncJobItemStatus.cs b/MediaBrowser.Model/Sync/SyncJobItemStatus.cs new file mode 100644 index 000000000..3d0579a3c --- /dev/null +++ b/MediaBrowser.Model/Sync/SyncJobItemStatus.cs @@ -0,0 +1,12 @@ + +namespace MediaBrowser.Model.Sync +{ + public enum SyncJobItemStatus + { + Queued = 0, + Converting = 1, + Transferring = 2, + Completed = 3, + Failed = 4 + } +} diff --git a/MediaBrowser.Model/Sync/SyncJobQuery.cs b/MediaBrowser.Model/Sync/SyncJobQuery.cs index 74b35186e..218b3823e 100644 --- a/MediaBrowser.Model/Sync/SyncJobQuery.cs +++ b/MediaBrowser.Model/Sync/SyncJobQuery.cs @@ -13,5 +13,10 @@ namespace MediaBrowser.Model.Sync /// /// The limit. public int? Limit { get; set; } + /// + /// Gets or sets a value indicating whether this instance is completed. + /// + /// null if [is completed] contains no value, true if [is completed]; otherwise, false. + public bool? IsCompleted { get; set; } } } diff --git a/MediaBrowser.Model/Sync/SyncJobStatus.cs b/MediaBrowser.Model/Sync/SyncJobStatus.cs index 42af96509..961ccf544 100644 --- a/MediaBrowser.Model/Sync/SyncJobStatus.cs +++ b/MediaBrowser.Model/Sync/SyncJobStatus.cs @@ -4,9 +4,8 @@ namespace MediaBrowser.Model.Sync public enum SyncJobStatus { Queued = 0, - Converting = 1, - Transferring = 2, - Completed = 3, - Cancelled = 4 + InProgress = 1, + Completed = 2, + CompletedWithError = 3 } } diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index 31136b919..062519f9d 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -44,6 +44,11 @@ namespace MediaBrowser.Providers.BoxSets target.LinkedChildren = list; } + + if (replaceData || target.Shares.Count == 0) + { + target.Shares = source.Shares; + } } protected override ItemUpdateType BeforeSave(BoxSet item) diff --git a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs index 586c17d07..2e407f10c 100644 --- a/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs +++ b/MediaBrowser.Providers/Playlists/PlaylistMetadataService.cs @@ -7,7 +7,6 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using MediaBrowser.Providers.Manager; using System.Collections.Generic; -using System.Linq; namespace MediaBrowser.Providers.Playlists { @@ -34,18 +33,14 @@ namespace MediaBrowser.Providers.Playlists target.PlaylistMediaType = source.PlaylistMediaType; } - if (replaceData || string.IsNullOrEmpty(target.OwnerUserId)) + if (replaceData || target.Shares.Count == 0) { - target.OwnerUserId = source.OwnerUserId; + target.Shares = source.Shares; } if (mergeMetadataSettings) { - var list = source.LinkedChildren.ToList(); - - list.AddRange(target.LinkedChildren); - - target.LinkedChildren = list; + target.LinkedChildren = source.LinkedChildren; } } } diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index e346d6d01..9fee27db9 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -72,7 +72,12 @@ namespace MediaBrowser.Server.Implementations.Collections DisplayMediaType = "Collection", Path = path, IsLocked = options.IsLocked, - ProviderIds = options.ProviderIds + ProviderIds = options.ProviderIds, + Shares = options.UserIds.Select(i => new Share + { + UserId = i.ToString("N") + + }).ToList() }; await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); @@ -156,7 +161,7 @@ namespace MediaBrowser.Server.Implementations.Collections } itemList.Add(item); - + if (currentLinkedChildren.Any(i => i.Id == itemId)) { throw new ArgumentException("Item already exists in collection"); diff --git a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs index e2c729b2d..8c67013ea 100644 --- a/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs +++ b/MediaBrowser.Server.Implementations/Devices/DeviceManager.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Devices; using MediaBrowser.Model.Events; using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; using MediaBrowser.Model.Session; using System; using System.Collections.Generic; @@ -28,7 +29,7 @@ namespace MediaBrowser.Server.Implementations.Devices /// Occurs when [device options updated]. /// public event EventHandler> DeviceOptionsUpdated; - + public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IConfigurationManager config, ILogger logger) { _repo = repo; @@ -79,9 +80,30 @@ namespace MediaBrowser.Server.Implementations.Devices return _repo.GetDevice(id); } - public IEnumerable GetDevices() + public QueryResult GetDevices(DeviceQuery query) { - return _repo.GetDevices().OrderByDescending(i => i.DateLastModified); + IEnumerable devices = _repo.GetDevices().OrderByDescending(i => i.DateLastModified); + + if (query.SupportsContentUploading.HasValue) + { + var val = query.SupportsContentUploading.Value; + + devices = devices.Where(i => GetCapabilities(i.Id).SupportsContentUploading == val); + } + + if (query.SupportsUniqueIdentifier.HasValue) + { + var val = query.SupportsUniqueIdentifier.Value; + + devices = devices.Where(i => GetCapabilities(i.Id).SupportsUniqueIdentifier == val); + } + + var array = devices.ToArray(); + return new QueryResult + { + Items = array, + TotalRecordCount = array.Length + }; } public Task DeleteDevice(string id) diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index e1e8da5c5..a6f9f0675 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -1142,6 +1142,15 @@ namespace MediaBrowser.Server.Implementations.Dto dto.SeasonId = episodeSeason.Id.ToString("N"); dto.SeasonName = episodeSeason.Name; } + + if (fields.Contains(ItemFields.SeriesGenres)) + { + var episodeseries = episode.Series; + if (episodeseries != null) + { + dto.SeriesGenres = episodeseries.Genres.ToList(); + } + } } // Add SeriesInfo diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index e06720192..bfdfc03ba 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -736,16 +736,27 @@ namespace MediaBrowser.Server.Implementations.Library } private UserRootFolder _userRootFolder; + private readonly object _syncLock = new object(); public Folder GetUserRootFolder() { if (_userRootFolder == null) { - var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; + lock (_syncLock) + { + if (_userRootFolder == null) + { + var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; - Directory.CreateDirectory(userRootPath); + Directory.CreateDirectory(userRootPath); - _userRootFolder = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder ?? - (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath)); + _userRootFolder = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder; + + if (_userRootFolder == null) + { + _userRootFolder = (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath)); + } + } + } } return _userRootFolder; diff --git a/MediaBrowser.Server.Implementations/Library/MusicManager.cs b/MediaBrowser.Server.Implementations/Library/MusicManager.cs index 7ffbab860..b8c29c19b 100644 --- a/MediaBrowser.Server.Implementations/Library/MusicManager.cs +++ b/MediaBrowser.Server.Implementations/Library/MusicManager.cs @@ -1,6 +1,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Playlists; using System; using System.Collections.Generic; using System.Linq; @@ -53,6 +54,18 @@ namespace MediaBrowser.Server.Implementations.Library return GetInstantMixFromGenres(genres, user); } + public IEnumerable