diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6bd416a38..8246c9977 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -2,4 +2,4 @@ Provide a description of the feature request or bug, the more details the better. -Please use https://forums.sonarr.tv/ for support or other questions. (When in doubt, use the forums) +Please use http://lidarr.audio for support or other questions. (When in doubt, use the forums) diff --git a/.gitignore b/.gitignore index 9ad1a84be..a340f3295 100644 --- a/.gitignore +++ b/.gitignore @@ -101,7 +101,7 @@ App_Data/*.ldf _NCrunch_* _TeamCity* -# Sonarr +# Lidarr config.xml nzbdrone.log*txt UpdateLogs/ diff --git a/.idea/.name b/.idea/.name index 02629676e..e402c1d9d 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -Sonarr \ No newline at end of file +Lidarr \ No newline at end of file diff --git a/CLA.md b/CLA.md index 40adac7f6..463c6c14c 100644 --- a/CLA.md +++ b/CLA.md @@ -1,6 +1,6 @@ -# Sonarr Individual Contributor License Agreement # +# Lidarr Individual Contributor License Agreement # -Thank you for your interest in contributing to Sonarr ("We" or "Us"). +Thank you for your interest in contributing to Lidarr ("We" or "Us"). This contributor agreement ("Agreement") documents the rights granted by contributors to Us. To make this document effective, please complete the form below. This is a legally binding document, so please read it carefully before agreeing to it. The Agreement may cover more than one software project managed by Us. ## 1. Definitions ## diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab945cb0c..f402f5c63 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # How to Contribute # -We're always looking for people to help make Sonarr even better, there are a number of ways to contribute. +We're always looking for people to help make Lidarr even better, there are a number of ways to contribute. ## Documentation ## Setup guides, FAQ, the more information we have on the wiki the better. @@ -15,7 +15,7 @@ Setup guides, FAQ, the more information we have on the wiki the better. ### Getting started ### -1. Fork Sonarr +1. Fork Lidarr 2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)* 3. Run `npm install` 4. Run `npm start` - Used to compile the UI components and copy them. @@ -24,8 +24,8 @@ Setup guides, FAQ, the more information we have on the wiki the better. 5. Compile in Visual Studio ### Contributing Code ### -- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/Sonarr/Sonarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first) -- Rebase from Sonarr's develop branch, don't merge +- If you're adding a new, already requested feature, please comment on [Github Issues](https://github.com/lidarr/Lidarr/issues "Github Issues") so work is not duplicated (If you want to add something not already on there, please talk to us first) +- Rebase from Lidarr's develop branch, don't merge - Make meaningful commits, or squash them - Feel free to make a pull request before work is complete, this will let us see where its at and make comments/suggest improvements - Reach out to us on the forums or on IRC if you have any questions diff --git a/README.md b/README.md index ac8e58d06..75e7bfdf8 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,13 @@ Lidarr is a music collection manager for Usenet and BitTorrent users. It can mon ## Feature Requests -[![Feature Requests](http://feathub.com/mattman86/Lidarr?format=svg)](http://feathub.com/mattman86/Lidarr) +[![Feature Requests](http://feathub.com/lidarr/Lidarr?format=svg)](http://feathub.com/lidarr/Lidarr) ## Configuring Development Environment: ### Requirements -* Visual Studio 2015 (https://www.visualstudio.com/vs/) +* Visual Studio 2015 or higher (https://www.visualstudio.com/vs/). The community version is free and works (https://www.visualstudio.com/downloads/). * [Git](https://git-scm.com/downloads) * [NodeJS](https://nodejs.org/en/download/) @@ -35,6 +35,8 @@ Lidarr is a music collection manager for Usenet and BitTorrent users. It can mon * Grab the submodules `git submodule init && git submodule update` * Install the required Node Packages `npm install` * Start gulp to monitor your dev environment for any changes that need post processing using `npm start` command. +* Run the project in Visual Studio +* Open http://localhost:8686 *Please note gulp must be running at all times while you are working with Lidarr client source files.* diff --git a/debian/control b/debian/control index ba30c02ee..34586f51d 100644 --- a/debian/control +++ b/debian/control @@ -2,11 +2,11 @@ Section: web Priority: optional Maintainer: Sonarr Source: nzbdrone -Homepage: https://sonarr.tv -Vcs-Git: git@github.com:Sonarr/Sonarr.git -Vcs-Browser: https://github.com/Sonarr/Sonarr +Homepage: https://lidarr.audio +Vcs-Git: git@github.com:lidarr/Lidarr.git +Vcs-Browser: https://github.com/lidarr/Lidarr Package: nzbdrone Architecture: all Depends: libmono-cil-dev (>= 3.2), sqlite3 (>= 3.7), mediainfo (>= 0.7.52) -Description: Sonarr is an internet PVR +Description: Lidarr is a music collection manager diff --git a/debian/copyright b/debian/copyright index 466d37fce..667d82a43 100755 --- a/debian/copyright +++ b/debian/copyright @@ -1,9 +1,9 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: nzbdrone -Source: https://github.com/Sonarr/Sonarr +Source: https://github.com/lidarr/Lidarr Files: * -Copyright: 2010-2016 Sonarr +Copyright: 2010-2016 Lidarr License: GPL-3.0+ diff --git a/gulp/start.js b/gulp/start.js index 5b5f88044..296eb6836 100644 --- a/gulp/start.js +++ b/gulp/start.js @@ -1,4 +1,4 @@ -// will download and run sonarr (server) in a non-windows enviroment +// will download and run Lidarr (server) in a non-windows enviroment // you can use this if you don't care about the server code and just want to work // with the web code. @@ -31,7 +31,7 @@ function getLatest(cb) { } }); - var url = 'http://services.sonarr.tv/v1/update/' + branch + '?os=osx'; + var url = 'http://services.lidarr.audio/v1/update/' + branch + '?os=osx'; console.log('Checking for latest version:', url); diff --git a/package.json b/package.json index c3556ed7f..12bc565c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "Sonarr", - "version": "2.0.0", - "description": "Sonarr", + "name": "Lidarr", + "version": "1.0.0", + "description": "Lidarr", "main": "main.js", "scripts": { "build": "gulp build", @@ -9,7 +9,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/Sonarr/Sonarr.git" + "url": "git://github.com/lidarr/Lidarr.git" }, "author": "", "license": "GPL-3.0", diff --git a/setup/nzbdrone.iss b/setup/nzbdrone.iss index e667c0d03..fd8094b0c 100644 --- a/setup/nzbdrone.iss +++ b/setup/nzbdrone.iss @@ -1,10 +1,10 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define AppName "Sonarr" -#define AppPublisher "Team Sonarr" -#define AppURL "https://sonarr.tv/" -#define ForumsURL "https://forums.sonarr.tv/" +#define AppName "Lidarr" +#define AppPublisher "Team Lidarr" +#define AppURL "https://lidarr.audio/" +#define ForumsURL "https://forums.lidarr.audio/" #define AppExeName "NzbDrone.exe" #define BuildNumber "2.0" #define BuildNumber GetEnv('BUILD_NUMBER') diff --git a/src/NzbDrone.Api/Music/AlbumResource.cs b/src/NzbDrone.Api/Music/AlbumResource.cs index a6d49d3bd..d3e243c66 100644 --- a/src/NzbDrone.Api/Music/AlbumResource.cs +++ b/src/NzbDrone.Api/Music/AlbumResource.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Api.Music { public class AlbumResource { - public int AlbumId { get; set; } + public string AlbumId { get; set; } public string AlbumName { get; set; } public bool Monitored { get; set; } public int Year { get; set; } diff --git a/src/NzbDrone.Api/Music/ArtistModule.cs b/src/NzbDrone.Api/Music/ArtistModule.cs index d616becfb..d7ef5fed3 100644 --- a/src/NzbDrone.Api/Music/ArtistModule.cs +++ b/src/NzbDrone.Api/Music/ArtistModule.cs @@ -71,7 +71,7 @@ namespace NzbDrone.Api.Music PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace()); - PostValidator.RuleFor(s => s.ItunesId).GreaterThan(0).SetValidator(artistExistsValidator); + PostValidator.RuleFor(s => s.SpotifyId).NotEqual("").SetValidator(artistExistsValidator); PutValidator.RuleFor(s => s.Path).IsValidPath(); } @@ -98,7 +98,6 @@ namespace NzbDrone.Api.Music { //var seriesStats = _seriesStatisticsService.SeriesStatistics(); var artistResources = _artistService.GetAllArtists().ToResource(); - MapCoversToLocal(artistResources.ToArray()); //LinkSeriesStatistics(seriesResources, seriesStats); //PopulateAlternateTitles(seriesResources); @@ -106,9 +105,9 @@ namespace NzbDrone.Api.Music return artistResources; } - private int AddArtist(ArtistResource seriesResource) + private int AddArtist(ArtistResource artistResource) { - var model = seriesResource.ToModel(); + var model = artistResource.ToModel(); return _addSeriesService.AddArtist(model).Id; } @@ -175,16 +174,6 @@ namespace NzbDrone.Api.Music BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); } - //public void Handle(ArtistDeletedEvent message) - //{ - // BroadcastResourceChange(ModelAction.Deleted, message.Artist.ToResource()); - //} - - //public void Handle(ArtistRenamedEvent message) - //{ - // BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); - //} - //public void Handle(MediaCoversUpdatedEvent message) //{ // BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); diff --git a/src/NzbDrone.Api/Music/ArtistResource.cs b/src/NzbDrone.Api/Music/ArtistResource.cs index c42c8f4d7..71cc14b85 100644 --- a/src/NzbDrone.Api/Music/ArtistResource.cs +++ b/src/NzbDrone.Api/Music/ArtistResource.cs @@ -19,9 +19,8 @@ namespace NzbDrone.Api.Music //View Only public string ArtistName { get; set; } - public int ItunesId { get; set; } - //public List AlternateTitles { get; set; } - //public string SortTitle { get; set; } + public string SpotifyId { get; set; } + public string Overview { get; set; } public int AlbumCount { @@ -29,7 +28,7 @@ namespace NzbDrone.Api.Music { if (Albums == null) return 0; - return Albums.Where(s => s.AlbumId > 0).Count(); // TODO: CHeck this condition + return Albums.Where(s => s.AlbumId != "").Count(); // TODO: CHeck this condition } } @@ -82,7 +81,7 @@ namespace NzbDrone.Api.Music //EpisodeFileCount //SizeOnDisk //Status = resource.Status, - //Overview = resource.Overview, + Overview = model.Overview, //NextAiring //PreviousAiring //Network = resource.Network, @@ -106,7 +105,7 @@ namespace NzbDrone.Api.Music //FirstAired = resource.FirstAired, //LastInfoSync = resource.LastInfoSync, //SeriesType = resource.SeriesType, - ItunesId = model.ItunesId, + SpotifyId = model.SpotifyId, ArtistSlug = model.ArtistSlug, RootFolderPath = model.RootFolderPath, @@ -135,7 +134,7 @@ namespace NzbDrone.Api.Music //EpisodeFileCount //SizeOnDisk //Status = resource.Status, - //Overview = resource.Overview, + Overview = resource.Overview, //NextAiring //PreviousAiring //Network = resource.Network, @@ -150,16 +149,8 @@ namespace NzbDrone.Api.Music ArtistFolder = resource.ArtistFolder, Monitored = resource.Monitored, - - //UseSceneNumbering = resource.UseSceneNumbering, - //Runtime = resource.Runtime, - //TvdbId = resource.TvdbId, - //TvRageId = resource.TvRageId, - //TvMazeId = resource.TvMazeId, - //FirstAired = resource.FirstAired, //LastInfoSync = resource.LastInfoSync, - //SeriesType = resource.SeriesType, - ItunesId = resource.ItunesId, + SpotifyId = resource.SpotifyId, ArtistSlug = resource.ArtistSlug, RootFolderPath = resource.RootFolderPath, diff --git a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs index 29f0e9d78..9efdda47c 100644 --- a/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs +++ b/src/NzbDrone.Common/Cloud/SonarrCloudRequestBuilder.cs @@ -6,6 +6,7 @@ namespace NzbDrone.Common.Cloud { IHttpRequestBuilderFactory Services { get; } IHttpRequestBuilderFactory Search { get; } + IHttpRequestBuilderFactory InternalSearch { get; } IHttpRequestBuilderFactory SkyHookTvdb { get; } } @@ -16,7 +17,11 @@ namespace NzbDrone.Common.Cloud Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/") .CreateFactory(); - Search = new HttpRequestBuilder("https://itunes.apple.com/{route}/") + Search = new HttpRequestBuilder("https://api.spotify.com/{version}/{route}/") // TODO: maybe use {version} + .SetSegment("version", "v1") + .CreateFactory(); + + InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search .CreateFactory(); SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/") @@ -28,6 +33,8 @@ namespace NzbDrone.Common.Cloud public IHttpRequestBuilderFactory Search { get; } + public IHttpRequestBuilderFactory InternalSearch { get; } + public IHttpRequestBuilderFactory SkyHookTvdb { get; } } } diff --git a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs index faa998a0a..0e2da7d1b 100644 --- a/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs +++ b/src/NzbDrone.Core/Datastore/Migration/111_setup_music.cs @@ -13,11 +13,12 @@ namespace NzbDrone.Core.Datastore.Migration protected override void MainDbUpgrade() { Create.TableForModel("Artist") - .WithColumn("ItunesId").AsInt32().Unique() + .WithColumn("SpotifyId").AsString().Nullable().Unique() .WithColumn("ArtistName").AsString().Unique() .WithColumn("ArtistSlug").AsString().Nullable() //.Unique() .WithColumn("CleanTitle").AsString().Nullable() // Do we need this? .WithColumn("Monitored").AsBoolean() + .WithColumn("Overview").AsString().Nullable() .WithColumn("AlbumFolder").AsBoolean().Nullable() .WithColumn("ArtistFolder").AsBoolean().Nullable() .WithColumn("LastInfoSync").AsDateTime().Nullable() @@ -36,8 +37,8 @@ namespace NzbDrone.Core.Datastore.Migration ; Create.TableForModel("Albums") - .WithColumn("AlbumId").AsInt32() - .WithColumn("ArtistId").AsInt32() + .WithColumn("AlbumId").AsString().Unique() + .WithColumn("ArtistId").AsInt32() // Should this be artistId (string) .WithColumn("Title").AsString() .WithColumn("Year").AsInt32() .WithColumn("Image").AsInt32() @@ -48,12 +49,13 @@ namespace NzbDrone.Core.Datastore.Migration Create.TableForModel("Tracks") .WithColumn("ItunesTrackId").AsInt32().Unique() - .WithColumn("AlbumId").AsInt32() + .WithColumn("AlbumId").AsString() .WithColumn("ArtistsId").AsString().Nullable() .WithColumn("TrackNumber").AsInt32() .WithColumn("Title").AsString().Nullable() .WithColumn("Ignored").AsBoolean().Nullable() .WithColumn("Explict").AsBoolean() + .WithColumn("Monitored").AsBoolean() .WithColumn("TrackExplicitName").AsString().Nullable() .WithColumn("TrackCensoredName").AsString().Nullable() .WithColumn("TrackFileId").AsInt32().Nullable() diff --git a/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs b/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs index f48ce9988..b855e1e28 100644 --- a/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs +++ b/src/NzbDrone.Core/Datastore/Migration/112_Add_music_fields_to_NamingConfig.cs @@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration { protected override void MainDbUpgrade() { - Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString(); - Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString(); + Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString().Nullable(); + Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString().Nullable(); } } } diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs index 3d2594bef..09163a47c 100644 --- a/src/NzbDrone.Core/Datastore/TableMapping.cs +++ b/src/NzbDrone.Core/Datastore/TableMapping.cs @@ -102,7 +102,7 @@ namespace NzbDrone.Core.Datastore .Relationships.AutoMapICollectionOrComplexProperties() .For("Tracks") .LazyLoad(condition: parent => parent.Id > 0, - query: (db, parent) => db.Query().Where(c => c.ItunesTrackId == parent.Id).ToList()) + query: (db, parent) => db.Query().Where(c => c.SpotifyTrackId == parent.Id).ToList()) .HasOne(file => file.Artist, file => file.AlbumId); Mapper.Entity().RegisterModel("Tracks") diff --git a/src/NzbDrone.Core/Exceptions/ArtistNotFoundException.cs b/src/NzbDrone.Core/Exceptions/ArtistNotFoundException.cs index 60a05febd..58c7ea61f 100644 --- a/src/NzbDrone.Core/Exceptions/ArtistNotFoundException.cs +++ b/src/NzbDrone.Core/Exceptions/ArtistNotFoundException.cs @@ -8,24 +8,24 @@ namespace NzbDrone.Core.Exceptions { public class ArtistNotFoundException : NzbDroneException { - public int ItunesId { get; set; } + public string SpotifyId { get; set; } - public ArtistNotFoundException(int itunesId) - : base(string.Format("Series with iTunesId {0} was not found, it may have been removed from iTunes.", itunesId)) + public ArtistNotFoundException(string spotifyId) + : base(string.Format("Artist with SpotifyId {0} was not found, it may have been removed from Spotify.", spotifyId)) { - ItunesId = itunesId; + SpotifyId = spotifyId; } - public ArtistNotFoundException(int itunesId, string message, params object[] args) + public ArtistNotFoundException(string spotifyId, string message, params object[] args) : base(message, args) { - ItunesId = itunesId; + SpotifyId = spotifyId; } - public ArtistNotFoundException(int itunesId, string message) + public ArtistNotFoundException(string spotifyId, string message) : base(message) { - ItunesId = itunesId; + SpotifyId = spotifyId; } } } diff --git a/src/NzbDrone.Core/Jobs/TaskManager.cs b/src/NzbDrone.Core/Jobs/TaskManager.cs index 3ad7b909a..ef974f612 100644 --- a/src/NzbDrone.Core/Jobs/TaskManager.cs +++ b/src/NzbDrone.Core/Jobs/TaskManager.cs @@ -16,6 +16,7 @@ using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Update.Commands; +using NzbDrone.Core.Music.Commands; namespace NzbDrone.Core.Jobs { @@ -64,9 +65,10 @@ namespace NzbDrone.Core.Jobs new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName}, new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName}, new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).FullName}, - new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}, + //new ScheduledTask{ Interval = 3*60, TypeName = typeof(UpdateSceneMappingCommand).FullName}, new ScheduledTask{ Interval = 6*60, TypeName = typeof(CheckHealthCommand).FullName}, - new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName}, + new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshArtistCommand).FullName}, + new ScheduledTask{ Interval = 12*60, TypeName = typeof(RefreshSeriesCommand).FullName}, // TODO: Remove new ScheduledTask{ Interval = 24*60, TypeName = typeof(HousekeepingCommand).FullName}, new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName}, diff --git a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs index 4ae5a3420..76810655a 100644 --- a/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs +++ b/src/NzbDrone.Core/MetadataSource/IProvideArtistInfo.cs @@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook { public interface IProvideArtistInfo { - Tuple> GetArtistInfo(int itunesId); + Tuple> GetArtistInfo(string spotifyId); } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs new file mode 100644 index 000000000..d442be7ec --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/AlbumInfoResource.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + public class AlbumInfoResource + { + public AlbumInfoResource() + { + + } + public string AlbumType { get; set; } // Might need to make this a separate class + public List Artists { get; set; } // Will always be length of 1 unless a compilation + public string Url { get; set; } // Link to the endpoint api to give full info for this object + public string Id { get; set; } // This is a unique Album ID. Needed for all future API calls + public List Images { get; set; } + public string Name { get; set; } // In case of a takedown, this may be empty + } + + +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistInfoResource.cs new file mode 100644 index 000000000..35f969001 --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistInfoResource.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + public class ArtistInfoResource + { + public ArtistInfoResource() { } + + public List Genres { get; set; } + public string AristUrl { get; set; } + public string Id { get; set; } + public List Images { get; set; } + public string Name { get; set; } + + // We may need external_urls.spotify to external linking... + } +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs index 88ad6eae6..8d98d6890 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ArtistResource.cs @@ -5,27 +5,36 @@ using System.Text; namespace NzbDrone.Core.MetadataSource.SkyHook.Resource { - public class AlbumResource - { - public AlbumResource() + + public class AristResultResource + { + public AristResultResource() { } - public string ArtistName { get; set; } - public int ArtistId { get; set; } - public string CollectionName { get; set; } - public int CollectionId { get; set; } - public string PrimaryGenreName { get; set; } - public string ArtworkUrl100 { get; set; } - public string Country { get; set; } - public string CollectionExplicitness { get; set; } - public int TrackCount { get; set; } - public string Copyright { get; set; } - public DateTime ReleaseDate { get; set; } + public List Items { get; set; } + } + + public class AlbumResultResource + { + public AlbumResultResource() + { + + } + public List Items { get; set; } } + public class TrackResultResource + { + public TrackResultResource() + { + + } + + public List Items { get; set; } + } public class ArtistResource { public ArtistResource() @@ -33,9 +42,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource } - public int ResultCount { get; set; } - public List Results { get; set; } - //public string ArtistName { get; set; } - //public List Albums { get; set; } + public AristResultResource Artists { get; set; } + public AristResultResource Albums { get; set; } } } diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ImageResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ImageResource.cs index 81a2f578e..518705757 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ImageResource.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/ImageResource.cs @@ -3,6 +3,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource public class ImageResource { public string CoverType { get; set; } + + // Spotify Mapping public string Url { get; set; } + public int Height { get; set; } + public int Width { get; set; } } } \ No newline at end of file diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs new file mode 100644 index 000000000..2f905637d --- /dev/null +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/Resource/TrackInfoResource.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.MetadataSource.SkyHook.Resource +{ + public class TrackInfoResource + { + public TrackInfoResource() + { + + } + + public int DiscNumber { get; set; } + public int DurationMs { get; set; } + public string Href { get; set; } + public string Id { get; set; } + public string Name { get; set; } + public int TrackNumber { get; set; } + public bool Explicit { get; set; } + public List Artists { get; set; } + + } +} diff --git a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs index e7b97350b..bf47348b1 100644 --- a/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs +++ b/src/NzbDrone.Core/MetadataSource/SkyHook/SkyHookProxy.cs @@ -30,6 +30,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook _logger = logger; } + + public Tuple> GetSeriesInfo(int tvdbSeriesId) { Console.WriteLine("[GetSeriesInfo] id:" + tvdbSeriesId); @@ -63,100 +65,136 @@ namespace NzbDrone.Core.MetadataSource.SkyHook public List SearchForNewSeries(string title) { - try - { - var lowerTitle = title.ToLowerInvariant(); - Console.WriteLine("Searching for " + lowerTitle); + // TODO: Remove this API + var tempList = new List(); + var tempSeries = new Series(); + tempSeries.Title = "AFI"; + tempList.Add(tempSeries); + return tempList; + } - //if (lowerTitle.StartsWith("tvdb:") || lowerTitle.StartsWith("tvdbid:")) - //{ - // var slug = lowerTitle.Split(':')[1].Trim(); - // int tvdbId; + public Tuple> GetArtistInfo(string spotifyId) + { - // if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out tvdbId) || tvdbId <= 0) - // { - // return new List(); - // } + _logger.Debug("Getting Artist with SpotifyId of {0}", spotifyId); - // try - // { - // return new List { GetSeriesInfo(tvdbId).Item1 }; - // } - // catch (SeriesNotFoundException) - // { - // return new List(); - // } - //} + ///v1/albums/{id} + // - // Majora: Temporarily, use iTunes to test. - var httpRequest = _requestBuilder.Create() - .AddQueryParam("entity", "album") - .AddQueryParam("term", title.ToLower().Trim()) - .Build(); + // We need to perform a direct lookup of the artist + var httpRequest = _requestBuilder.Create() + .SetSegment("route", "artists/" + spotifyId) + //.SetSegment("route", "search") + //.AddQueryParam("type", "artist,album") + //.AddQueryParam("q", spotifyId.ToString()) + .Build(); + + httpRequest.AllowAutoRedirect = true; + httpRequest.SuppressHttpError = true; - Console.WriteLine("httpRequest: ", httpRequest); + var httpResponse = _httpClient.Get(httpRequest); + - var httpResponse = _httpClient.Get>(httpRequest); + if (httpResponse.HasHttpError) + { + if (httpResponse.StatusCode == HttpStatusCode.NotFound) + { + throw new ArtistNotFoundException(spotifyId); + } + else + { + throw new HttpException(httpRequest, httpResponse); + } + } - //Console.WriteLine("Response: ", httpResponse.GetType()); - //_logger.Info("Response: ", httpResponse.Resource.ResultCount); + Artist artist = new Artist(); + artist.ArtistName = httpResponse.Resource.Name; + artist.SpotifyId = httpResponse.Resource.Id; + artist.Genres = httpResponse.Resource.Genres; - //_logger.Info("HTTP Response: ", httpResponse.Resource.ResultCount); - var tempList = new List(); - var tempSeries = new Series(); - tempSeries.Title = "AFI"; - tempList.Add(tempSeries); - return tempList; - - return httpResponse.Resource.SelectList(MapSeries); - } - catch (HttpException) + + artist = MapAlbums(artist); + + + // TODO: implement tracks api call + return new Tuple>(artist, new List()); + } + + private Artist MapAlbums(Artist artist) + { + + // Find all albums for the artist and all tracks for said album + ///v1/artists/{id}/albums + var httpRequest = _requestBuilder.Create() + .SetSegment("route", "artists/" + artist.SpotifyId + "/albums") + .Build(); + httpRequest.AllowAutoRedirect = true; + httpRequest.SuppressHttpError = true; + + var httpResponse = _httpClient.Get(httpRequest); + + if (httpResponse.HasHttpError) { - throw new SkyHookException("Search for '{0}' failed. Unable to communicate with SkyHook.", title); + throw new HttpException(httpRequest, httpResponse); } - catch (Exception ex) + + List albums = new List(); + foreach(var albumResource in httpResponse.Resource.Items) { - _logger.Warn(ex, ex.Message); - throw new SkyHookException("Search for '{0}' failed. Invalid response received from SkyHook.", title); + Album album = new Album(); + album.AlbumId = albumResource.Id; + album.Title = albumResource.Name; + album.ArtworkUrl = albumResource.Images[0].Url; + album.Tracks = MapTracksToAlbum(album); + albums.Add(album); } + + // TODO: We now need to get all tracks for each album + + artist.Albums = albums; + return artist; } - public Tuple> GetArtistInfo(int itunesId) + private List MapTracksToAlbum(Album album) { - Console.WriteLine("[GetArtistInfo] id:" + itunesId); - //https://itunes.apple.com/lookup?id=909253 var httpRequest = _requestBuilder.Create() - .SetSegment("route", "lookup") - .AddQueryParam("id", itunesId.ToString()) - .Build(); + .SetSegment("route", "albums/" + album.AlbumId + "/tracks") + .Build(); httpRequest.AllowAutoRedirect = true; httpRequest.SuppressHttpError = true; - var httpResponse = _httpClient.Get(httpRequest); + var httpResponse = _httpClient.Get(httpRequest); if (httpResponse.HasHttpError) { - if (httpResponse.StatusCode == HttpStatusCode.NotFound) - { - throw new ArtistNotFoundException(itunesId); - } - else - { - throw new HttpException(httpRequest, httpResponse); - } + throw new HttpException(httpRequest, httpResponse); + } + + List tracks = new List(); + foreach(var trackResource in httpResponse.Resource.Items) + { + Track track = new Track(); + track.AlbumId = album.AlbumId; + //track.Album = album; // This will cause infinite loop when trying to serialize. + // TODO: Implement more track mapping + //track.Artist = trackResource.Artists + //track.ArtistId = album. + track.Explict = trackResource.Explicit; + track.Compilation = trackResource.Artists.Count > 1; + track.TrackNumber = trackResource.TrackNumber; + track.TrackExplicitName = trackResource.Name; + track.TrackCensoredName = trackResource.Name; + tracks.Add(track); } - Console.WriteLine("GetArtistInfo, GetArtistInfo"); - //var tracks = httpResponse.Resource.Episodes.Select(MapEpisode); - //var artist = MapArtist(httpResponse.Resource); - // I don't know how we are getting tracks from iTunes yet. - return new Tuple>(MapArtists(httpResponse.Resource)[0], new List()); - //return new Tuple>(artist, tracks.ToList()); + return tracks; } + + public List SearchForNewArtist(string title) { try @@ -168,16 +206,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook { var slug = lowerTitle.Split(':')[1].Trim(); - int itunesId; - - if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out itunesId) || itunesId <= 0) + if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace)) { return new List(); } try { - return new List { GetArtistInfo(itunesId).Item1 }; + return new List { GetArtistInfo(slug).Item1 }; } catch (ArtistNotFoundException) { @@ -187,15 +223,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook var httpRequest = _requestBuilder.Create() .SetSegment("route", "search") - .AddQueryParam("entity", "album") - .AddQueryParam("term", title.ToLower().Trim()) + .AddQueryParam("type", "artist,album") + .AddQueryParam("q", title.ToLower().Trim()) .Build(); var httpResponse = _httpClient.Get(httpRequest); - return MapArtists(httpResponse.Resource); + + List artists = MapArtists(httpResponse.Resource); + + return artists; } catch (HttpException) { @@ -208,45 +247,52 @@ namespace NzbDrone.Core.MetadataSource.SkyHook } } + private Artist MapArtistInfo(ArtistInfoResource resource) + { + // This expects ArtistInfoResource, thus just need to populate one artist + Artist artist = new Artist(); + //artist.Overview = resource.artistBio; + //artist.ArtistName = resource.name; + //foreach(var genre in resource.genreNames) + //{ + // artist.Genres.Add(genre); + //} + + return artist; + } + private List MapArtists(ArtistResource resource) { - Album tempAlbum; + + List artists = new List(); - foreach (var album in resource.Results) + foreach(var artistResource in resource.Artists.Items) { - int index = artists.FindIndex(a => a.ItunesId == album.ArtistId); - tempAlbum = MapAlbum(album); + Artist artist = new Artist(); + artist.ArtistName = artistResource.Name; + artist.SpotifyId = artistResource.Id; + artist.Genres = artistResource.Genres; + //artist.ArtistSlug = a//TODO implement artistSlug mapping; + artists.Add(artist); + } - if (index >= 0) - { - artists[index].Albums.Add(tempAlbum); - } - else - { - Artist tempArtist = new Artist(); - tempArtist.ItunesId = album.ArtistId; - tempArtist.ArtistName = album.ArtistName; - tempArtist.Genres.Add(album.PrimaryGenreName); - tempArtist.Albums.Add(tempAlbum); - artists.Add(tempArtist); - } + // Maybe? Get all the albums for said artist - } return artists; } - private Album MapAlbum(AlbumResource albumQuery) - { - Album album = new Album(); - - album.AlbumId = albumQuery.CollectionId; - album.Title = albumQuery.CollectionName; - album.Year = albumQuery.ReleaseDate.Year; - album.ArtworkUrl = albumQuery.ArtworkUrl100; - album.Explicitness = albumQuery.CollectionExplicitness; - return album; - } + //private Album MapAlbum(AlbumResource albumQuery) + //{ + // Album album = new Album(); + + // album.AlbumId = albumQuery.CollectionId; + // album.Title = albumQuery.CollectionName; + // album.Year = albumQuery.ReleaseDate.Year; + // album.ArtworkUrl = albumQuery.ArtworkUrl100; + // album.Explicitness = albumQuery.CollectionExplicitness; + // return album; + //} private static Series MapSeries(ShowResource show) { diff --git a/src/NzbDrone.Core/Music/AddArtistService.cs b/src/NzbDrone.Core/Music/AddArtistService.cs index 072a67754..07f995f69 100644 --- a/src/NzbDrone.Core/Music/AddArtistService.cs +++ b/src/NzbDrone.Core/Music/AddArtistService.cs @@ -48,7 +48,7 @@ namespace NzbDrone.Core.Music if (string.IsNullOrWhiteSpace(newArtist.Path)) { - var folderName = newArtist.ArtistName;// _fileNameBuilder.GetArtistFolder(newArtist); + var folderName = newArtist.ArtistName;// TODO: _fileNameBuilder.GetArtistFolder(newArtist); newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName); } @@ -63,7 +63,7 @@ namespace NzbDrone.Core.Music throw new ValidationException(validationResult.Errors); } - _logger.Info("Adding Series {0} Path: [{1}]", newArtist, newArtist.Path); + _logger.Info("Adding Artist {0} Path: [{1}]", newArtist, newArtist.Path); _artistService.AddArtist(newArtist); return newArtist; @@ -75,22 +75,21 @@ namespace NzbDrone.Core.Music try { - tuple = _artistInfo.GetArtistInfo(newArtist.ItunesId); + tuple = _artistInfo.GetArtistInfo(newArtist.SpotifyId); } catch (SeriesNotFoundException) { - _logger.Error("tvdbid {1} was not found, it may have been removed from TheTVDB.", newArtist.ItunesId); + _logger.Error("SpotifyId {1} was not found, it may have been removed from Spotify.", newArtist.SpotifyId); throw new ValidationException(new List { - new ValidationFailure("TvdbId", "A series with this ID was not found", newArtist.ItunesId) + new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.SpotifyId) }); } var artist = tuple.Item1; - // If seasons were passed in on the new series use them, otherwise use the seasons from Skyhook - // TODO: Refactor for albums + // If albums were passed in on the new artist use them, otherwise use the albums from Skyhook newArtist.Albums = newArtist.Albums != null && newArtist.Albums.Any() ? newArtist.Albums : artist.Albums; artist.ApplyChanges(newArtist); diff --git a/src/NzbDrone.Core/Music/AddArtistValidator.cs b/src/NzbDrone.Core/Music/AddArtistValidator.cs index a21e3bac5..ab789c2fc 100644 --- a/src/NzbDrone.Core/Music/AddArtistValidator.cs +++ b/src/NzbDrone.Core/Music/AddArtistValidator.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Core.Music SeriesPathValidator seriesPathValidator, DroneFactoryValidator droneFactoryValidator, SeriesAncestorValidator seriesAncestorValidator, - ArtistSlugValidator seriesTitleSlugValidator) + ArtistSlugValidator artistTitleSlugValidator) { RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure) .IsValidPath() @@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music .SetValidator(droneFactoryValidator) .SetValidator(seriesAncestorValidator); - RuleFor(c => c.ArtistSlug).SetValidator(seriesTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName + RuleFor(c => c.ArtistSlug).SetValidator(artistTitleSlugValidator);// TODO: Check if we are going to use a slug or artistName } } } diff --git a/src/NzbDrone.Core/Music/Album.cs b/src/NzbDrone.Core/Music/Album.cs index c0c7fc19e..6d18a766b 100644 --- a/src/NzbDrone.Core/Music/Album.cs +++ b/src/NzbDrone.Core/Music/Album.cs @@ -14,10 +14,11 @@ namespace NzbDrone.Core.Music Images = new List(); } - public int AlbumId { get; set; } + public string AlbumId { get; set; } public string Title { get; set; } // NOTE: This should be CollectionName in API public int Year { get; set; } public int TrackCount { get; set; } + public List Tracks { get; set; } public int DiscCount { get; set; } public bool Monitored { get; set; } public List Images { get; set; } diff --git a/src/NzbDrone.Core/Music/Artist.cs b/src/NzbDrone.Core/Music/Artist.cs index 2fde7c0ac..000aaf928 100644 --- a/src/NzbDrone.Core/Music/Artist.cs +++ b/src/NzbDrone.Core/Music/Artist.cs @@ -22,53 +22,38 @@ namespace NzbDrone.Core.Music } - public int ItunesId { get; set; } + public string SpotifyId { get; set; } public string ArtistName { get; set; } public string ArtistSlug { get; set; } public string CleanTitle { get; set; } + public string Overview { get; set; } public bool Monitored { get; set; } public bool AlbumFolder { get; set; } public bool ArtistFolder { get; set; } public DateTime? LastInfoSync { get; set; } public DateTime? LastDiskSync { get; set; } - public int Status { get; set; } // TODO: Figure out what this is, do we need it? public string Path { get; set; } public List Images { get; set; } public List Genres { get; set; } public int QualityProfileId { get; set; } - public string RootFolderPath { get; set; } public DateTime Added { get; set; } public LazyLoaded Profile { get; set; } public int ProfileId { get; set; } public List Albums { get; set; } public HashSet Tags { get; set; } - public AddSeriesOptions AddOptions { get; set; } - //public string SortTitle { get; set; } - //public SeriesStatusType Status { get; set; } - //public int Runtime { get; set; } - //public SeriesTypes SeriesType { get; set; } - //public string Network { get; set; } - //public bool UseSceneNumbering { get; set; } - //public string TitleSlug { get; set; } - //public int Year { get; set; } - //public Ratings Ratings { get; set; } - //public List Actors { get; set; } // MOve to album? - //public string Certification { get; set; } - //public DateTime? FirstAired { get; set; } - public override string ToString() { - return string.Format("[{0}][{1}]", ItunesId, ArtistName.NullSafe()); + return string.Format("[{0}][{1}]", SpotifyId, ArtistName.NullSafe()); } public void ApplyChanges(Artist otherArtist) { - ItunesId = otherArtist.ItunesId; + SpotifyId = otherArtist.SpotifyId; ArtistName = otherArtist.ArtistName; ArtistSlug = otherArtist.ArtistSlug; CleanTitle = otherArtist.CleanTitle; @@ -87,18 +72,11 @@ namespace NzbDrone.Core.Music ArtistFolder = otherArtist.ArtistFolder; AddOptions = otherArtist.AddOptions; - - //TODO: Implement - ItunesId = otherArtist.ItunesId; - Albums = otherArtist.Albums; Path = otherArtist.Path; ProfileId = otherArtist.ProfileId; - AlbumFolder = otherArtist.AlbumFolder; Monitored = otherArtist.Monitored; - - //SeriesType = otherArtist.SeriesType; RootFolderPath = otherArtist.RootFolderPath; Tags = otherArtist.Tags; AddOptions = otherArtist.AddOptions; diff --git a/src/NzbDrone.Core/Music/ArtistAddedHandler.cs b/src/NzbDrone.Core/Music/ArtistAddedHandler.cs new file mode 100644 index 000000000..b2da66db9 --- /dev/null +++ b/src/NzbDrone.Core/Music/ArtistAddedHandler.cs @@ -0,0 +1,26 @@ +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Commands; +using NzbDrone.Core.Music.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public class ArtistAddedHandler : IHandle + { + private readonly IManageCommandQueue _commandQueueManager; + + public ArtistAddedHandler(IManageCommandQueue commandQueueManager) + { + _commandQueueManager = commandQueueManager; + } + + public void Handle(ArtistAddedEvent message) + { + _commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id)); + } + } +} diff --git a/src/NzbDrone.Core/Music/ArtistRepository.cs b/src/NzbDrone.Core/Music/ArtistRepository.cs index f9e7f9da4..0da04ad0d 100644 --- a/src/NzbDrone.Core/Music/ArtistRepository.cs +++ b/src/NzbDrone.Core/Music/ArtistRepository.cs @@ -8,7 +8,7 @@ namespace NzbDrone.Core.Music { bool ArtistPathExists(string path); Artist FindByName(string cleanTitle); - Artist FindByItunesId(int iTunesId); + Artist FindById(string spotifyId); } public class ArtistRepository : BasicRepository, IArtistRepository @@ -24,9 +24,9 @@ namespace NzbDrone.Core.Music return Query.Where(c => c.Path == path).Any(); } - public Artist FindByItunesId(int iTunesId) + public Artist FindById(string spotifyId) { - return Query.Where(s => s.ItunesId == iTunesId).SingleOrDefault(); + return Query.Where(s => s.SpotifyId == spotifyId).SingleOrDefault(); } public Artist FindByName(string cleanName) diff --git a/src/NzbDrone.Core/Music/ArtistService.cs b/src/NzbDrone.Core/Music/ArtistService.cs index 1397fad5d..bedf41f74 100644 --- a/src/NzbDrone.Core/Music/ArtistService.cs +++ b/src/NzbDrone.Core/Music/ArtistService.cs @@ -17,7 +17,7 @@ namespace NzbDrone.Core.Music Artist GetArtist(int artistId); List GetArtists(IEnumerable artistIds); Artist AddArtist(Artist newArtist); - Artist FindByItunesId(int itunesId); + Artist FindById(string spotifyId); Artist FindByName(string title); Artist FindByTitleInexact(string title); void DeleteArtist(int artistId, bool deleteFiles); @@ -69,9 +69,9 @@ namespace NzbDrone.Core.Music _eventAggregator.PublishEvent(new ArtistDeletedEvent(artist, deleteFiles)); } - public Artist FindByItunesId(int itunesId) + public Artist FindById(string spotifyId) { - return _artistRepository.FindByItunesId(itunesId); + return _artistRepository.FindById(spotifyId); } public Artist FindByName(string title) @@ -114,7 +114,7 @@ namespace NzbDrone.Core.Music if (storedAlbum != null && album.Monitored != storedAlbum.Monitored) { - _trackService.SetTrackMonitoredByAlbum(artist.Id, album.AlbumId, album.Monitored); + _trackService.SetTrackMonitoredByAlbum(artist.SpotifyId, album.AlbumId, album.Monitored); } } diff --git a/src/NzbDrone.Core/Music/Commands/RefreshArtistCommand.cs b/src/NzbDrone.Core/Music/Commands/RefreshArtistCommand.cs new file mode 100644 index 000000000..fdf3e56d6 --- /dev/null +++ b/src/NzbDrone.Core/Music/Commands/RefreshArtistCommand.cs @@ -0,0 +1,26 @@ +using NzbDrone.Core.Messaging.Commands; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Commands +{ + public class RefreshArtistCommand : Command + { + public int? ArtistId { get; set; } + + public RefreshArtistCommand() + { + } + + public RefreshArtistCommand(int? artistId) + { + ArtistId = artistId; + } + + public override bool SendUpdatesToClient => true; + + public override bool UpdateScheduledTask => !ArtistId.HasValue; + } +} diff --git a/src/NzbDrone.Core/Music/Events/ArtistRefreshStartingEvent.cs b/src/NzbDrone.Core/Music/Events/ArtistRefreshStartingEvent.cs new file mode 100644 index 000000000..45d9a50c8 --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/ArtistRefreshStartingEvent.cs @@ -0,0 +1,18 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class ArtistRefreshStartingEvent : IEvent + { + public bool ManualTrigger { get; set; } + + public ArtistRefreshStartingEvent(bool manualTrigger) + { + ManualTrigger = manualTrigger; + } + } +} diff --git a/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs b/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs new file mode 100644 index 000000000..99661c480 --- /dev/null +++ b/src/NzbDrone.Core/Music/Events/TrackInfoRefreshedEvent.cs @@ -0,0 +1,23 @@ +using NzbDrone.Common.Messaging; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music.Events +{ + public class TrackInfoRefreshedEvent : IEvent + { + public Artist Artist { get; set; } + public ReadOnlyCollection Added { get; private set; } + public ReadOnlyCollection Updated { get; private set; } + + public TrackInfoRefreshedEvent(Artist artist, IList added, IList updated) + { + Artist = artist; + Added = new ReadOnlyCollection(added); + Updated = new ReadOnlyCollection(updated); + } + } +} diff --git a/src/NzbDrone.Core/Music/RefreshArtistService.cs b/src/NzbDrone.Core/Music/RefreshArtistService.cs new file mode 100644 index 000000000..af0717437 --- /dev/null +++ b/src/NzbDrone.Core/Music/RefreshArtistService.cs @@ -0,0 +1,173 @@ +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Exceptions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.MetadataSource.SkyHook; +using NzbDrone.Core.Music.Commands; +using NzbDrone.Core.Music.Events; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public class RefreshArtistService : IExecute + { + private readonly IProvideArtistInfo _artistInfo; + private readonly IArtistService _artistService; + private readonly IRefreshTrackService _refreshTrackService; + private readonly IEventAggregator _eventAggregator; + //private readonly IDailySeriesService _dailySeriesService; + private readonly IDiskScanService _diskScanService; + //private readonly ICheckIfArtistShouldBeRefreshed _checkIfArtistShouldBeRefreshed; + private readonly Logger _logger; + + public RefreshArtistService(IProvideArtistInfo artistInfo, + IArtistService artistService, + IRefreshTrackService refreshTrackService, + IEventAggregator eventAggregator, + IDiskScanService diskScanService, + //ICheckIfArtistShouldBeRefreshed checkIfArtistShouldBeRefreshed, + Logger logger) + { + _artistInfo = artistInfo; + _artistService = artistService; + _refreshTrackService = refreshTrackService; + _eventAggregator = eventAggregator; + _diskScanService = diskScanService; + //_checkIfArtistShouldBeRefreshed = checkIfArtistShouldBeRefreshed; + _logger = logger; + } + + private void RefreshArtistInfo(Artist artist) + { + _logger.ProgressInfo("Updating Info for {0}", artist.ArtistName); + + Tuple> tuple; + + try + { + tuple = _artistInfo.GetArtistInfo(artist.SpotifyId); + } + catch (ArtistNotFoundException) + { + _logger.Error("Artist '{0}' (SpotifyId {1}) was not found, it may have been removed from Spotify.", artist.ArtistName, artist.SpotifyId); + return; + } + + var artistInfo = tuple.Item1; + + if (artist.SpotifyId != artistInfo.SpotifyId) + { + _logger.Warn("Artist '{0}' (SpotifyId {1}) was replaced with '{2}' (SpotifyId {3}), because the original was a duplicate.", artist.ArtistName, artist.SpotifyId, artistInfo.ArtistName, artistInfo.SpotifyId); + artist.SpotifyId = artistInfo.SpotifyId; + } + + artist.ArtistName = artistInfo.ArtistName; + artist.ArtistSlug = artistInfo.ArtistSlug; + artist.Overview = artistInfo.Overview; + artist.Status = artistInfo.Status; + artist.CleanTitle = artistInfo.CleanTitle; + artist.LastInfoSync = DateTime.UtcNow; + artist.Images = artistInfo.Images; + //artist.Actors = artistInfo.Actors; + artist.Genres = artistInfo.Genres; + + try + { + artist.Path = new DirectoryInfo(artist.Path).FullName; + artist.Path = artist.Path.GetActualCasing(); + } + catch (Exception e) + { + _logger.Warn(e, "Couldn't update artist path for " + artist.Path); + } + + artist.Albums = UpdateAlbums(artist, artistInfo); + + _artistService.UpdateArtist(artist); + _refreshTrackService.RefreshTrackInfo(artist, tuple.Item2); + + _logger.Debug("Finished artist refresh for {0}", artist.ArtistName); + _eventAggregator.PublishEvent(new ArtistUpdatedEvent(artist)); + } + + private List UpdateAlbums(Artist artist, Artist artistInfo) + { + var albums = artistInfo.Albums.DistinctBy(s => s.AlbumId).ToList(); + + foreach (var album in albums) + { + var existingAlbum = artist.Albums.FirstOrDefault(s => s.AlbumId == album.AlbumId); + + //Todo: Should this should use the previous season's monitored state? + if (existingAlbum == null) + { + //if (album.SeasonNumber == 0) + //{ + // album.Monitored = false; + // continue; + //} + + _logger.Debug("New album ({0}) for artist: [{1}] {2}, setting monitored to true", album.Title, artist.SpotifyId, artist.ArtistName); + album.Monitored = true; + } + + else + { + album.Monitored = existingAlbum.Monitored; + } + } + + return albums; + } + + public void Execute(RefreshArtistCommand message) + { + _eventAggregator.PublishEvent(new ArtistRefreshStartingEvent(message.Trigger == CommandTrigger.Manual)); + + if (message.ArtistId.HasValue) + { + var artist = _artistService.GetArtist(message.ArtistId.Value); + RefreshArtistInfo(artist); + } + else + { + var allArtists = _artistService.GetAllArtists().OrderBy(c => c.ArtistName).ToList(); + + foreach (var artist in allArtists) + { + if (message.Trigger == CommandTrigger.Manual /*|| _checkIfArtistShouldBeRefreshed.ShouldRefresh(artist)*/) + { + try + { + RefreshArtistInfo(artist); + } + catch (Exception e) + { + _logger.Error(e, "Couldn't refresh info for {0}", artist); + } + } + + else + { + try + { + _logger.Info("Skipping refresh of artist: {0}", artist.ArtistName); + //TODO: _diskScanService.Scan(artist); + } + catch (Exception e) + { + _logger.Error(e, "Couldn't rescan artist {0}", artist); + } + } + } + } + } + } +} diff --git a/src/NzbDrone.Core/Music/RefreshTrackService.cs b/src/NzbDrone.Core/Music/RefreshTrackService.cs new file mode 100644 index 000000000..bee0e8a5d --- /dev/null +++ b/src/NzbDrone.Core/Music/RefreshTrackService.cs @@ -0,0 +1,125 @@ +using NLog; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Messaging.Events; +using NzbDrone.Core.Music.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace NzbDrone.Core.Music +{ + public interface IRefreshTrackService + { + void RefreshTrackInfo(Artist artist, IEnumerable remoteTracks); + } + + public class RefreshTrackService : IRefreshTrackService + { + private readonly ITrackService _trackService; + private readonly IEventAggregator _eventAggregator; + private readonly Logger _logger; + + public RefreshTrackService(ITrackService trackService, IEventAggregator eventAggregator, Logger logger) + { + _trackService = trackService; + _eventAggregator = eventAggregator; + _logger = logger; + } + + public void RefreshTrackInfo(Artist artist, IEnumerable remoteTracks) + { + _logger.Info("Starting track info refresh for: {0}", artist); + var successCount = 0; + var failCount = 0; + + var existingTracks = _trackService.GetTrackByArtist(artist.SpotifyId); + var albums = artist.Albums; + + var updateList = new List(); + var newList = new List(); + var dupeFreeRemoteTracks = remoteTracks.DistinctBy(m => new { m.AlbumId, m.TrackNumber }).ToList(); + + foreach (var track in OrderTracks(artist, dupeFreeRemoteTracks)) + { + try + { + var trackToUpdate = GetTrackToUpdate(artist, track, existingTracks); + + if (trackToUpdate != null) + { + existingTracks.Remove(trackToUpdate); + updateList.Add(trackToUpdate); + } + else + { + trackToUpdate = new Track(); + trackToUpdate.Monitored = GetMonitoredStatus(track, albums); + newList.Add(trackToUpdate); + } + trackToUpdate.ArtistId = artist.SpotifyId; // TODO: Ensure LazyLoaded field gets updated. + trackToUpdate.TrackNumber = track.TrackNumber; + trackToUpdate.Title = track.Title ?? "Unknown"; + + // TODO: Implement rest of [RefreshTrackService] fields + + + + successCount++; + } + catch (Exception e) + { + _logger.Fatal(e, "An error has occurred while updating track info for artist {0}. {1}", artist, track); + failCount++; + } + } + + var allTracks = new List(); + allTracks.AddRange(newList); + allTracks.AddRange(updateList); + + // TODO: See if anything needs to be done here + //AdjustMultiEpisodeAirTime(artist, allTracks); + //AdjustDirectToDvdAirDate(artist, allTracks); + + _trackService.DeleteMany(existingTracks); + _trackService.UpdateMany(updateList); + _trackService.InsertMany(newList); + + _eventAggregator.PublishEvent(new TrackInfoRefreshedEvent(artist, newList, updateList)); + + if (failCount != 0) + { + _logger.Info("Finished track refresh for artist: {0}. Successful: {1} - Failed: {2} ", + artist.ArtistName, successCount, failCount); + } + else + { + _logger.Info("Finished track refresh for artist: {0}.", artist); + } + } + + private bool GetMonitoredStatus(Track track, IEnumerable albums) + { + if (track.TrackNumber == 0 /*&& track.AlbumId != 1*/) + { + return false; + } + + var album = albums.SingleOrDefault(c => c.AlbumId == track.AlbumId); + return album == null || album.Monitored; + } + + + private Track GetTrackToUpdate(Artist artist, Track track, List existingTracks) + { + return existingTracks.FirstOrDefault(e => e.AlbumId == track.AlbumId && e.TrackNumber == track.TrackNumber); + } + + private IEnumerable OrderTracks(Artist artist, List tracks) + { + return tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber); + } + } +} + diff --git a/src/NzbDrone.Core/Music/Track.cs b/src/NzbDrone.Core/Music/Track.cs index 65e28231b..f1f563306 100644 --- a/src/NzbDrone.Core/Music/Track.cs +++ b/src/NzbDrone.Core/Music/Track.cs @@ -17,9 +17,10 @@ namespace NzbDrone.Core.Music public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; - public int ItunesTrackId { get; set; } - public int AlbumId { get; set; } - public LazyLoaded ArtistsId { get; set; } + public int SpotifyTrackId { get; set; } + public string AlbumId { get; set; } + public LazyLoaded Artist { get; set; } + public string ArtistId { get; set; } public int CompilationId { get; set; } public bool Compilation { get; set; } public int TrackNumber { get; set; } @@ -28,11 +29,10 @@ namespace NzbDrone.Core.Music public bool Explict { get; set; } public string TrackExplicitName { get; set; } public string TrackCensoredName { get; set; } - public string Monitored { get; set; } - public int TrackFileId { get; set; } // JVM: Is this needed with TrackFile reference? + public bool Monitored { get; set; } + public int TrackFileId { get; set; } public DateTime? ReleaseDate { get; set; } - /*public int? SceneEpisodeNumber { get; set; } - public bool UnverifiedSceneNumbering { get; set; } + /* public Ratings Ratings { get; set; } // This might be aplicable as can be pulled from IDv3 tags public List Images { get; set; }*/ @@ -46,7 +46,7 @@ namespace NzbDrone.Core.Music public override string ToString() { - return string.Format("[{0}]{1}", ItunesTrackId, Title.NullSafe()); + return string.Format("[{0}]{1}", SpotifyTrackId, Title.NullSafe()); } } } diff --git a/src/NzbDrone.Core/Music/TrackService.cs b/src/NzbDrone.Core/Music/TrackService.cs index 91bdeb5f7..b8bbbafa7 100644 --- a/src/NzbDrone.Core/Music/TrackService.cs +++ b/src/NzbDrone.Core/Music/TrackService.cs @@ -10,12 +10,12 @@ namespace NzbDrone.Core.Music { Track GetTrack(int id); List GetTracks(IEnumerable ids); - Track FindTrack(int artistId, int albumId, int trackNumber); - Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); - List GetTrackByArtist(int artistId); - List GetTracksByAblum(int artistId, int albumId); - List GetTracksByAblumTitle(int artistId, string albumTitle); - List TracksWithFiles(int artistId); + Track FindTrack(string artistId, string albumId, int trackNumber); + Track FindTrackByTitle(string artistId, string albumId, string releaseTitle); + List GetTrackByArtist(string artistId); + List GetTracksByAlbum(string artistId, string albumId); + List GetTracksByAlbumTitle(string artistId, string albumTitle); + List TracksWithFiles(string artistId); PagingSpec TracksWithoutFiles(PagingSpec pagingSpec); List GeTracksByFileId(int trackFileId); void UpdateTrack(Track track); @@ -24,7 +24,7 @@ namespace NzbDrone.Core.Music void InsertMany(List tracks); void UpdateMany(List tracks); void DeleteMany(List tracks); - void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored); + void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored); } public class TrackService : ITrackService @@ -34,12 +34,12 @@ namespace NzbDrone.Core.Music throw new NotImplementedException(); } - public Track FindTrack(int artistId, int albumId, int trackNumber) + public Track FindTrack(string artistId, string albumId, int trackNumber) { throw new NotImplementedException(); } - public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle) + public Track FindTrackByTitle(string artistId, string albumId, string releaseTitle) { throw new NotImplementedException(); } @@ -54,7 +54,7 @@ namespace NzbDrone.Core.Music throw new NotImplementedException(); } - public List GetTrackByArtist(int artistId) + public List GetTrackByArtist(string artistId) { throw new NotImplementedException(); } @@ -64,12 +64,12 @@ namespace NzbDrone.Core.Music throw new NotImplementedException(); } - public List GetTracksByAblum(int artistId, int albumId) + public List GetTracksByAlbum(string artistId, string albumId) { throw new NotImplementedException(); } - public List GetTracksByAblumTitle(int artistId, string albumTitle) + public List GetTracksByAlbumTitle(string artistId, string albumTitle) { throw new NotImplementedException(); } @@ -84,12 +84,12 @@ namespace NzbDrone.Core.Music throw new NotImplementedException(); } - public void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored) + public void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored) { throw new NotImplementedException(); } - public List TracksWithFiles(int artistId) + public List TracksWithFiles(string artistId) { throw new NotImplementedException(); } diff --git a/src/NzbDrone.Core/NzbDrone.Core.csproj b/src/NzbDrone.Core/NzbDrone.Core.csproj index 678b2e8da..d263b8013 100644 --- a/src/NzbDrone.Core/NzbDrone.Core.csproj +++ b/src/NzbDrone.Core/NzbDrone.Core.csproj @@ -815,6 +815,8 @@ + + @@ -822,6 +824,7 @@ + @@ -851,14 +854,20 @@ + + + + + + diff --git a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs index e3577527d..2f8b35588 100644 --- a/src/NzbDrone.Core/Parser/Model/LocalTrack.cs +++ b/src/NzbDrone.Core/Parser/Model/LocalTrack.cs @@ -24,7 +24,7 @@ namespace NzbDrone.Core.Parser.Model public MediaInfoModel MediaInfo { get; set; } public bool ExistingFile { get; set; } - public int Album + public string Album { get { @@ -32,7 +32,7 @@ namespace NzbDrone.Core.Parser.Model } } - public bool IsSpecial => Album == 0; + public bool IsSpecial => Album != ""; public override string ToString() { diff --git a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs index 4a56bd072..3260895c5 100644 --- a/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs +++ b/src/NzbDrone.Core/Validation/Paths/ArtistExistsValidator.cs @@ -12,7 +12,7 @@ namespace NzbDrone.Core.Validation.Paths private readonly IArtistService _artistService; public ArtistExistsValidator(IArtistService artistService) - : base("This artist has already been added") + : base("This artist has already been added.") { _artistService = artistService; } @@ -21,9 +21,7 @@ namespace NzbDrone.Core.Validation.Paths { if (context.PropertyValue == null) return true; - var itunesId = Convert.ToInt32(context.PropertyValue.ToString()); - - return (!_artistService.GetAllArtists().Exists(s => s.ItunesId == itunesId)); + return (!_artistService.GetAllArtists().Exists(s => s.SpotifyId == context.PropertyValue.ToString())); } } } diff --git a/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs b/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs index 08ef40273..5372a9680 100644 --- a/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs +++ b/src/NzbDrone.Update/UpdateEngine/StartNzbDrone.cs @@ -40,7 +40,7 @@ namespace NzbDrone.Update.UpdateEngine } catch (InvalidOperationException e) { - _logger.Warn(e, "Couldn't start Lidarr Service (Most likely due to permission issues). falling back to console."); + _logger.Warn(e, "Couldn't start Lidarr Service (Most likely due to permission issues). Falling back to console."); StartConsole(installationFolder); } } diff --git a/src/UI/AddSeries/AddSeriesLayout.js b/src/UI/AddSeries/AddSeriesLayout.js index 166aedb5a..949ae4942 100644 --- a/src/UI/AddSeries/AddSeriesLayout.js +++ b/src/UI/AddSeries/AddSeriesLayout.js @@ -6,7 +6,7 @@ var ExistingSeriesCollectionView = require('./Existing/AddExistingSeriesCollecti var AddSeriesView = require('./AddSeriesView'); var ProfileCollection = require('../Profile/ProfileCollection'); var RootFolderCollection = require('./RootFolders/RootFolderCollection'); -require('../Series/SeriesCollection'); +require('../Artist/ArtistCollection'); module.exports = Marionette.Layout.extend({ template : 'AddSeries/AddSeriesLayoutTemplate', diff --git a/src/UI/AddSeries/SearchResultView.js b/src/UI/AddSeries/SearchResultView.js index aaef92a1f..8c4c70e7e 100644 --- a/src/UI/AddSeries/SearchResultView.js +++ b/src/UI/AddSeries/SearchResultView.js @@ -223,12 +223,12 @@ var view = Marionette.ItemView.extend({ self.close(); Messenger.show({ - message : 'Added: ' + self.model.get('title'), + message : 'Added: ' + self.model.get('artistName'), actions : { goToSeries : { label : 'Go to Artist', action : function() { - Backbone.history.navigate('/artist/' + self.model.get('titleSlug'), { trigger : true }); + Backbone.history.navigate('/artist/' + self.model.get('artistSlug'), { trigger : true }); } } }, diff --git a/src/UI/Artist/ArtistController.js b/src/UI/Artist/ArtistController.js index 838018670..4805bd9bf 100644 --- a/src/UI/Artist/ArtistController.js +++ b/src/UI/Artist/ArtistController.js @@ -1,34 +1,33 @@ var NzbDroneController = require('../Shared/NzbDroneController'); var AppLayout = require('../AppLayout'); var ArtistCollection = require('./ArtistCollection'); -var SeriesIndexLayout = require('./Index/SeriesIndexLayout'); +var SeriesIndexLayout = require('../Series/Index/SeriesIndexLayout'); var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout'); module.exports = NzbDroneController.extend({ _originalInit : NzbDroneController.prototype.initialize, initialize : function() { - this.route('', this.series); - this.route('artist', this.series); - this.route('artist/:query', this.seriesDetails); + this.route('', this.artist); + this.route('artist', this.artist); + this.route('artist/:query', this.artistDetails); this._originalInit.apply(this, arguments); }, artist : function() { this.setTitle('Lidarr'); - this.setArtistName('Lidarr'); this.showMainRegion(new SeriesIndexLayout()); }, - seriesDetails : function(query) { + artistDetails : function(query) { var artists = ArtistCollection.where({ artistNameSlug : query }); - console.log('seriesDetails, artists: ', artists); + console.log('artistDetails, artists: ', artists); if (artists.length !== 0) { var targetSeries = artists[0]; console.log("[ArtistController] targetSeries: ", targetSeries); - this.setTitle(targetSeries.get('title')); - this.setArtistName(targetSeries.get('artistName')); + this.setTitle(targetSeries.get('artistName')); // TODO: Update NzbDroneController + //this.setArtistName(targetSeries.get('artistName')); this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries })); } else { this.showNotFound(); diff --git a/src/UI/Handlebars/Helpers/Series.js b/src/UI/Handlebars/Helpers/Series.js index ff3ffd7f1..4003a7e5b 100644 --- a/src/UI/Handlebars/Helpers/Series.js +++ b/src/UI/Handlebars/Helpers/Series.js @@ -71,6 +71,28 @@ Handlebars.registerHelper('seasonCountHelper', function() { return new Handlebars.SafeString('{0} Seasons'.format(seasonCount)); }); +Handlebars.registerHelper ('truncate', function (str, len) { + if (str && str.length > len && str.length > 0) { + var new_str = str + " "; + new_str = str.substr (0, len); + new_str = str.substr (0, new_str.lastIndexOf(" ")); + new_str = (new_str.length > 0) ? new_str : str.substr (0, len); + + return new Handlebars.SafeString ( new_str +'...' ); + } + return str; +}); + +Handlebars.registerHelper('albumCountHelper', function() { + var albumCount = this.albumCount; + + if (albumCount === 1) { + return new Handlebars.SafeString('{0} Albums'.format(albumCount)); + } + + return new Handlebars.SafeString('{0} Albums'.format(albumCount)); +}); + /*Handlebars.registerHelper('titleWithYear', function() { if (this.title.endsWith(' ({0})'.format(this.year))) { return this.title; diff --git a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs index 1bc7f1a3b..19da3e335 100644 --- a/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs +++ b/src/UI/Series/Index/Overview/SeriesOverviewItemViewTemplate.hbs @@ -8,24 +8,22 @@
-
- -
- {{overview}} -
-
+
+
+ {{truncate overview 600}} +
@@ -35,21 +33,26 @@
- {{#if_eq status compare="ended"}} + + - {{seasonCountHelper}} + {{albumCountHelper}} {{profile profileId}}
{{> EpisodeProgressPartial }}
+
+ Path {{path}} +
diff --git a/src/UI/Series/Index/SeriesIndexLayout.js b/src/UI/Series/Index/SeriesIndexLayout.js index 9cd77d08e..a8ae3d61c 100644 --- a/src/UI/Series/Index/SeriesIndexLayout.js +++ b/src/UI/Series/Index/SeriesIndexLayout.js @@ -4,7 +4,7 @@ var Backgrid = require('backgrid'); var PosterCollectionView = require('./Posters/SeriesPostersCollectionView'); var ListCollectionView = require('./Overview/SeriesOverviewCollectionView'); var EmptyView = require('./EmptyView'); -var SeriesCollection = require('../SeriesCollection'); +var ArtistCollection = require('../../Artist/ArtistCollection'); var RelativeDateCell = require('../../Cells/RelativeDateCell'); var SeriesTitleCell = require('../../Cells/SeriesTitleCell'); var TemplatedCell = require('../../Cells/TemplatedCell'); @@ -20,6 +20,7 @@ require('../../Mixins/backbone.signalr.mixin'); module.exports = Marionette.Layout.extend({ template : 'Series/Index/SeriesIndexLayoutTemplate', + regions : { seriesRegion : '#x-series', toolbar : '#x-toolbar', @@ -111,28 +112,28 @@ module.exports = Marionette.Layout.extend({ }, initialize : function() { - this.seriesCollection = SeriesCollection.clone(); - this.seriesCollection.shadowCollection.bindSignalR(); + this.artistCollection = ArtistCollection.clone(); + this.artistCollection.shadowCollection.bindSignalR(); - this.listenTo(this.seriesCollection.shadowCollection, 'sync', function(model, collection, options) { - this.seriesCollection.fullCollection.resetFiltered(); + this.listenTo(this.artistCollection, 'sync', function(model, collection, options) { + this.artistCollection.fullCollection.resetFiltered(); this._renderView(); }); - this.listenTo(this.seriesCollection.shadowCollection, 'add', function(model, collection, options) { - this.seriesCollection.fullCollection.resetFiltered(); + this.listenTo(this.artistCollection, 'add', function(model, collection, options) { + this.artistCollection.fullCollection.resetFiltered(); this._renderView(); }); - this.listenTo(this.seriesCollection.shadowCollection, 'remove', function(model, collection, options) { - this.seriesCollection.fullCollection.resetFiltered(); + this.listenTo(this.artistCollection, 'remove', function(model, collection, options) { + this.artistCollection.fullCollection.resetFiltered(); this._renderView(); }); this.sortingOptions = { type : 'sorting', storeState : false, - viewCollection : this.seriesCollection, + viewCollection : this.artistCollection, items : [ { title : 'Title', @@ -243,7 +244,7 @@ module.exports = Marionette.Layout.extend({ _showTable : function() { this.currentView = new Backgrid.Grid({ - collection : this.seriesCollection, + collection : this.artistCollection, columns : this.columns, className : 'table table-hover' }); @@ -253,7 +254,7 @@ module.exports = Marionette.Layout.extend({ _showList : function() { this.currentView = new ListCollectionView({ - collection : this.seriesCollection + collection : this.artistCollection }); this._renderView(); @@ -261,14 +262,15 @@ module.exports = Marionette.Layout.extend({ _showPosters : function() { this.currentView = new PosterCollectionView({ - collection : this.seriesCollection + collection : this.artistCollection }); this._renderView(); }, _renderView : function() { - if (SeriesCollection.length === 0) { + // Problem is this is calling before artistCollection has updated. Where are the promises with backbone? + if (this.artistCollection.length === 0) { this.seriesRegion.show(new EmptyView()); this.toolbar.close(); @@ -282,13 +284,14 @@ module.exports = Marionette.Layout.extend({ }, _fetchCollection : function() { - this.seriesCollection.fetch(); + this.artistCollection.fetch(); + console.log('index page, collection: ', this.artistCollection); }, _setFilter : function(buttonContext) { var mode = buttonContext.model.get('key'); - this.seriesCollection.setFilterMode(mode); + this.artistCollection.setFilterMode(mode); }, _showToolbar : function() { @@ -317,22 +320,22 @@ module.exports = Marionette.Layout.extend({ _showFooter : function() { var footerModel = new FooterModel(); - var series = SeriesCollection.models.length; + var series = this.artistCollection.models.length; var episodes = 0; var episodeFiles = 0; var ended = 0; var continuing = 0; var monitored = 0; - _.each(SeriesCollection.models, function(model) { - episodes += model.get('episodeCount'); + _.each(this.artistCollection.models, function(model) { + episodes += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks episodeFiles += model.get('episodeFileCount'); - if (model.get('status').toLowerCase() === 'ended') { + /*if (model.get('status').toLowerCase() === 'ended') { ended++; } else { continuing++; - } + }*/ if (model.get('monitored')) { monitored++; diff --git a/src/UI/Series/series.less b/src/UI/Series/series.less index c023a7da5..f17229f6f 100644 --- a/src/UI/Series/series.less +++ b/src/UI/Series/series.less @@ -8,6 +8,12 @@ max-width: 100%; } +.truncate { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + .edit-series-modal, .delete-series-modal { overflow : visible; diff --git a/src/UI/System/Info/MoreInfo/MoreInfoViewTemplate.hbs b/src/UI/System/Info/MoreInfo/MoreInfoViewTemplate.hbs index 40c89af35..b74f00ac5 100644 --- a/src/UI/System/Info/MoreInfo/MoreInfoViewTemplate.hbs +++ b/src/UI/System/Info/MoreInfo/MoreInfoViewTemplate.hbs @@ -25,8 +25,8 @@
Consultation - Galli-leo
Feature Requests
-
feathub.com/mattman86/Lidarr
-
github.com/mattman86/Lidarr/issues (Please post issues on the forum first and not on github)
+
feathub.com/lidarr/Lidarr
+
github.com/lidarr/Lidarr/issues (Please post issues on the forum first and not on github)
diff --git a/src/UI/main.js b/src/UI/main.js index f46f68b93..40b9e9efe 100644 --- a/src/UI/main.js +++ b/src/UI/main.js @@ -5,7 +5,7 @@ var RouteBinder = require('./jQuery/RouteBinder'); var SignalRBroadcaster = require('./Shared/SignalRBroadcaster'); var NavbarLayout = require('./Navbar/NavbarLayout'); var AppLayout = require('./AppLayout'); -var SeriesController = require('./Series/SeriesController'); +var ArtistController = require('./Artist/ArtistController'); var Router = require('./Router'); var ModalController = require('./Shared/Modal/ModalController'); var ControlPanelController = require('./Shared/ControlPanel/ControlPanelController'); @@ -20,7 +20,7 @@ require('./Hotkeys/Hotkeys'); require('./Shared/piwikCheck'); require('./Shared/VersionChangeMonitor'); -new SeriesController(); +new ArtistController(); new ModalController(); new ControlPanelController(); new Router(); diff --git a/src/UI/vent.js b/src/UI/vent.js index 1b9346529..0baf8d0d4 100644 --- a/src/UI/vent.js +++ b/src/UI/vent.js @@ -5,6 +5,8 @@ var vent = new Wreqr.EventAggregator(); vent.Events = { SeriesAdded : 'series:added', SeriesDeleted : 'series:deleted', + ArtistAdded : 'artist:added', + ArtistDeleted : 'artist:deleted', CommandComplete : 'command:complete', ServerUpdated : 'server:updated', EpisodeFileDeleted : 'episodefile:deleted'