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.
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_*
_TeamCity*
# Sonarr
# Lidarr
config.xml
nzbdrone.log*txt
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.
## 1. Definitions ##

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

@ -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.*

8
debian/control vendored

@ -2,11 +2,11 @@ Section: web
Priority: optional
Maintainer: Sonarr <contact@nzbdrone.com>
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

4
debian/copyright vendored

@ -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 <hello@sonarr.tv>
Copyright: 2010-2016 Lidarr <hello@lidarr.audio>
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
// 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);

@ -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",

@ -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')

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

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

@ -19,9 +19,8 @@ namespace NzbDrone.Api.Music
//View Only
public string ArtistName { get; set; }
public int ItunesId { get; set; }
//public List<AlternateTitleResource> 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,

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

@ -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()

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

@ -102,7 +102,7 @@ namespace NzbDrone.Core.Datastore
.Relationships.AutoMapICollectionOrComplexProperties()
.For("Tracks")
.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);
Mapper.Entity<Track>().RegisterModel("Tracks")

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

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

@ -6,6 +6,6 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
{
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
{
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<ArtistInfoResource> Items { get; set; }
}
public class AlbumResultResource
{
public AlbumResultResource()
{
}
public List<AlbumInfoResource> Items { get; set; }
}
public class TrackResultResource
{
public TrackResultResource()
{
}
public List<TrackInfoResource> 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<AlbumResource> Results { get; set; }
//public string ArtistName { get; set; }
//public List<AlbumResource> Albums { get; set; }
public AristResultResource Artists { get; set; }
public AristResultResource Albums { get; set; }
}
}

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

@ -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;
}
public Tuple<Series, List<Episode>> GetSeriesInfo(int tvdbSeriesId)
{
Console.WriteLine("[GetSeriesInfo] id:" + tvdbSeriesId);
@ -63,100 +65,136 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
public List<Series> SearchForNewSeries(string title)
{
try
{
var lowerTitle = title.ToLowerInvariant();
Console.WriteLine("Searching for " + lowerTitle);
// TODO: Remove this API
var tempList = new List<Series>();
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<Artist, List<Track>> GetArtistInfo(string spotifyId)
{
// if (slug.IsNullOrWhiteSpace() || slug.Any(char.IsWhiteSpace) || !int.TryParse(slug, out tvdbId) || tvdbId <= 0)
// {
// return new List<Series>();
// }
_logger.Debug("Getting Artist with SpotifyId of {0}", spotifyId);
// try
// {
// return new List<Series> { GetSeriesInfo(tvdbId).Item1 };
// }
// catch (SeriesNotFoundException)
// {
// return new List<Series>();
// }
//}
///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<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());
//_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<Series>();
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, List<Track>>(artist, new List<Track>());
}
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<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);
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<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()
.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<ArtistResource>(httpRequest);
var httpResponse = _httpClient.Get<TrackResultResource>(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<Track> tracks = new List<Track>();
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<Artist, List<Track>>(MapArtists(httpResponse.Resource)[0], new List<Track>());
//return new Tuple<Artist, List<Track>>(artist, tracks.ToList());
return tracks;
}
public List<Artist> 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<Artist>();
}
try
{
return new List<Artist> { GetArtistInfo(itunesId).Item1 };
return new List<Artist> { 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<ArtistResource>(httpRequest);
return MapArtists(httpResponse.Resource);
List<Artist> 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<Artist> MapArtists(ArtistResource resource)
{
Album tempAlbum;
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);
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)
{

@ -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<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;
// 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);

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

@ -14,10 +14,11 @@ namespace NzbDrone.Core.Music
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 int Year { get; set; }
public int TrackCount { get; set; }
public List<Track> Tracks { get; set; }
public int DiscCount { get; set; }
public bool Monitored { 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 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<MediaCover.MediaCover> Images { get; set; }
public List<string> Genres { get; set; }
public int QualityProfileId { get; set; }
public string RootFolderPath { get; set; }
public DateTime Added { get; set; }
public LazyLoaded<Profile> Profile { get; set; }
public int ProfileId { get; set; }
public List<Album> Albums { get; set; }
public HashSet<int> 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<Actor> 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;

@ -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);
Artist FindByName(string cleanTitle);
Artist FindByItunesId(int iTunesId);
Artist FindById(string spotifyId);
}
public class ArtistRepository : BasicRepository<Artist>, 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)

@ -17,7 +17,7 @@ namespace NzbDrone.Core.Music
Artist GetArtist(int artistId);
List<Artist> GetArtists(IEnumerable<int> 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);
}
}

@ -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 int ItunesTrackId { get; set; }
public int AlbumId { get; set; }
public LazyLoaded<Artist> ArtistsId { get; set; }
public int SpotifyTrackId { get; set; }
public string AlbumId { get; set; }
public LazyLoaded<Artist> 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<MediaCover.MediaCover> 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());
}
}
}

@ -10,12 +10,12 @@ namespace NzbDrone.Core.Music
{
Track GetTrack(int id);
List<Track> GetTracks(IEnumerable<int> ids);
Track FindTrack(int artistId, int albumId, int trackNumber);
Track FindTrackByTitle(int artistId, int albumId, string releaseTitle);
List<Track> GetTrackByArtist(int artistId);
List<Track> GetTracksByAblum(int artistId, int albumId);
List<Track> GetTracksByAblumTitle(int artistId, string albumTitle);
List<Track> TracksWithFiles(int artistId);
Track FindTrack(string artistId, string albumId, int trackNumber);
Track FindTrackByTitle(string artistId, string albumId, string releaseTitle);
List<Track> GetTrackByArtist(string artistId);
List<Track> GetTracksByAlbum(string artistId, string albumId);
List<Track> GetTracksByAlbumTitle(string artistId, string albumTitle);
List<Track> TracksWithFiles(string artistId);
PagingSpec<Track> TracksWithoutFiles(PagingSpec<Track> pagingSpec);
List<Track> GeTracksByFileId(int trackFileId);
void UpdateTrack(Track track);
@ -24,7 +24,7 @@ namespace NzbDrone.Core.Music
void InsertMany(List<Track> tracks);
void UpdateMany(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
@ -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<Track> GetTrackByArtist(int artistId)
public List<Track> GetTrackByArtist(string artistId)
{
throw new NotImplementedException();
}
@ -64,12 +64,12 @@ namespace NzbDrone.Core.Music
throw new NotImplementedException();
}
public List<Track> GetTracksByAblum(int artistId, int albumId)
public List<Track> GetTracksByAlbum(string artistId, string albumId)
{
throw new NotImplementedException();
}
public List<Track> GetTracksByAblumTitle(int artistId, string albumTitle)
public List<Track> 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<Track> TracksWithFiles(int artistId)
public List<Track> TracksWithFiles(string artistId)
{
throw new NotImplementedException();
}

@ -815,6 +815,8 @@
<Compile Include="Messaging\IProcessMessage.cs" />
<Compile Include="MetadataSource\IProvideArtistInfo.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\EpisodeResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ImageResource.cs" />
@ -822,6 +824,7 @@
<Compile Include="MetadataSource\SkyHook\Resource\SeasonResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\ShowResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\TimeOfDayResource.cs" />
<Compile Include="MetadataSource\SkyHook\Resource\TrackInfoResource.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxy.cs" />
<Compile Include="MetadataSource\SearchSeriesComparer.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookException.cs" />
@ -851,14 +854,20 @@
<Compile Include="Music\AddArtistValidator.cs" />
<Compile Include="Music\Album.cs" />
<Compile Include="Music\Artist.cs" />
<Compile Include="Music\ArtistAddedHandler.cs" />
<Compile Include="Music\ArtistNameNormalizer.cs" />
<Compile Include="Music\ArtistSlugValidator.cs" />
<Compile Include="Music\ArtistRepository.cs" />
<Compile Include="Music\ArtistService.cs" />
<Compile Include="Music\Commands\RefreshArtistCommand.cs" />
<Compile Include="Music\Events\ArtistAddedEvent.cs" />
<Compile Include="Music\Events\ArtistDeletedEvent.cs" />
<Compile Include="Music\Events\ArtistEditedEvent.cs" />
<Compile Include="Music\Events\ArtistRefreshStartingEvent.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\TrackService.cs" />
<Compile Include="Notifications\Join\JoinAuthException.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()
{

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

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

@ -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',

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

@ -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();

@ -71,6 +71,28 @@ Handlebars.registerHelper('seasonCountHelper', function() {
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() {
if (this.title.endsWith(' ({0})'.format(this.year))) {
return this.title;

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

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

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

@ -25,8 +25,8 @@
<dd>Consultation - <a href="https://github.com/galli-leo">Galli-leo</a></dd>
<dt>Feature Requests</dt>
<dd><a href="http://feathub.com/mattman86/Lidarr/">feathub.com/mattman86/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="http://feathub.com/lidarr/Lidarr/">feathub.com/lidarr/Lidarr</a></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>
</fieldset>

@ -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();

@ -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'

Loading…
Cancel
Save