Merge branch 'develop' of https://github.com/mattman86/Lidarr into develop

pull/2/head
Matthew Despain 7 years ago
commit f50ce4abf9

@ -2,4 +2,4 @@
Provide a description of the feature request or bug, the more details the better. 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)

2
.gitignore vendored

@ -101,7 +101,7 @@ App_Data/*.ldf
_NCrunch_* _NCrunch_*
_TeamCity* _TeamCity*
# Sonarr # Lidarr
config.xml config.xml
nzbdrone.log*txt nzbdrone.log*txt
UpdateLogs/ UpdateLogs/

@ -1 +1 @@
Sonarr Lidarr

@ -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. 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 ## ## 1. Definitions ##

@ -1,6 +1,6 @@
# How to Contribute # # 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 ## ## Documentation ##
Setup guides, FAQ, the more information we have on the wiki the better. 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 ### ### 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)* 2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
3. Run `npm install` 3. Run `npm install`
4. Run `npm start` - Used to compile the UI components and copy them. 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 5. Compile in Visual Studio
### Contributing Code ### ### 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) - 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 Sonarr's develop branch, don't merge - Rebase from Lidarr's develop branch, don't merge
- Make meaningful commits, or squash them - 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 - 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 - Reach out to us on the forums or on IRC if you have any questions

@ -18,13 +18,13 @@ Lidarr is a music collection manager for Usenet and BitTorrent users. It can mon
## Feature Requests ## 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: ## Configuring Development Environment:
### Requirements ### 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) * [Git](https://git-scm.com/downloads)
* [NodeJS](https://nodejs.org/en/download/) * [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` * Grab the submodules `git submodule init && git submodule update`
* Install the required Node Packages `npm install` * 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. * 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.* *Please note gulp must be running at all times while you are working with Lidarr client source files.*

8
debian/control vendored

@ -2,11 +2,11 @@ Section: web
Priority: optional Priority: optional
Maintainer: Sonarr <contact@nzbdrone.com> Maintainer: Sonarr <contact@nzbdrone.com>
Source: nzbdrone Source: nzbdrone
Homepage: https://sonarr.tv Homepage: https://lidarr.audio
Vcs-Git: git@github.com:Sonarr/Sonarr.git Vcs-Git: git@github.com:lidarr/Lidarr.git
Vcs-Browser: https://github.com/Sonarr/Sonarr Vcs-Browser: https://github.com/lidarr/Lidarr
Package: nzbdrone Package: nzbdrone
Architecture: all Architecture: all
Depends: libmono-cil-dev (>= 3.2), sqlite3 (>= 3.7), mediainfo (>= 0.7.52) 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

4
debian/copyright vendored

@ -1,9 +1,9 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: nzbdrone Upstream-Name: nzbdrone
Source: https://github.com/Sonarr/Sonarr Source: https://github.com/lidarr/Lidarr
Files: * Files: *
Copyright: 2010-2016 Sonarr <hello@sonarr.tv> Copyright: 2010-2016 Lidarr <hello@lidarr.audio>
License: GPL-3.0+ License: GPL-3.0+

@ -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 // you can use this if you don't care about the server code and just want to work
// with the web code. // 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); console.log('Checking for latest version:', url);

@ -1,7 +1,7 @@
{ {
"name": "Sonarr", "name": "Lidarr",
"version": "2.0.0", "version": "1.0.0",
"description": "Sonarr", "description": "Lidarr",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"build": "gulp build", "build": "gulp build",
@ -9,7 +9,7 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/Sonarr/Sonarr.git" "url": "git://github.com/lidarr/Lidarr.git"
}, },
"author": "", "author": "",
"license": "GPL-3.0", "license": "GPL-3.0",

@ -1,10 +1,10 @@
; Script generated by the Inno Setup Script Wizard. ; Script generated by the Inno Setup Script Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define AppName "Sonarr" #define AppName "Lidarr"
#define AppPublisher "Team Sonarr" #define AppPublisher "Team Lidarr"
#define AppURL "https://sonarr.tv/" #define AppURL "https://lidarr.audio/"
#define ForumsURL "https://forums.sonarr.tv/" #define ForumsURL "https://forums.lidarr.audio/"
#define AppExeName "NzbDrone.exe" #define AppExeName "NzbDrone.exe"
#define BuildNumber "2.0" #define BuildNumber "2.0"
#define BuildNumber GetEnv('BUILD_NUMBER') #define BuildNumber GetEnv('BUILD_NUMBER')

@ -8,7 +8,7 @@ namespace NzbDrone.Api.Music
{ {
public class AlbumResource public class AlbumResource
{ {
public int AlbumId { get; set; } public string AlbumId { get; set; }
public string AlbumName { get; set; } public string AlbumName { get; set; }
public bool Monitored { get; set; } public bool Monitored { get; set; }
public int Year { get; set; } public int Year { get; set; }

@ -71,7 +71,7 @@ namespace NzbDrone.Api.Music
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace()); 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.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(); PutValidator.RuleFor(s => s.Path).IsValidPath();
} }
@ -98,7 +98,6 @@ namespace NzbDrone.Api.Music
{ {
//var seriesStats = _seriesStatisticsService.SeriesStatistics(); //var seriesStats = _seriesStatisticsService.SeriesStatistics();
var artistResources = _artistService.GetAllArtists().ToResource(); var artistResources = _artistService.GetAllArtists().ToResource();
MapCoversToLocal(artistResources.ToArray()); MapCoversToLocal(artistResources.ToArray());
//LinkSeriesStatistics(seriesResources, seriesStats); //LinkSeriesStatistics(seriesResources, seriesStats);
//PopulateAlternateTitles(seriesResources); //PopulateAlternateTitles(seriesResources);
@ -106,9 +105,9 @@ namespace NzbDrone.Api.Music
return artistResources; 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; return _addSeriesService.AddArtist(model).Id;
} }
@ -175,16 +174,6 @@ namespace NzbDrone.Api.Music
BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); 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) //public void Handle(MediaCoversUpdatedEvent message)
//{ //{
// BroadcastResourceChange(ModelAction.Updated, message.Artist.Id); // BroadcastResourceChange(ModelAction.Updated, message.Artist.Id);

@ -19,9 +19,8 @@ namespace NzbDrone.Api.Music
//View Only //View Only
public string ArtistName { get; set; } public string ArtistName { get; set; }
public int ItunesId { get; set; } public string SpotifyId { get; set; }
//public List<AlternateTitleResource> AlternateTitles { get; set; } public string Overview { get; set; }
//public string SortTitle { get; set; }
public int AlbumCount public int AlbumCount
{ {
@ -29,7 +28,7 @@ namespace NzbDrone.Api.Music
{ {
if (Albums == null) return 0; 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 //EpisodeFileCount
//SizeOnDisk //SizeOnDisk
//Status = resource.Status, //Status = resource.Status,
//Overview = resource.Overview, Overview = model.Overview,
//NextAiring //NextAiring
//PreviousAiring //PreviousAiring
//Network = resource.Network, //Network = resource.Network,
@ -106,7 +105,7 @@ namespace NzbDrone.Api.Music
//FirstAired = resource.FirstAired, //FirstAired = resource.FirstAired,
//LastInfoSync = resource.LastInfoSync, //LastInfoSync = resource.LastInfoSync,
//SeriesType = resource.SeriesType, //SeriesType = resource.SeriesType,
ItunesId = model.ItunesId, SpotifyId = model.SpotifyId,
ArtistSlug = model.ArtistSlug, ArtistSlug = model.ArtistSlug,
RootFolderPath = model.RootFolderPath, RootFolderPath = model.RootFolderPath,
@ -135,7 +134,7 @@ namespace NzbDrone.Api.Music
//EpisodeFileCount //EpisodeFileCount
//SizeOnDisk //SizeOnDisk
//Status = resource.Status, //Status = resource.Status,
//Overview = resource.Overview, Overview = resource.Overview,
//NextAiring //NextAiring
//PreviousAiring //PreviousAiring
//Network = resource.Network, //Network = resource.Network,
@ -150,16 +149,8 @@ namespace NzbDrone.Api.Music
ArtistFolder = resource.ArtistFolder, ArtistFolder = resource.ArtistFolder,
Monitored = resource.Monitored, Monitored = resource.Monitored,
//UseSceneNumbering = resource.UseSceneNumbering,
//Runtime = resource.Runtime,
//TvdbId = resource.TvdbId,
//TvRageId = resource.TvRageId,
//TvMazeId = resource.TvMazeId,
//FirstAired = resource.FirstAired,
//LastInfoSync = resource.LastInfoSync, //LastInfoSync = resource.LastInfoSync,
//SeriesType = resource.SeriesType, SpotifyId = resource.SpotifyId,
ItunesId = resource.ItunesId,
ArtistSlug = resource.ArtistSlug, ArtistSlug = resource.ArtistSlug,
RootFolderPath = resource.RootFolderPath, RootFolderPath = resource.RootFolderPath,

@ -6,6 +6,7 @@ namespace NzbDrone.Common.Cloud
{ {
IHttpRequestBuilderFactory Services { get; } IHttpRequestBuilderFactory Services { get; }
IHttpRequestBuilderFactory Search { get; } IHttpRequestBuilderFactory Search { get; }
IHttpRequestBuilderFactory InternalSearch { get; }
IHttpRequestBuilderFactory SkyHookTvdb { get; } IHttpRequestBuilderFactory SkyHookTvdb { get; }
} }
@ -16,7 +17,11 @@ namespace NzbDrone.Common.Cloud
Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/") Services = new HttpRequestBuilder("http://services.lidarr.tv/v1/")
.CreateFactory(); .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(); .CreateFactory();
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.lidarr.tv/v1/tvdb/{route}/{language}/") 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 Search { get; }
public IHttpRequestBuilderFactory InternalSearch { get; }
public IHttpRequestBuilderFactory SkyHookTvdb { get; } public IHttpRequestBuilderFactory SkyHookTvdb { get; }
} }
} }

@ -13,11 +13,12 @@ namespace NzbDrone.Core.Datastore.Migration
protected override void MainDbUpgrade() protected override void MainDbUpgrade()
{ {
Create.TableForModel("Artist") Create.TableForModel("Artist")
.WithColumn("ItunesId").AsInt32().Unique() .WithColumn("SpotifyId").AsString().Nullable().Unique()
.WithColumn("ArtistName").AsString().Unique() .WithColumn("ArtistName").AsString().Unique()
.WithColumn("ArtistSlug").AsString().Nullable() //.Unique() .WithColumn("ArtistSlug").AsString().Nullable() //.Unique()
.WithColumn("CleanTitle").AsString().Nullable() // Do we need this? .WithColumn("CleanTitle").AsString().Nullable() // Do we need this?
.WithColumn("Monitored").AsBoolean() .WithColumn("Monitored").AsBoolean()
.WithColumn("Overview").AsString().Nullable()
.WithColumn("AlbumFolder").AsBoolean().Nullable() .WithColumn("AlbumFolder").AsBoolean().Nullable()
.WithColumn("ArtistFolder").AsBoolean().Nullable() .WithColumn("ArtistFolder").AsBoolean().Nullable()
.WithColumn("LastInfoSync").AsDateTime().Nullable() .WithColumn("LastInfoSync").AsDateTime().Nullable()
@ -36,8 +37,8 @@ namespace NzbDrone.Core.Datastore.Migration
; ;
Create.TableForModel("Albums") Create.TableForModel("Albums")
.WithColumn("AlbumId").AsInt32() .WithColumn("AlbumId").AsString().Unique()
.WithColumn("ArtistId").AsInt32() .WithColumn("ArtistId").AsInt32() // Should this be artistId (string)
.WithColumn("Title").AsString() .WithColumn("Title").AsString()
.WithColumn("Year").AsInt32() .WithColumn("Year").AsInt32()
.WithColumn("Image").AsInt32() .WithColumn("Image").AsInt32()
@ -48,12 +49,13 @@ namespace NzbDrone.Core.Datastore.Migration
Create.TableForModel("Tracks") Create.TableForModel("Tracks")
.WithColumn("ItunesTrackId").AsInt32().Unique() .WithColumn("ItunesTrackId").AsInt32().Unique()
.WithColumn("AlbumId").AsInt32() .WithColumn("AlbumId").AsString()
.WithColumn("ArtistsId").AsString().Nullable() .WithColumn("ArtistsId").AsString().Nullable()
.WithColumn("TrackNumber").AsInt32() .WithColumn("TrackNumber").AsInt32()
.WithColumn("Title").AsString().Nullable() .WithColumn("Title").AsString().Nullable()
.WithColumn("Ignored").AsBoolean().Nullable() .WithColumn("Ignored").AsBoolean().Nullable()
.WithColumn("Explict").AsBoolean() .WithColumn("Explict").AsBoolean()
.WithColumn("Monitored").AsBoolean()
.WithColumn("TrackExplicitName").AsString().Nullable() .WithColumn("TrackExplicitName").AsString().Nullable()
.WithColumn("TrackCensoredName").AsString().Nullable() .WithColumn("TrackCensoredName").AsString().Nullable()
.WithColumn("TrackFileId").AsInt32().Nullable() .WithColumn("TrackFileId").AsInt32().Nullable()

@ -8,8 +8,8 @@ namespace NzbDrone.Core.Datastore.Migration
{ {
protected override void MainDbUpgrade() protected override void MainDbUpgrade()
{ {
Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString(); Alter.Table("NamingConfig").AddColumn("ArtistFolderFormat").AsAnsiString().Nullable();
Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString(); Alter.Table("NamingConfig").AddColumn("AlbumFolderFormat").AsAnsiString().Nullable();
} }
} }
} }

@ -102,7 +102,7 @@ namespace NzbDrone.Core.Datastore
.Relationships.AutoMapICollectionOrComplexProperties() .Relationships.AutoMapICollectionOrComplexProperties()
.For("Tracks") .For("Tracks")
.LazyLoad(condition: parent => parent.Id > 0, .LazyLoad(condition: parent => parent.Id > 0,
query: (db, parent) => db.Query<Track>().Where(c => c.ItunesTrackId == parent.Id).ToList()) query: (db, parent) => db.Query<Track>().Where(c => c.SpotifyTrackId == parent.Id).ToList())
.HasOne(file => file.Artist, file => file.AlbumId); .HasOne(file => file.Artist, file => file.AlbumId);
Mapper.Entity<Track>().RegisterModel("Tracks") Mapper.Entity<Track>().RegisterModel("Tracks")

@ -8,24 +8,24 @@ namespace NzbDrone.Core.Exceptions
{ {
public class ArtistNotFoundException : NzbDroneException public class ArtistNotFoundException : NzbDroneException
{ {
public int ItunesId { get; set; } public string SpotifyId { get; set; }
public ArtistNotFoundException(int itunesId) public ArtistNotFoundException(string spotifyId)
: base(string.Format("Series with iTunesId {0} was not found, it may have been removed from iTunes.", itunesId)) : 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) : base(message, args)
{ {
ItunesId = itunesId; SpotifyId = spotifyId;
} }
public ArtistNotFoundException(int itunesId, string message) public ArtistNotFoundException(string spotifyId, string message)
: base(message) : base(message)
{ {
ItunesId = itunesId; SpotifyId = spotifyId;
} }
} }
} }

@ -16,6 +16,7 @@ using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv.Commands; using NzbDrone.Core.Tv.Commands;
using NzbDrone.Core.Update.Commands; using NzbDrone.Core.Update.Commands;
using NzbDrone.Core.Music.Commands;
namespace NzbDrone.Core.Jobs namespace NzbDrone.Core.Jobs
{ {
@ -64,9 +65,10 @@ namespace NzbDrone.Core.Jobs
new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName}, new ScheduledTask{ Interval = 1, TypeName = typeof(CheckForFinishedDownloadCommand).FullName},
new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName}, new ScheduledTask{ Interval = 5, TypeName = typeof(MessagingCleanupCommand).FullName},
new ScheduledTask{ Interval = 6*60, TypeName = typeof(ApplicationUpdateCommand).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 = 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 = 24*60, TypeName = typeof(HousekeepingCommand).FullName},
new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName}, new ScheduledTask{ Interval = 7*24*60, TypeName = typeof(BackupCommand).FullName},

@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
public interface IProvideArtistInfo public interface IProvideArtistInfo
{ {
Tuple<Artist, List<Track>> GetArtistInfo(int itunesId); Tuple<Artist, List<Track>> GetArtistInfo(string spotifyId);
} }
} }

@ -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<ArtistInfoResource> 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<ImageResource> Images { get; set; }
public string Name { get; set; } // In case of a takedown, this may be empty
}
}

@ -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<string> Genres { get; set; }
public string AristUrl { get; set; }
public string Id { get; set; }
public List<ImageResource> Images { get; set; }
public string Name { get; set; }
// We may need external_urls.spotify to external linking...
}
}

@ -5,27 +5,36 @@ using System.Text;
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{ {
public class AlbumResource
{ public class AristResultResource
public AlbumResource() {
public AristResultResource()
{ {
} }
public string ArtistName { get; set; } public List<ArtistInfoResource> Items { get; set; }
public int ArtistId { get; set; } }
public string CollectionName { get; set; }
public int CollectionId { get; set; } public class AlbumResultResource
public string PrimaryGenreName { get; set; } {
public string ArtworkUrl100 { get; set; } public AlbumResultResource()
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<AlbumInfoResource> Items { get; set; }
} }
public class TrackResultResource
{
public TrackResultResource()
{
}
public List<TrackInfoResource> Items { get; set; }
}
public class ArtistResource public class ArtistResource
{ {
public ArtistResource() public ArtistResource()
@ -33,9 +42,7 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
} }
public int ResultCount { get; set; } public AristResultResource Artists { get; set; }
public List<AlbumResource> Results { get; set; } public AristResultResource Albums { get; set; }
//public string ArtistName { get; set; }
//public List<AlbumResource> Albums { get; set; }
} }
} }

@ -3,6 +3,10 @@ namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
public class ImageResource public class ImageResource
{ {
public string CoverType { get; set; } public string CoverType { get; set; }
// Spotify Mapping
public string Url { get; set; } public string Url { get; set; }
public int Height { get; set; }
public int Width { get; set; }
} }
} }

@ -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<ArtistInfoResource> Artists { get; set; }
}
}

@ -30,6 +30,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
_logger = logger; _logger = logger;
} }
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId) public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
{ {
Console.WriteLine("[GetSeriesInfo] id:" + tvdbSeriesId); Console.WriteLine("[GetSeriesInfo] id:" + tvdbSeriesId);
@ -63,100 +65,136 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
public List<Series> SearchForNewSeries(string title) public List<Series> SearchForNewSeries(string title)
{ {
try // TODO: Remove this API
{ var tempList = new List<Series>();
var lowerTitle = title.ToLowerInvariant(); var tempSeries = new Series();
Console.WriteLine("Searching for " + lowerTitle); 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<Artist, List<Track>> GetArtistInfo(string spotifyId)
{
// if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out tvdbId) || tvdbId <= 0) _logger.Debug("Getting Artist with SpotifyId of {0}", spotifyId);
// {
// return new List<Series>();
// }
// try ///v1/albums/{id}
// { //
// return new List<Series> { GetSeriesInfo(tvdbId).Item1 };
// }
// catch (SeriesNotFoundException)
// {
// return new List<Series>();
// }
//}
// Majora: Temporarily, use iTunes to test. // We need to perform a direct lookup of the artist
var httpRequest = _requestBuilder.Create() var httpRequest = _requestBuilder.Create()
.AddQueryParam("entity", "album") .SetSegment("route", "artists/" + spotifyId)
.AddQueryParam("term", title.ToLower().Trim()) //.SetSegment("route", "search")
.Build(); //.AddQueryParam("type", "artist,album")
//.AddQueryParam("q", spotifyId.ToString())
.Build();
httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
Console.WriteLine("httpRequest: ", httpRequest); var httpResponse = _httpClient.Get<ArtistInfoResource>(httpRequest);
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest); if (httpResponse.HasHttpError)
{
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
{
throw new ArtistNotFoundException(spotifyId);
}
else
{
throw new HttpException(httpRequest, httpResponse);
}
}
//Console.WriteLine("Response: ", httpResponse.GetType()); Artist artist = new Artist();
//_logger.Info("Response: ", httpResponse.Resource.ResultCount); 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<Series>(); artist = MapAlbums(artist);
var tempSeries = new Series();
tempSeries.Title = "AFI";
tempList.Add(tempSeries); // TODO: implement tracks api call
return tempList; return new Tuple<Artist, List<Track>>(artist, new List<Track>());
}
return httpResponse.Resource.SelectList(MapSeries);
} private Artist MapAlbums(Artist artist)
catch (HttpException) {
// 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<AlbumResultResource>(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<Album> albums = new List<Album>();
foreach(var albumResource in httpResponse.Resource.Items)
{ {
_logger.Warn(ex, ex.Message); Album album = new Album();
throw new SkyHookException("Search for '{0}' failed. Invalid response received from SkyHook.", title); 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<Artist, List<Track>> GetArtistInfo(int itunesId) private List<Track> MapTracksToAlbum(Album album)
{ {
Console.WriteLine("[GetArtistInfo] id:" + itunesId);
//https://itunes.apple.com/lookup?id=909253
var httpRequest = _requestBuilder.Create() var httpRequest = _requestBuilder.Create()
.SetSegment("route", "lookup") .SetSegment("route", "albums/" + album.AlbumId + "/tracks")
.AddQueryParam("id", itunesId.ToString()) .Build();
.Build();
httpRequest.AllowAutoRedirect = true; httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true; httpRequest.SuppressHttpError = true;
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest); var httpResponse = _httpClient.Get<TrackResultResource>(httpRequest);
if (httpResponse.HasHttpError) if (httpResponse.HasHttpError)
{ {
if (httpResponse.StatusCode == HttpStatusCode.NotFound) throw new HttpException(httpRequest, httpResponse);
{ }
throw new ArtistNotFoundException(itunesId);
} List<Track> tracks = new List<Track>();
else foreach(var trackResource in httpResponse.Resource.Items)
{ {
throw new HttpException(httpRequest, httpResponse); 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"); return tracks;
//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<Artist, List<Track>>(MapArtists(httpResponse.Resource)[0], new List<Track>());
//return new Tuple<Artist, List<Track>>(artist, tracks.ToList());
} }
public List<Artist> SearchForNewArtist(string title) public List<Artist> SearchForNewArtist(string title)
{ {
try try
@ -168,16 +206,14 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{ {
var slug = lowerTitle.Split(':')[1].Trim(); var slug = lowerTitle.Split(':')[1].Trim();
int itunesId; if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace))
if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out itunesId) || itunesId <= 0)
{ {
return new List<Artist>(); return new List<Artist>();
} }
try try
{ {
return new List<Artist> { GetArtistInfo(itunesId).Item1 }; return new List<Artist> { GetArtistInfo(slug).Item1 };
} }
catch (ArtistNotFoundException) catch (ArtistNotFoundException)
{ {
@ -187,15 +223,18 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
var httpRequest = _requestBuilder.Create() var httpRequest = _requestBuilder.Create()
.SetSegment("route", "search") .SetSegment("route", "search")
.AddQueryParam("entity", "album") .AddQueryParam("type", "artist,album")
.AddQueryParam("term", title.ToLower().Trim()) .AddQueryParam("q", title.ToLower().Trim())
.Build(); .Build();
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest); var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
return MapArtists(httpResponse.Resource);
List<Artist> artists = MapArtists(httpResponse.Resource);
return artists;
} }
catch (HttpException) 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<Artist> MapArtists(ArtistResource resource) private List<Artist> MapArtists(ArtistResource resource)
{ {
Album tempAlbum;
List<Artist> artists = new List<Artist>(); List<Artist> artists = new List<Artist>();
foreach (var album in resource.Results) foreach(var artistResource in resource.Artists.Items)
{ {
int index = artists.FindIndex(a => a.ItunesId == album.ArtistId); Artist artist = new Artist();
tempAlbum = MapAlbum(album); 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) // Maybe? Get all the albums for said artist
{
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);
}
}
return artists; return artists;
} }
private Album MapAlbum(AlbumResource albumQuery) //private Album MapAlbum(AlbumResource albumQuery)
{ //{
Album album = new Album(); // Album album = new Album();
album.AlbumId = albumQuery.CollectionId; // album.AlbumId = albumQuery.CollectionId;
album.Title = albumQuery.CollectionName; // album.Title = albumQuery.CollectionName;
album.Year = albumQuery.ReleaseDate.Year; // album.Year = albumQuery.ReleaseDate.Year;
album.ArtworkUrl = albumQuery.ArtworkUrl100; // album.ArtworkUrl = albumQuery.ArtworkUrl100;
album.Explicitness = albumQuery.CollectionExplicitness; // album.Explicitness = albumQuery.CollectionExplicitness;
return album; // return album;
} //}
private static Series MapSeries(ShowResource show) private static Series MapSeries(ShowResource show)
{ {

@ -48,7 +48,7 @@ namespace NzbDrone.Core.Music
if (string.IsNullOrWhiteSpace(newArtist.Path)) 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); newArtist.Path = Path.Combine(newArtist.RootFolderPath, folderName);
} }
@ -63,7 +63,7 @@ namespace NzbDrone.Core.Music
throw new ValidationException(validationResult.Errors); 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); _artistService.AddArtist(newArtist);
return newArtist; return newArtist;
@ -75,22 +75,21 @@ namespace NzbDrone.Core.Music
try try
{ {
tuple = _artistInfo.GetArtistInfo(newArtist.ItunesId); tuple = _artistInfo.GetArtistInfo(newArtist.SpotifyId);
} }
catch (SeriesNotFoundException) 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<ValidationFailure> throw new ValidationException(new List<ValidationFailure>
{ {
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; var artist = tuple.Item1;
// If seasons were passed in on the new series use them, otherwise use the seasons from Skyhook // If albums were passed in on the new artist use them, otherwise use the albums from Skyhook
// TODO: Refactor for albums
newArtist.Albums = newArtist.Albums != null && newArtist.Albums.Any() ? newArtist.Albums : artist.Albums; newArtist.Albums = newArtist.Albums != null && newArtist.Albums.Any() ? newArtist.Albums : artist.Albums;
artist.ApplyChanges(newArtist); artist.ApplyChanges(newArtist);

@ -19,7 +19,7 @@ namespace NzbDrone.Core.Music
SeriesPathValidator seriesPathValidator, SeriesPathValidator seriesPathValidator,
DroneFactoryValidator droneFactoryValidator, DroneFactoryValidator droneFactoryValidator,
SeriesAncestorValidator seriesAncestorValidator, SeriesAncestorValidator seriesAncestorValidator,
ArtistSlugValidator seriesTitleSlugValidator) ArtistSlugValidator artistTitleSlugValidator)
{ {
RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure) RuleFor(c => c.Path).Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath() .IsValidPath()
@ -28,7 +28,7 @@ namespace NzbDrone.Core.Music
.SetValidator(droneFactoryValidator) .SetValidator(droneFactoryValidator)
.SetValidator(seriesAncestorValidator); .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
} }
} }
} }

@ -14,10 +14,11 @@ namespace NzbDrone.Core.Music
Images = new List<MediaCover.MediaCover>(); Images = new List<MediaCover.MediaCover>();
} }
public int AlbumId { get; set; } public string AlbumId { get; set; }
public string Title { get; set; } // NOTE: This should be CollectionName in API public string Title { get; set; } // NOTE: This should be CollectionName in API
public int Year { get; set; } public int Year { get; set; }
public int TrackCount { get; set; } public int TrackCount { get; set; }
public List<Track> Tracks { get; set; }
public int DiscCount { get; set; } public int DiscCount { get; set; }
public bool Monitored { get; set; } public bool Monitored { get; set; }
public List<MediaCover.MediaCover> Images { get; set; } public List<MediaCover.MediaCover> Images { get; set; }

@ -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 ArtistName { get; set; }
public string ArtistSlug { get; set; } public string ArtistSlug { get; set; }
public string CleanTitle { get; set; } public string CleanTitle { get; set; }
public string Overview { get; set; }
public bool Monitored { get; set; } public bool Monitored { get; set; }
public bool AlbumFolder { get; set; } public bool AlbumFolder { get; set; }
public bool ArtistFolder { get; set; } public bool ArtistFolder { get; set; }
public DateTime? LastInfoSync { get; set; } public DateTime? LastInfoSync { get; set; }
public DateTime? LastDiskSync { get; set; } public DateTime? LastDiskSync { get; set; }
public int Status { get; set; } // TODO: Figure out what this is, do we need it? public int Status { get; set; } // TODO: Figure out what this is, do we need it?
public string Path { get; set; } public string Path { get; set; }
public List<MediaCover.MediaCover> Images { get; set; } public List<MediaCover.MediaCover> Images { get; set; }
public List<string> Genres { get; set; } public List<string> Genres { get; set; }
public int QualityProfileId { get; set; } public int QualityProfileId { get; set; }
public string RootFolderPath { get; set; } public string RootFolderPath { get; set; }
public DateTime Added { get; set; } public DateTime Added { get; set; }
public LazyLoaded<Profile> Profile { get; set; } public LazyLoaded<Profile> Profile { get; set; }
public int ProfileId { get; set; } public int ProfileId { get; set; }
public List<Album> Albums { get; set; } public List<Album> Albums { get; set; }
public HashSet<int> Tags { get; set; } public HashSet<int> Tags { get; set; }
public AddSeriesOptions AddOptions { 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<Actor> Actors { get; set; } // MOve to album?
//public string Certification { get; set; }
//public DateTime? FirstAired { get; set; }
public override string ToString() 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) public void ApplyChanges(Artist otherArtist)
{ {
ItunesId = otherArtist.ItunesId; SpotifyId = otherArtist.SpotifyId;
ArtistName = otherArtist.ArtistName; ArtistName = otherArtist.ArtistName;
ArtistSlug = otherArtist.ArtistSlug; ArtistSlug = otherArtist.ArtistSlug;
CleanTitle = otherArtist.CleanTitle; CleanTitle = otherArtist.CleanTitle;
@ -87,18 +72,11 @@ namespace NzbDrone.Core.Music
ArtistFolder = otherArtist.ArtistFolder; ArtistFolder = otherArtist.ArtistFolder;
AddOptions = otherArtist.AddOptions; AddOptions = otherArtist.AddOptions;
//TODO: Implement
ItunesId = otherArtist.ItunesId;
Albums = otherArtist.Albums; Albums = otherArtist.Albums;
Path = otherArtist.Path; Path = otherArtist.Path;
ProfileId = otherArtist.ProfileId; ProfileId = otherArtist.ProfileId;
AlbumFolder = otherArtist.AlbumFolder; AlbumFolder = otherArtist.AlbumFolder;
Monitored = otherArtist.Monitored; Monitored = otherArtist.Monitored;
//SeriesType = otherArtist.SeriesType;
RootFolderPath = otherArtist.RootFolderPath; RootFolderPath = otherArtist.RootFolderPath;
Tags = otherArtist.Tags; Tags = otherArtist.Tags;
AddOptions = otherArtist.AddOptions; AddOptions = otherArtist.AddOptions;

@ -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<ArtistAddedEvent>
{
private readonly IManageCommandQueue _commandQueueManager;
public ArtistAddedHandler(IManageCommandQueue commandQueueManager)
{
_commandQueueManager = commandQueueManager;
}
public void Handle(ArtistAddedEvent message)
{
_commandQueueManager.Push(new RefreshArtistCommand(message.Artist.Id));
}
}
}

@ -8,7 +8,7 @@ namespace NzbDrone.Core.Music
{ {
bool ArtistPathExists(string path); bool ArtistPathExists(string path);
Artist FindByName(string cleanTitle); Artist FindByName(string cleanTitle);
Artist FindByItunesId(int iTunesId); Artist FindById(string spotifyId);
} }
public class ArtistRepository : BasicRepository<Artist>, IArtistRepository public class ArtistRepository : BasicRepository<Artist>, IArtistRepository
@ -24,9 +24,9 @@ namespace NzbDrone.Core.Music
return Query.Where(c => c.Path == path).Any(); 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) public Artist FindByName(string cleanName)

@ -17,7 +17,7 @@ namespace NzbDrone.Core.Music
Artist GetArtist(int artistId); Artist GetArtist(int artistId);
List<Artist> GetArtists(IEnumerable<int> artistIds); List<Artist> GetArtists(IEnumerable<int> artistIds);
Artist AddArtist(Artist newArtist); Artist AddArtist(Artist newArtist);
Artist FindByItunesId(int itunesId); Artist FindById(string spotifyId);
Artist FindByName(string title); Artist FindByName(string title);
Artist FindByTitleInexact(string title); Artist FindByTitleInexact(string title);
void DeleteArtist(int artistId, bool deleteFiles); void DeleteArtist(int artistId, bool deleteFiles);
@ -69,9 +69,9 @@ namespace NzbDrone.Core.Music
_eventAggregator.PublishEvent(new ArtistDeletedEvent(artist, deleteFiles)); _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) public Artist FindByName(string title)
@ -114,7 +114,7 @@ namespace NzbDrone.Core.Music
if (storedAlbum != null && album.Monitored != storedAlbum.Monitored) if (storedAlbum != null && album.Monitored != storedAlbum.Monitored)
{ {
_trackService.SetTrackMonitoredByAlbum(artist.Id, album.AlbumId, album.Monitored); _trackService.SetTrackMonitoredByAlbum(artist.SpotifyId, album.AlbumId, album.Monitored);
} }
} }

@ -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;
}
}

@ -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;
}
}
}

@ -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<Track> Added { get; private set; }
public ReadOnlyCollection<Track> Updated { get; private set; }
public TrackInfoRefreshedEvent(Artist artist, IList<Track> added, IList<Track> updated)
{
Artist = artist;
Added = new ReadOnlyCollection<Track>(added);
Updated = new ReadOnlyCollection<Track>(updated);
}
}
}

@ -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<RefreshArtistCommand>
{
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<Artist, List<Track>> 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<Album> 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);
}
}
}
}
}
}
}

@ -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<Track> 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<Track> 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<Track>();
var newList = new List<Track>();
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<Artist> 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<Track>();
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<Album> 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<Track> existingTracks)
{
return existingTracks.FirstOrDefault(e => e.AlbumId == track.AlbumId && e.TrackNumber == track.TrackNumber);
}
private IEnumerable<Track> OrderTracks(Artist artist, List<Track> tracks)
{
return tracks.OrderBy(e => e.AlbumId).ThenBy(e => e.TrackNumber);
}
}
}

@ -17,9 +17,10 @@ namespace NzbDrone.Core.Music
public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd"; public const string RELEASE_DATE_FORMAT = "yyyy-MM-dd";
public int ItunesTrackId { get; set; } public int SpotifyTrackId { get; set; }
public int AlbumId { get; set; } public string AlbumId { get; set; }
public LazyLoaded<Artist> ArtistsId { get; set; } public LazyLoaded<Artist> Artist { get; set; }
public string ArtistId { get; set; }
public int CompilationId { get; set; } public int CompilationId { get; set; }
public bool Compilation { get; set; } public bool Compilation { get; set; }
public int TrackNumber { get; set; } public int TrackNumber { get; set; }
@ -28,11 +29,10 @@ namespace NzbDrone.Core.Music
public bool Explict { get; set; } public bool Explict { get; set; }
public string TrackExplicitName { get; set; } public string TrackExplicitName { get; set; }
public string TrackCensoredName { get; set; } public string TrackCensoredName { get; set; }
public string Monitored { get; set; } public bool Monitored { get; set; }
public int TrackFileId { get; set; } // JVM: Is this needed with TrackFile reference? public int TrackFileId { get; set; }
public DateTime? ReleaseDate { 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 Ratings Ratings { get; set; } // This might be aplicable as can be pulled from IDv3 tags
public List<MediaCover.MediaCover> Images { get; set; }*/ public List<MediaCover.MediaCover> Images { get; set; }*/
@ -46,7 +46,7 @@ namespace NzbDrone.Core.Music
public override string ToString() public override string ToString()
{ {
return string.Format("[{0}]{1}", ItunesTrackId, Title.NullSafe()); return string.Format("[{0}]{1}", SpotifyTrackId, Title.NullSafe());
} }
} }
} }

@ -10,12 +10,12 @@ namespace NzbDrone.Core.Music
{ {
Track GetTrack(int id); Track GetTrack(int id);
List<Track> GetTracks(IEnumerable<int> ids); List<Track> GetTracks(IEnumerable<int> ids);
Track FindTrack(int artistId, int albumId, int trackNumber); Track FindTrack(string artistId, string albumId, int trackNumber);
Track FindTrackByTitle(int artistId, int albumId, string releaseTitle); Track FindTrackByTitle(string artistId, string albumId, string releaseTitle);
List<Track> GetTrackByArtist(int artistId); List<Track> GetTrackByArtist(string artistId);
List<Track> GetTracksByAblum(int artistId, int albumId); List<Track> GetTracksByAlbum(string artistId, string albumId);
List<Track> GetTracksByAblumTitle(int artistId, string albumTitle); List<Track> GetTracksByAlbumTitle(string artistId, string albumTitle);
List<Track> TracksWithFiles(int artistId); List<Track> TracksWithFiles(string artistId);
PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec); PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
List<Track> GeTracksByFileId(int trackFileId); List<Track> GeTracksByFileId(int trackFileId);
void UpdateTrack(Track track); void UpdateTrack(Track track);
@ -24,7 +24,7 @@ namespace NzbDrone.Core.Music
void InsertMany(List<Track> tracks); void InsertMany(List<Track> tracks);
void UpdateMany(List<Track> tracks); void UpdateMany(List<Track> tracks);
void DeleteMany(List<Track> tracks); void DeleteMany(List<Track> tracks);
void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored); void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored);
} }
public class TrackService : ITrackService public class TrackService : ITrackService
@ -34,12 +34,12 @@ namespace NzbDrone.Core.Music
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Track FindTrack(int artistId, int albumId, int trackNumber) public Track FindTrack(string artistId, string albumId, int trackNumber)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Track FindTrackByTitle(int artistId, int albumId, string releaseTitle) public Track FindTrackByTitle(string artistId, string albumId, string releaseTitle)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -54,7 +54,7 @@ namespace NzbDrone.Core.Music
throw new NotImplementedException(); throw new NotImplementedException();
} }
public List<Track> GetTrackByArtist(int artistId) public List<Track> GetTrackByArtist(string artistId)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -64,12 +64,12 @@ namespace NzbDrone.Core.Music
throw new NotImplementedException(); throw new NotImplementedException();
} }
public List<Track> GetTracksByAblum(int artistId, int albumId) public List<Track> GetTracksByAlbum(string artistId, string albumId)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public List<Track> GetTracksByAblumTitle(int artistId, string albumTitle) public List<Track> GetTracksByAlbumTitle(string artistId, string albumTitle)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@ -84,12 +84,12 @@ namespace NzbDrone.Core.Music
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void SetTrackMonitoredByAlbum(int artistId, int albumId, bool monitored) public void SetTrackMonitoredByAlbum(string artistId, string albumId, bool monitored)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public List<Track> TracksWithFiles(int artistId) public List<Track> TracksWithFiles(string artistId)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

@ -815,6 +815,8 @@
<Compile Include="Messaging\IProcessMessage.cs" /> <Compile Include="Messaging\IProcessMessage.cs" />
<Compile Include="MetadataSource\IProvideArtistInfo.cs" /> <Compile Include="MetadataSource\IProvideArtistInfo.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ActorResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\AlbumInfoResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ArtistInfoResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ArtistResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ArtistResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\EpisodeResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ImageResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ImageResource.cs" />
@ -822,6 +824,7 @@
<Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" /> <Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\TrackInfoResource.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" /> <Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
<Compile Include="MetadataSource\SearchSeriesComparer.cs" /> <Compile Include="MetadataSource\SearchSeriesComparer.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" /> <Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />
@ -851,14 +854,20 @@
<Compile Include="Music\AddArtistValidator.cs" /> <Compile Include="Music\AddArtistValidator.cs" />
<Compile Include="Music\Album.cs" /> <Compile Include="Music\Album.cs" />
<Compile Include="Music\Artist.cs" /> <Compile Include="Music\Artist.cs" />
<Compile Include="Music\ArtistAddedHandler.cs" />
<Compile Include="Music\ArtistNameNormalizer.cs" /> <Compile Include="Music\ArtistNameNormalizer.cs" />
<Compile Include="Music\ArtistSlugValidator.cs" /> <Compile Include="Music\ArtistSlugValidator.cs" />
<Compile Include="Music\ArtistRepository.cs" /> <Compile Include="Music\ArtistRepository.cs" />
<Compile Include="Music\ArtistService.cs" /> <Compile Include="Music\ArtistService.cs" />
<Compile Include="Music\Commands\RefreshArtistCommand.cs" />
<Compile Include="Music\Events\ArtistAddedEvent.cs" /> <Compile Include="Music\Events\ArtistAddedEvent.cs" />
<Compile Include="Music\Events\ArtistDeletedEvent.cs" /> <Compile Include="Music\Events\ArtistDeletedEvent.cs" />
<Compile Include="Music\Events\ArtistEditedEvent.cs" /> <Compile Include="Music\Events\ArtistEditedEvent.cs" />
<Compile Include="Music\Events\ArtistRefreshStartingEvent.cs" />
<Compile Include="Music\Events\ArtistUpdatedEvent.cs" /> <Compile Include="Music\Events\ArtistUpdatedEvent.cs" />
<Compile Include="Music\Events\TrackInfoRefreshedEvent.cs" />
<Compile Include="Music\RefreshArtistService.cs" />
<Compile Include="Music\RefreshTrackService.cs" />
<Compile Include="Music\Track.cs" /> <Compile Include="Music\Track.cs" />
<Compile Include="Music\TrackService.cs" /> <Compile Include="Music\TrackService.cs" />
<Compile Include="Notifications\Join\JoinAuthException.cs" /> <Compile Include="Notifications\Join\JoinAuthException.cs" />

@ -24,7 +24,7 @@ namespace NzbDrone.Core.Parser.Model
public MediaInfoModel MediaInfo { get; set; } public MediaInfoModel MediaInfo { get; set; }
public bool ExistingFile { get; set; } public bool ExistingFile { get; set; }
public int Album public string Album
{ {
get get
{ {
@ -32,7 +32,7 @@ namespace NzbDrone.Core.Parser.Model
} }
} }
public bool IsSpecial => Album == 0; public bool IsSpecial => Album != "";
public override string ToString() public override string ToString()
{ {

@ -12,7 +12,7 @@ namespace NzbDrone.Core.Validation.Paths
private readonly IArtistService _artistService; private readonly IArtistService _artistService;
public ArtistExistsValidator(IArtistService artistService) public ArtistExistsValidator(IArtistService artistService)
: base("This artist has already been added") : base("This artist has already been added.")
{ {
_artistService = artistService; _artistService = artistService;
} }
@ -21,9 +21,7 @@ namespace NzbDrone.Core.Validation.Paths
{ {
if (context.PropertyValue == null) return true; if (context.PropertyValue == null) return true;
var itunesId = Convert.ToInt32(context.PropertyValue.ToString()); return (!_artistService.GetAllArtists().Exists(s => s.SpotifyId == context.PropertyValue.ToString()));
return (!_artistService.GetAllArtists().Exists(s => s.ItunesId == itunesId));
} }
} }
} }

@ -40,7 +40,7 @@ namespace NzbDrone.Update.UpdateEngine
} }
catch (InvalidOperationException e) 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); StartConsole(installationFolder);
} }
} }

@ -6,7 +6,7 @@ var ExistingSeriesCollectionView = require('./Existing/AddExistingSeriesCollecti
var AddSeriesView = require('./AddSeriesView'); var AddSeriesView = require('./AddSeriesView');
var ProfileCollection = require('../Profile/ProfileCollection'); var ProfileCollection = require('../Profile/ProfileCollection');
var RootFolderCollection = require('./RootFolders/RootFolderCollection'); var RootFolderCollection = require('./RootFolders/RootFolderCollection');
require('../Series/SeriesCollection'); require('../Artist/ArtistCollection');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
template : 'AddSeries/AddSeriesLayoutTemplate', template : 'AddSeries/AddSeriesLayoutTemplate',

@ -223,12 +223,12 @@ var view = Marionette.ItemView.extend({
self.close(); self.close();
Messenger.show({ Messenger.show({
message : 'Added: ' + self.model.get('title'), message : 'Added: ' + self.model.get('artistName'),
actions : { actions : {
goToSeries : { goToSeries : {
label : 'Go to Artist', label : 'Go to Artist',
action : function() { action : function() {
Backbone.history.navigate('/artist/' + self.model.get('titleSlug'), { trigger : true }); Backbone.history.navigate('/artist/' + self.model.get('artistSlug'), { trigger : true });
} }
} }
}, },

@ -1,34 +1,33 @@
var NzbDroneController = require('../Shared/NzbDroneController'); var NzbDroneController = require('../Shared/NzbDroneController');
var AppLayout = require('../AppLayout'); var AppLayout = require('../AppLayout');
var ArtistCollection = require('./ArtistCollection'); var ArtistCollection = require('./ArtistCollection');
var SeriesIndexLayout = require('./Index/SeriesIndexLayout'); var SeriesIndexLayout = require('../Series/Index/SeriesIndexLayout');
var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout'); var SeriesDetailsLayout = require('../Series/Details/SeriesDetailsLayout');
module.exports = NzbDroneController.extend({ module.exports = NzbDroneController.extend({
_originalInit : NzbDroneController.prototype.initialize, _originalInit : NzbDroneController.prototype.initialize,
initialize : function() { initialize : function() {
this.route('', this.series); this.route('', this.artist);
this.route('artist', this.series); this.route('artist', this.artist);
this.route('artist/:query', this.seriesDetails); this.route('artist/:query', this.artistDetails);
this._originalInit.apply(this, arguments); this._originalInit.apply(this, arguments);
}, },
artist : function() { artist : function() {
this.setTitle('Lidarr'); this.setTitle('Lidarr');
this.setArtistName('Lidarr');
this.showMainRegion(new SeriesIndexLayout()); this.showMainRegion(new SeriesIndexLayout());
}, },
seriesDetails : function(query) { artistDetails : function(query) {
var artists = ArtistCollection.where({ artistNameSlug : query }); var artists = ArtistCollection.where({ artistNameSlug : query });
console.log('seriesDetails, artists: ', artists); console.log('artistDetails, artists: ', artists);
if (artists.length !== 0) { if (artists.length !== 0) {
var targetSeries = artists[0]; var targetSeries = artists[0];
console.log("[ArtistController] targetSeries: ", targetSeries); console.log("[ArtistController] targetSeries: ", targetSeries);
this.setTitle(targetSeries.get('title')); this.setTitle(targetSeries.get('artistName')); // TODO: Update NzbDroneController
this.setArtistName(targetSeries.get('artistName')); //this.setArtistName(targetSeries.get('artistName'));
this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries })); this.showMainRegion(new SeriesDetailsLayout({ model : targetSeries }));
} else { } else {
this.showNotFound(); this.showNotFound();

@ -71,6 +71,28 @@ Handlebars.registerHelper('seasonCountHelper', function() {
return new Handlebars.SafeString('<span class="label label-info">{0} Seasons</span>'.format(seasonCount)); return new Handlebars.SafeString('<span class="label label-info">{0} Seasons</span>'.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('<span class="label label-info">{0} Albums</span>'.format(albumCount));
}
return new Handlebars.SafeString('<span class="label label-info">{0} Albums</span>'.format(albumCount));
});
/*Handlebars.registerHelper('titleWithYear', function() { /*Handlebars.registerHelper('titleWithYear', function() {
if (this.title.endsWith(' ({0})'.format(this.year))) { if (this.title.endsWith(' ({0})'.format(this.year))) {
return this.title; return this.title;

@ -8,24 +8,22 @@
<div class="col-md-10 col-xs-9"> <div class="col-md-10 col-xs-9">
<div class="row"> <div class="row">
<div class="col-md-10 col-xs-10"> <div class="col-md-10 col-xs-10">
<a href="{{route}}" target="_blank"> <a href="artist/{{artistNameSlug}}" target="_blank">
<h2>{{title}}</h2> <h2>{{artistName}}</h2>
</a> </a>
</div> </div>
<div class="col-md-2 col-xs-2"> <div class="col-md-2 col-xs-2">
<div class="pull-right series-overview-list-actions"> <div class="pull-right series-overview-list-actions">
<i class="icon-lidarr-refresh x-refresh" title="Update series info and scan disk"/> <i class="icon-lidarr-refresh x-refresh" title="Update artist info and scan disk"/>
<i class="icon-lidarr-edit x-edit" title="Edit Series"/> <i class="icon-lidarr-edit x-edit" title="Edit Artist"/>
</div> </div>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12 col-xs-12"> <div class="col-md-10 col-xs-12">
<a href="{{route}}"> <div>
<div> {{truncate overview 600}}
{{overview}} </div>
</div>
</a>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@ -35,21 +33,26 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-10 col-xs-8"> <div class="col-md-10 col-xs-8">
{{#if_eq status compare="ended"}} <!--{{#if_eq status compare="ended"}}
<span class="label label-danger">Ended</span> <span class="label label-danger">Ended</span>
{{/if_eq}} {{/if_eq}}-->
<!--
NOTE: We can show next drop date of album in future
{{#if nextAiring}} {{#if nextAiring}}
<span class="label label-default">{{RelativeDate nextAiring}}</span> <span class="label label-default">{{RelativeDate nextAiring}}</span>
{{/if}} {{/if}}-->
{{seasonCountHelper}} {{albumCountHelper}}
{{profile profileId}} {{profile profileId}}
</div> </div>
<div class="col-md-2 col-xs-4"> <div class="col-md-2 col-xs-4">
{{> EpisodeProgressPartial }} {{> EpisodeProgressPartial }}
</div> </div>
<div class="col-md-8 col-xs-10">
Path {{path}}
</div>
</div> </div>
</div> </div>
</div> </div>

@ -4,7 +4,7 @@ var Backgrid = require('backgrid');
var PosterCollectionView = require('./Posters/SeriesPostersCollectionView'); var PosterCollectionView = require('./Posters/SeriesPostersCollectionView');
var ListCollectionView = require('./Overview/SeriesOverviewCollectionView'); var ListCollectionView = require('./Overview/SeriesOverviewCollectionView');
var EmptyView = require('./EmptyView'); var EmptyView = require('./EmptyView');
var SeriesCollection = require('../SeriesCollection'); var ArtistCollection = require('../../Artist/ArtistCollection');
var RelativeDateCell = require('../../Cells/RelativeDateCell'); var RelativeDateCell = require('../../Cells/RelativeDateCell');
var SeriesTitleCell = require('../../Cells/SeriesTitleCell'); var SeriesTitleCell = require('../../Cells/SeriesTitleCell');
var TemplatedCell = require('../../Cells/TemplatedCell'); var TemplatedCell = require('../../Cells/TemplatedCell');
@ -20,6 +20,7 @@ require('../../Mixins/backbone.signalr.mixin');
module.exports = Marionette.Layout.extend({ module.exports = Marionette.Layout.extend({
template : 'Series/Index/SeriesIndexLayoutTemplate', template : 'Series/Index/SeriesIndexLayoutTemplate',
regions : { regions : {
seriesRegion : '#x-series', seriesRegion : '#x-series',
toolbar : '#x-toolbar', toolbar : '#x-toolbar',
@ -111,28 +112,28 @@ module.exports = Marionette.Layout.extend({
}, },
initialize : function() { initialize : function() {
this.seriesCollection = SeriesCollection.clone(); this.artistCollection = ArtistCollection.clone();
this.seriesCollection.shadowCollection.bindSignalR(); this.artistCollection.shadowCollection.bindSignalR();
this.listenTo(this.seriesCollection.shadowCollection, 'sync', function(model, collection, options) { this.listenTo(this.artistCollection, 'sync', function(model, collection, options) {
this.seriesCollection.fullCollection.resetFiltered(); this.artistCollection.fullCollection.resetFiltered();
this._renderView(); this._renderView();
}); });
this.listenTo(this.seriesCollection.shadowCollection, 'add', function(model, collection, options) { this.listenTo(this.artistCollection, 'add', function(model, collection, options) {
this.seriesCollection.fullCollection.resetFiltered(); this.artistCollection.fullCollection.resetFiltered();
this._renderView(); this._renderView();
}); });
this.listenTo(this.seriesCollection.shadowCollection, 'remove', function(model, collection, options) { this.listenTo(this.artistCollection, 'remove', function(model, collection, options) {
this.seriesCollection.fullCollection.resetFiltered(); this.artistCollection.fullCollection.resetFiltered();
this._renderView(); this._renderView();
}); });
this.sortingOptions = { this.sortingOptions = {
type : 'sorting', type : 'sorting',
storeState : false, storeState : false,
viewCollection : this.seriesCollection, viewCollection : this.artistCollection,
items : [ items : [
{ {
title : 'Title', title : 'Title',
@ -243,7 +244,7 @@ module.exports = Marionette.Layout.extend({
_showTable : function() { _showTable : function() {
this.currentView = new Backgrid.Grid({ this.currentView = new Backgrid.Grid({
collection : this.seriesCollection, collection : this.artistCollection,
columns : this.columns, columns : this.columns,
className : 'table table-hover' className : 'table table-hover'
}); });
@ -253,7 +254,7 @@ module.exports = Marionette.Layout.extend({
_showList : function() { _showList : function() {
this.currentView = new ListCollectionView({ this.currentView = new ListCollectionView({
collection : this.seriesCollection collection : this.artistCollection
}); });
this._renderView(); this._renderView();
@ -261,14 +262,15 @@ module.exports = Marionette.Layout.extend({
_showPosters : function() { _showPosters : function() {
this.currentView = new PosterCollectionView({ this.currentView = new PosterCollectionView({
collection : this.seriesCollection collection : this.artistCollection
}); });
this._renderView(); this._renderView();
}, },
_renderView : function() { _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.seriesRegion.show(new EmptyView());
this.toolbar.close(); this.toolbar.close();
@ -282,13 +284,14 @@ module.exports = Marionette.Layout.extend({
}, },
_fetchCollection : function() { _fetchCollection : function() {
this.seriesCollection.fetch(); this.artistCollection.fetch();
console.log('index page, collection: ', this.artistCollection);
}, },
_setFilter : function(buttonContext) { _setFilter : function(buttonContext) {
var mode = buttonContext.model.get('key'); var mode = buttonContext.model.get('key');
this.seriesCollection.setFilterMode(mode); this.artistCollection.setFilterMode(mode);
}, },
_showToolbar : function() { _showToolbar : function() {
@ -317,22 +320,22 @@ module.exports = Marionette.Layout.extend({
_showFooter : function() { _showFooter : function() {
var footerModel = new FooterModel(); var footerModel = new FooterModel();
var series = SeriesCollection.models.length; var series = this.artistCollection.models.length;
var episodes = 0; var episodes = 0;
var episodeFiles = 0; var episodeFiles = 0;
var ended = 0; var ended = 0;
var continuing = 0; var continuing = 0;
var monitored = 0; var monitored = 0;
_.each(SeriesCollection.models, function(model) { _.each(this.artistCollection.models, function(model) {
episodes += model.get('episodeCount'); episodes += model.get('episodeCount'); // TODO: Refactor to Seasons and Tracks
episodeFiles += model.get('episodeFileCount'); episodeFiles += model.get('episodeFileCount');
if (model.get('status').toLowerCase() === 'ended') { /*if (model.get('status').toLowerCase() === 'ended') {
ended++; ended++;
} else { } else {
continuing++; continuing++;
} }*/
if (model.get('monitored')) { if (model.get('monitored')) {
monitored++; monitored++;

@ -8,6 +8,12 @@
max-width: 100%; max-width: 100%;
} }
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.edit-series-modal, .delete-series-modal { .edit-series-modal, .delete-series-modal {
overflow : visible; overflow : visible;

@ -25,8 +25,8 @@
<dd>Consultation - <a href="https://github.com/galli-leo">Galli-leo</a></dd> <dd>Consultation - <a href="https://github.com/galli-leo">Galli-leo</a></dd>
<dt>Feature Requests</dt> <dt>Feature Requests</dt>
<dd><a href="http://feathub.com/mattman86/Lidarr/">feathub.com/mattman86/Lidarr</a></dd> <dd><a href="http://feathub.com/lidarr/Lidarr/">feathub.com/lidarr/Lidarr</a></dd>
<dd><a href="https://github.com/mattman86/Lidarr/issues">github.com/mattman86/Lidarr/issues</a> <b>(Please post issues on the forum first and not on github)</b></dd> <dd><a href="https://github.com/lidarr/Lidarr/issues">github.com/lidarr/Lidarr/issues</a> <b>(Please post issues on the forum first and not on github)</b></dd>
</dl> </dl>
</fieldset> </fieldset>

@ -5,7 +5,7 @@ var RouteBinder = require('./jQuery/RouteBinder');
var SignalRBroadcaster = require('./Shared/SignalRBroadcaster'); var SignalRBroadcaster = require('./Shared/SignalRBroadcaster');
var NavbarLayout = require('./Navbar/NavbarLayout'); var NavbarLayout = require('./Navbar/NavbarLayout');
var AppLayout = require('./AppLayout'); var AppLayout = require('./AppLayout');
var SeriesController = require('./Series/SeriesController'); var ArtistController = require('./Artist/ArtistController');
var Router = require('./Router'); var Router = require('./Router');
var ModalController = require('./Shared/Modal/ModalController'); var ModalController = require('./Shared/Modal/ModalController');
var ControlPanelController = require('./Shared/ControlPanel/ControlPanelController'); var ControlPanelController = require('./Shared/ControlPanel/ControlPanelController');
@ -20,7 +20,7 @@ require('./Hotkeys/Hotkeys');
require('./Shared/piwikCheck'); require('./Shared/piwikCheck');
require('./Shared/VersionChangeMonitor'); require('./Shared/VersionChangeMonitor');
new SeriesController(); new ArtistController();
new ModalController(); new ModalController();
new ControlPanelController(); new ControlPanelController();
new Router(); new Router();

@ -5,6 +5,8 @@ var vent = new Wreqr.EventAggregator();
vent.Events = { vent.Events = {
SeriesAdded : 'series:added', SeriesAdded : 'series:added',
SeriesDeleted : 'series:deleted', SeriesDeleted : 'series:deleted',
ArtistAdded : 'artist:added',
ArtistDeleted : 'artist:deleted',
CommandComplete : 'command:complete', CommandComplete : 'command:complete',
ServerUpdated : 'server:updated', ServerUpdated : 'server:updated',
EpisodeFileDeleted : 'episodefile:deleted' EpisodeFileDeleted : 'episodefile:deleted'

Loading…
Cancel
Save