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

pull/2/head
8 years ago
commit 67714561ea

@ -18,7 +18,7 @@ Lidarr is a music collection manager for Usenet and BitTorrent users. It can mon
## Feature Requests ## Feature Requests
[![Feature Requests](http://feathub.com/lidarr/Lidarr?format=svg)](http://feathub.com/lidarr/Lidarr) [![Feature Requests](http://feathub.com/mattman86/Lidarr?format=svg)](http://feathub.com/mattman86/Lidarr)
## Configuring Development Environment: ## Configuring Development Environment:

@ -0,0 +1,53 @@
version: '0.2.0.{build}'
assembly_info:
patch: true
file: 'src\NzbDrone.Common\Properties\SharedAssemblyInfo.cs'
assembly_version: '{version}'
assembly_file_version: '{version}'
assembly_informational_version: '{version}-rc1'
environment:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
install:
- git submodule update --init --recursive
build_script:
- ps: ./build-appveyor.ps1
# test: off
test:
assemblies:
- '_tests\*Test.dll'
categories:
except:
- IntegrationTest
- AutomationTest
artifacts:
- path: '_artifacts\*.zip'
- path: '_artifacts\*.exe'
- path: '_artifacts\*.tar.gz'
cache:
- '%USERPROFILE%\.nuget\packages'
- node_modules
pull_requests:
do_not_increment_build_number: true
on_failure:
- ps: Get-ChildItem .\_artifacts\*.zip | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- ps: Get-ChildItem .\_artifacts\*.exe | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
- ps: Get-ChildItem .\_artifacts\*.tar.gz | % { Push-AppveyorArtifact $_.FullName -FileName $_.Name }
only_commits:
files:
- src/
- osx/
- gulp/
- logo/
- setup/
- appveyor.yml
- build-appveyor.cake

@ -0,0 +1,313 @@
#addin "Cake.Npm"
#addin "SharpZipLib"
#addin "Cake.Compression"
// Build variables
var outputFolder = "./_output";
var outputFolderMono = outputFolder + "_mono";
var outputFolderOsx = outputFolder + "_osx";
var outputFolderOsxApp = outputFolderOsx + "_app";
var testPackageFolder = "./_tests";
var testSearchPattern = "*.Test/bin/x86/Release";
var sourceFolder = "./src";
var solutionFile = sourceFolder + "/NzbDrone.sln";
var updateFolder = outputFolder + "/NzbDrone.Update";
var updateFolderMono = outputFolderMono + "/NzbDrone.Update";
// Artifact variables
var artifactsFolder = "./_artifacts";
var artifactsFolderWindows = artifactsFolder + "/windows";
var artifactsFolderLinux = artifactsFolder + "/linux";
var artifactsFolderOsx = artifactsFolder + "/osx";
var artifactsFolderOsxApp = artifactsFolder + "/osx-app";
// Utility methods
public void RemoveEmptyFolders(string startLocation) {
foreach (var directory in System.IO.Directory.GetDirectories(startLocation))
{
RemoveEmptyFolders(directory);
if (System.IO.Directory.GetFiles(directory).Length == 0 &&
System.IO.Directory.GetDirectories(directory).Length == 0)
{
DeleteDirectory(directory, false);
}
}
}
public void CleanFolder(string path, bool keepConfigFiles) {
DeleteFiles(path + "/**/*.transform");
if (!keepConfigFiles) {
DeleteFiles(path + "/**/*.dll.config");
}
DeleteFiles(path + "/**/FluentValidation.resources.dll");
DeleteFiles(path + "/**/App.config");
DeleteFiles(path + "/**/*.less");
DeleteFiles(path + "/**/*.vshost.exe");
DeleteFiles(path + "/**/*.dylib");
RemoveEmptyFolders(path);
}
public void CreateMdbs(string path) {
foreach (var file in System.IO.Directory.EnumerateFiles(path, "*.pdb", System.IO.SearchOption.AllDirectories)) {
var actualFile = file.Substring(0, file.Length - 4);
if (FileExists(actualFile + ".exe")) {
StartProcess("./tools/pdb2mdb/pdb2mdb.exe", new ProcessSettings()
.WithArguments(args => args.Append(actualFile + ".exe")));
}
if (FileExists(actualFile + ".dll")) {
StartProcess("./tools/pdb2mdb/pdb2mdb.exe", new ProcessSettings()
.WithArguments(args => args.Append(actualFile + ".dll")));
}
}
}
// Build Tasks
Task("Compile").Does(() => {
// Build
if (DirectoryExists(outputFolder)) {
DeleteDirectory(outputFolder, true);
}
MSBuild(solutionFile, config =>
config.UseToolVersion(MSBuildToolVersion.VS2015)
.WithTarget("Clean")
.SetVerbosity(Verbosity.Minimal));
NuGetRestore(solutionFile);
MSBuild(solutionFile, config =>
config.UseToolVersion(MSBuildToolVersion.VS2015)
.SetPlatformTarget(PlatformTarget.x86)
.SetConfiguration("Release")
.WithProperty("AllowedReferenceRelatedFileExtensions", new string[] { ".pdb" })
.WithTarget("Build")
.SetVerbosity(Verbosity.Minimal));
CleanFolder(outputFolder, false);
// Add JsonNet
DeleteFiles(outputFolder + "/Newtonsoft.Json.*");
CopyFiles(sourceFolder + "/packages/Newtonsoft.Json.*/lib/net35/*.dll", outputFolder);
CopyFiles(sourceFolder + "/packages/Newtonsoft.Json.*/lib/net35/*.dll", updateFolder);
// Remove Mono stuff
DeleteFile(outputFolder + "/Mono.Posix.dll");
});
Task("Gulp").Does(() => {
NpmInstall(new NpmInstallSettings {
LogLevel = NpmLogLevel.Silent,
WorkingDirectory = "./",
Production = true
});
NpmRunScript("build");
});
Task("PackageMono").Does(() => {
// Start mono package
if (DirectoryExists(outputFolderMono)) {
DeleteDirectory(outputFolderMono, true);
}
CopyDirectory(outputFolder, outputFolderMono);
// Create MDBs
CreateMdbs(outputFolderMono);
// Remove PDBs
DeleteFiles(outputFolderMono + "/**/*.pdb");
// Remove service helpers
DeleteFiles(outputFolderMono + "/ServiceUninstall.*");
DeleteFiles(outputFolderMono + "/ServiceInstall.*");
// Remove native windows binaries
DeleteFiles(outputFolderMono + "/sqlite3.*");
DeleteFiles(outputFolderMono + "/MediaInfo.*");
// Adding NzbDrone.Core.dll.config (for dllmap)
CopyFile(sourceFolder + "/NzbDrone.Core/NzbDrone.Core.dll.config", outputFolderMono + "/NzbDrone.Core.dll.config");
// Adding CurlSharp.dll.config (for dllmap)
CopyFile(sourceFolder + "/NzbDrone.Common/CurlSharp.dll.config", outputFolderMono + "/CurlSharp.dll.config");
// Renaming Lidarr.Console.exe to Lidarr.exe
DeleteFiles(outputFolderMono + "/Lidarr.exe*");
MoveFile(outputFolderMono + "/Lidarr.Console.exe", outputFolderMono + "/Lidarr.exe");
MoveFile(outputFolderMono + "/Lidarr.Console.exe.config", outputFolderMono + "/Lidarr.exe.config");
MoveFile(outputFolderMono + "/Lidarr.Console.exe.mdb", outputFolderMono + "/Lidarr.exe.mdb");
// Remove NzbDrone.Windows.*
DeleteFiles(outputFolderMono + "/NzbDrone.Windows.*");
// Adding NzbDrone.Mono to updatePackage
CopyFiles(outputFolderMono + "/NzbDrone.Mono.*", updateFolderMono);
});
Task("PackageOsx").Does(() => {
// Start osx package
if (DirectoryExists(outputFolderOsx)) {
DeleteDirectory(outputFolderOsx, true);
}
CopyDirectory(outputFolderMono, outputFolderOsx);
// Adding sqlite dylibs
CopyFiles(sourceFolder + "/Libraries/Sqlite/*.dylib", outputFolderOsx);
// Adding MediaInfo dylib
CopyFiles(sourceFolder + "/Libraries/MediaInfo/*.dylib", outputFolderOsx);
// Adding Startup script
CopyFile("./osx/Lidarr", outputFolderOsx + "/Lidarr");
});
Task("PackageOsxApp").Does(() => {
// Start osx app package
if (DirectoryExists(outputFolderOsxApp)) {
DeleteDirectory(outputFolderOsxApp, true);
}
CreateDirectory(outputFolderOsxApp);
// Copy osx package files
CopyDirectory("./osx/Lidarr.app", outputFolderOsxApp + "/Lidarr.app");
CopyDirectory(outputFolderOsx, outputFolderOsxApp + "/Lidarr.app/Contents/MacOS");
});
Task("PackageTests").Does(() => {
// Start tests package
if (DirectoryExists(testPackageFolder)) {
DeleteDirectory(testPackageFolder, true);
}
CreateDirectory(testPackageFolder);
// Copy tests
CopyFiles(sourceFolder + "/" + testSearchPattern + "/*", testPackageFolder);
foreach (var directory in System.IO.Directory.GetDirectories(sourceFolder, "*.Test")) {
var releaseDirectory = directory + "/bin/x86/Release";
if (DirectoryExists(releaseDirectory)) {
foreach (var releaseSubDirectory in System.IO.Directory.GetDirectories(releaseDirectory)) {
Information(System.IO.Path.GetDirectoryName(releaseSubDirectory));
CopyDirectory(releaseSubDirectory, testPackageFolder + "/" + System.IO.Path.GetFileName(releaseSubDirectory));
}
}
}
// Install NUnit.ConsoleRunner
NuGetInstall("NUnit.ConsoleRunner", new NuGetInstallSettings {
Version = "3.2.0",
OutputDirectory = testPackageFolder
});
// Copy dlls
CopyFiles(outputFolder + "/*.dll", testPackageFolder);
// Copy scripts
CopyFiles("./*.sh", testPackageFolder);
// Create MDBs for tests
CreateMdbs(testPackageFolder);
// Remove config
DeleteFiles(testPackageFolder + "/*.log.config");
// Clean
CleanFolder(testPackageFolder, true);
// Adding NzbDrone.Core.dll.config (for dllmap)
CopyFile(sourceFolder + "/NzbDrone.Core/NzbDrone.Core.dll.config", testPackageFolder + "/NzbDrone.Core.dll.config");
// Adding CurlSharp.dll.config (for dllmap)
CopyFile(sourceFolder + "/NzbDrone.Common/CurlSharp.dll.config", testPackageFolder + "/CurlSharp.dll.config");
// Adding CurlSharp libraries
CopyFiles(sourceFolder + "/ExternalModules/CurlSharp/libs/i386/*", testPackageFolder);
});
Task("CleanupWindowsPackage").Does(() => {
// Remove mono
DeleteFiles(outputFolder + "/NzbDrone.Mono.*");
// Adding NzbDrone.Windows to updatePackage
CopyFiles(outputFolder + "/NzbDrone.Windows.*", updateFolder);
});
Task("Build")
.IsDependentOn("Compile")
.IsDependentOn("Gulp")
.IsDependentOn("PackageMono")
.IsDependentOn("PackageOsx")
.IsDependentOn("PackageOsxApp")
.IsDependentOn("PackageTests")
.IsDependentOn("CleanupWindowsPackage");
// Build Artifacts
Task("CleanArtifacts").Does(() => {
if (DirectoryExists(artifactsFolder)) {
DeleteDirectory(artifactsFolder, true);
}
CreateDirectory(artifactsFolder);
});
Task("ArtifactsWindows").Does(() => {
CopyDirectory(outputFolder, artifactsFolderWindows + "/Lidarr");
});
Task("ArtifactsWindowsInstaller").Does(() => {
InnoSetup("./setup/nzbdrone.iss", new InnoSetupSettings {
OutputDirectory = artifactsFolder,
ToolPath = "./setup/inno/ISCC.exe"
});
});
Task("ArtifactsLinux").Does(() => {
CopyDirectory(outputFolderMono, artifactsFolderLinux + "/Lidarr");
});
Task("ArtifactsOsx").Does(() => {
CopyDirectory(outputFolderOsx, artifactsFolderOsx + "/Lidarr");
});
Task("ArtifactsOsxApp").Does(() => {
CopyDirectory(outputFolderOsxApp, artifactsFolderOsxApp);
});
Task("CompressArtifacts").Does(() => {
var prefix = "";
if (AppVeyor.IsRunningOnAppVeyor) {
prefix += AppVeyor.Environment.Repository.Branch.Replace("/", "-") + ".";
prefix += AppVeyor.Environment.Build.Version + ".";
}
Zip(artifactsFolderWindows, artifactsFolder + "/Lidarr." + prefix + "windows.zip");
GZipCompress(artifactsFolderLinux, artifactsFolder + "/Lidarr." + prefix + "linux.tar.gz");
GZipCompress(artifactsFolderOsx, artifactsFolder + "/Lidarr." + prefix + "osx.tar.gz");
Zip(artifactsFolderOsxApp, artifactsFolder + "/Lidarr." + prefix + "osx-app.zip");
});
Task("Artifacts")
.IsDependentOn("CleanArtifacts")
.IsDependentOn("ArtifactsWindows")
.IsDependentOn("ArtifactsWindowsInstaller")
.IsDependentOn("ArtifactsLinux")
.IsDependentOn("ArtifactsOsx")
.IsDependentOn("ArtifactsOsxApp")
.IsDependentOn("CompressArtifacts");
// Run
RunTarget("Build");
RunTarget("Artifacts");

@ -0,0 +1,184 @@
##########################################################################
# This is the Cake bootstrapper script for PowerShell.
# This file was downloaded from https://github.com/cake-build/resources
# Feel free to change this file to fit your needs.
##########################################################################
<#
.SYNOPSIS
This is a Powershell script to bootstrap a Cake build.
.DESCRIPTION
This Powershell script will download NuGet if missing, restore NuGet tools (including Cake)
and execute your Cake build script with the parameters you provide.
.PARAMETER Script
The build script to execute.
.PARAMETER Target
The build script target to run.
.PARAMETER Configuration
The build configuration to use.
.PARAMETER Verbosity
Specifies the amount of information to be displayed.
.PARAMETER Experimental
Tells Cake to use the latest Roslyn release.
.PARAMETER WhatIf
Performs a dry run of the build script.
No tasks will be executed.
.PARAMETER Mono
Tells Cake to use the Mono scripting engine.
.PARAMETER SkipToolPackageRestore
Skips restoring of packages.
.PARAMETER ScriptArgs
Remaining arguments are added here.
.LINK
http://cakebuild.net
#>
[CmdletBinding()]
Param(
[string]$Script = "build-appveyor.cake",
[string]$Target = "Default",
[ValidateSet("Release", "Debug")]
[string]$Configuration = "Release",
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
[string]$Verbosity = "Verbose",
[switch]$Experimental,
[Alias("DryRun","Noop")]
[switch]$WhatIf,
[switch]$Mono,
[switch]$SkipToolPackageRestore,
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
[string[]]$ScriptArgs
)
[Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
function MD5HashFile([string] $filePath)
{
if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf))
{
return $null
}
[System.IO.Stream] $file = $null;
[System.Security.Cryptography.MD5] $md5 = $null;
try
{
$md5 = [System.Security.Cryptography.MD5]::Create()
$file = [System.IO.File]::OpenRead($filePath)
return [System.BitConverter]::ToString($md5.ComputeHash($file))
}
finally
{
if ($file -ne $null)
{
$file.Dispose()
}
}
}
Write-Host "Preparing to run build script..."
if(!$PSScriptRoot){
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
}
$TOOLS_DIR = Join-Path $PSScriptRoot "tools-cake"
$NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe"
$CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe"
$NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
$PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config"
$PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum"
# Should we use mono?
$UseMono = "";
if($Mono.IsPresent) {
Write-Verbose -Message "Using the Mono based scripting engine."
$UseMono = "-mono"
}
# Should we use the new Roslyn?
$UseExperimental = "";
if($Experimental.IsPresent -and !($Mono.IsPresent)) {
Write-Verbose -Message "Using experimental version of Roslyn."
$UseExperimental = "-experimental"
}
# Is this a dry run?
$UseDryRun = "";
if($WhatIf.IsPresent) {
$UseDryRun = "-dryrun"
}
# Make sure tools folder exists
if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) {
Write-Verbose -Message "Creating tools directory..."
New-Item -Path $TOOLS_DIR -Type directory | out-null
}
# Make sure that packages.config exist.
if (!(Test-Path $PACKAGES_CONFIG)) {
Write-Verbose -Message "Downloading packages.config..."
try { (New-Object System.Net.WebClient).DownloadFile("http://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch {
Throw "Could not download packages.config."
}
}
# Try find NuGet.exe in path if not exists
if (!(Test-Path $NUGET_EXE)) {
Write-Verbose -Message "Trying to find nuget.exe in PATH..."
$existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_) }
$NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1
if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) {
Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)."
$NUGET_EXE = $NUGET_EXE_IN_PATH.FullName
}
}
# Try download NuGet.exe if not exists
if (!(Test-Path $NUGET_EXE)) {
Write-Verbose -Message "Downloading NuGet.exe..."
try {
(New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE)
} catch {
Throw "Could not download NuGet.exe."
}
}
# Save nuget.exe path to environment to be available to child processed
$ENV:NUGET_EXE = $NUGET_EXE
# Restore tools from NuGet?
if(-Not $SkipToolPackageRestore.IsPresent) {
Push-Location
Set-Location $TOOLS_DIR
# Check for changes in packages.config and remove installed tools if true.
[string] $md5Hash = MD5HashFile($PACKAGES_CONFIG)
if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or
($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) {
Write-Verbose -Message "Missing or changed package.config hash..."
Remove-Item * -Recurse -Exclude packages.config,nuget.exe
}
Write-Verbose -Message "Restoring tools from NuGet..."
$NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`""
if ($LASTEXITCODE -ne 0) {
Throw "An error occured while restoring NuGet tools."
}
else
{
$md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII"
}
Write-Verbose -Message ($NuGetOutput | out-string)
Pop-Location
}
# Make sure that Cake has been installed.
if (!(Test-Path $CAKE_EXE)) {
Throw "Could not find Cake.exe at $CAKE_EXE"
}
# Start Cake
Write-Host "Running build script..."
Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs"
exit $LASTEXITCODE

@ -181,7 +181,7 @@ PackageOsx()
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderOsx cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderOsx
echo "Adding Startup script" echo "Adding Startup script"
cp ./osx/Sonarr $outputFolderOsx cp ./osx/Lidarr $outputFolderOsx
echo "##teamcity[progressFinish 'Creating OS X Package']" echo "##teamcity[progressFinish 'Creating OS X Package']"
} }
@ -192,8 +192,8 @@ PackageOsxApp()
rm -rf $outputFolderOsxApp rm -rf $outputFolderOsxApp
mkdir $outputFolderOsxApp mkdir $outputFolderOsxApp
cp -r ./osx/Sonarr.app $outputFolderOsxApp cp -r ./osx/Lidarr.app $outputFolderOsxApp
cp -r $outputFolderOsx $outputFolderOsxApp/Sonarr.app/Contents/MacOS cp -r $outputFolderOsx $outputFolderOsxApp/Lidarr.app/Contents/MacOS
echo "##teamcity[progressFinish 'Creating OS X App Package']" echo "##teamcity[progressFinish 'Creating OS X App Package']"
} }

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

@ -19,9 +19,7 @@ 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 SortTitle { get; set; }
public string Overview { get; set; } public string Overview { get; set; }
public int AlbumCount public int AlbumCount
@ -30,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
} }
} }
@ -107,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,
@ -151,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,

@ -17,7 +17,8 @@ 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(); .CreateFactory();
InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search InternalSearch = new HttpRequestBuilder("https://itunes.apple.com/WebObjects/MZStore.woa/wa/{route}") //viewArtist or search

@ -13,7 +13,7 @@ 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?
@ -37,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()
@ -49,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()

@ -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,63 +5,36 @@ using System.Text;
namespace NzbDrone.Core.MetadataSource.SkyHook.Resource namespace NzbDrone.Core.MetadataSource.SkyHook.Resource
{ {
public class StorePlatformDataResource
public class AristResultResource
{ {
public StorePlatformDataResource() { } public AristResultResource()
public ArtistInfoResource Artist { get; set; } {
//public Lockup lockup { get; set; }
}
public List<ArtistInfoResource> Items { get; set; }
} }
public class ArtistInfoResource public class AlbumResultResource
{ {
public ArtistInfoResource() { } public AlbumResultResource()
public Dictionary<int, ArtistInfoResource> Results { get; set; } {
public bool HasArtistBio { get; set; }
public string url { get; set; } }
public string shortUrl { get; set; }
public List<string> artistContemporaries { get; set; }
public List<string> genreNames { get; set; }
public bool hasSocialPosts { get; set; }
public string artistBio { get; set; }
public bool isGroup { get; set; }
public string id { get; set; }
public string bornOrFormed { get; set; }
public string name { get; set; }
public string latestAlbumContentId { get; set; }
public string nameRaw { get; set; }
//public string kind { get; set; } public List<AlbumInfoResource> Items { get; set; }
//public List<Gallery> gallery { get; set; }
//public List<Genre> genres { get; set; }
public List<object> artistInfluencers { get; set; }
public List<object> artistFollowers { get; set; }
//public string umcArtistImageUrl { get; set; }
} }
public class AlbumResource public class TrackResultResource
{ {
public AlbumResource() public TrackResultResource()
{ {
} }
public string ArtistName { get; set; } public List<TrackInfoResource> Items { 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 class ArtistResource public class ArtistResource
{ {
public ArtistResource() public ArtistResource()
@ -69,10 +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; }
public StorePlatformDataResource StorePlatformData { 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; }
}
}

@ -22,16 +22,16 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
private readonly Logger _logger; private readonly Logger _logger;
private readonly IHttpRequestBuilderFactory _requestBuilder; private readonly IHttpRequestBuilderFactory _requestBuilder;
private readonly IHttpRequestBuilderFactory _internalRequestBuilder;
public SkyHookProxy(IHttpClient httpClient, ILidarrCloudRequestBuilder requestBuilder, Logger logger) public SkyHookProxy(IHttpClient httpClient, ILidarrCloudRequestBuilder requestBuilder, Logger logger)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_requestBuilder = requestBuilder.Search; _requestBuilder = requestBuilder.Search;
_internalRequestBuilder = requestBuilder.InternalSearch;
_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);
@ -65,150 +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;
var httpResponse = _httpClient.Get<ArtistInfoResource>(httpRequest);
if (httpResponse.HasHttpError)
{
if (httpResponse.StatusCode == HttpStatusCode.NotFound)
{
throw new ArtistNotFoundException(spotifyId);
}
else
{
throw new HttpException(httpRequest, httpResponse);
}
}
Artist artist = new Artist();
artist.ArtistName = httpResponse.Resource.Name;
artist.SpotifyId = httpResponse.Resource.Id;
artist.Genres = httpResponse.Resource.Genres;
Console.WriteLine("httpRequest: ", httpRequest); artist = MapAlbums(artist);
var httpResponse = _httpClient.Get<List<ShowResource>>(httpRequest); // TODO: implement tracks api call
return new Tuple<Artist, List<Track>>(artist, new List<Track>());
}
//Console.WriteLine("Response: ", httpResponse.GetType()); private Artist MapAlbums(Artist artist)
//_logger.Info("Response: ", httpResponse.Resource.ResultCount); {
//_logger.Info("HTTP Response: ", httpResponse.Resource.ResultCount); // Find all albums for the artist and all tracks for said album
var tempList = new List<Series>(); ///v1/artists/{id}/albums
var tempSeries = new Series(); var httpRequest = _requestBuilder.Create()
tempSeries.Title = "AFI"; .SetSegment("route", "artists/" + artist.SpotifyId + "/albums")
tempList.Add(tempSeries); .Build();
return tempList; httpRequest.AllowAutoRedirect = true;
httpRequest.SuppressHttpError = true;
return httpResponse.Resource.SelectList(MapSeries);
} var httpResponse = _httpClient.Get<AlbumResultResource>(httpRequest);
catch (HttpException)
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);
} }
}
//public Artist GetArtistInfo(int itunesId) // TODO: We now need to get all tracks for each 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();
// // TODO: Add special header, add Overview to Artist model
// var httpRequest = _requestBuilder.Create()
// .SetSegment("route", "viewArtist")
// .AddQueryParam("id", itunesId.ToString())
// .Build();
// httpRequest.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3");
// httpRequest.AllowAutoRedirect = true;
// httpRequest.SuppressHttpError = true;
// var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
// if (httpResponse.HasHttpError)
// {
// if (httpResponse.StatusCode == HttpStatusCode.NotFound)
// {
// throw new ArtistNotFoundException(itunesId);
// }
// else
// {
// throw new HttpException(httpRequest, httpResponse);
// }
// }
// Console.WriteLine("GetArtistInfo, GetArtistInfo");
// return MapArtists(httpResponse.Resource)[0];
//}
public Tuple<Artist, List<Track>> GetArtistInfo(int itunesId) artist.Albums = albums;
{ return artist;
_logger.Debug("Getting Artist with iTunesID of {0}", itunesId); }
var httpRequest1 = _requestBuilder.Create()
.SetSegment("route", "lookup")
.AddQueryParam("id", itunesId.ToString())
.Build();
var httpRequest2 = _internalRequestBuilder.Create() private List<Track> MapTracksToAlbum(Album album)
.SetSegment("route", "viewArtist") {
.AddQueryParam("id", itunesId.ToString()) var httpRequest = _requestBuilder.Create()
.Build(); .SetSegment("route", "albums/" + album.AlbumId + "/tracks")
httpRequest2.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3"); .Build();
httpRequest2.Headers.ContentType = "application/json";
httpRequest1.AllowAutoRedirect = true; httpRequest.AllowAutoRedirect = true;
httpRequest1.SuppressHttpError = true; httpRequest.SuppressHttpError = true;
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest1); 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);
}
else
{
throw new HttpException(httpRequest1, httpResponse);
}
} }
List<Artist> artists = MapArtists(httpResponse.Resource); List<Track> tracks = new List<Track>();
List<Artist> newArtists = new List<Artist>(artists.Count); foreach(var trackResource in httpResponse.Resource.Items)
int count = 0;
foreach (var artist in artists)
{ {
newArtists.Add(AddOverview(artist)); Track track = new Track();
count++; 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);
} }
// I don't know how we are getting tracks from iTunes yet. return tracks;
return new Tuple<Artist, List<Track>>(newArtists[0], new List<Track>());
} }
public List<Artist> SearchForNewArtist(string title) public List<Artist> SearchForNewArtist(string title)
{ {
try try
@ -220,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)
{ {
@ -239,8 +223,8 @@ 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();
@ -249,16 +233,8 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
List<Artist> artists = MapArtists(httpResponse.Resource); List<Artist> artists = MapArtists(httpResponse.Resource);
List<Artist> newArtists = new List<Artist>(artists.Count);
int count = 0;
foreach (var artist in artists)
{
newArtists.Add(AddOverview(artist));
count++;
}
return newArtists; return artists;
} }
catch (HttpException) catch (HttpException)
{ {
@ -271,77 +247,52 @@ namespace NzbDrone.Core.MetadataSource.SkyHook
} }
} }
private Artist AddOverview(Artist artist)
{
var httpRequest = _internalRequestBuilder.Create()
.SetSegment("route", "viewArtist")
.AddQueryParam("id", artist.ItunesId.ToString())
.Build();
httpRequest.Headers.Add("X-Apple-Store-Front", "143459-2,32 t:music3");
httpRequest.Headers.ContentType = "application/json";
var httpResponse = _httpClient.Get<ArtistResource>(httpRequest);
if (!httpResponse.HasHttpError)
{
artist.Overview = httpResponse.Resource.StorePlatformData.Artist.Results[artist.ItunesId].artistBio;
}
return artist;
}
private Artist MapArtistInfo(ArtistInfoResource resource) private Artist MapArtistInfo(ArtistInfoResource resource)
{ {
// This expects ArtistInfoResource, thus just need to populate one artist // This expects ArtistInfoResource, thus just need to populate one artist
Artist artist = new Artist(); Artist artist = new Artist();
artist.Overview = resource.artistBio; //artist.Overview = resource.artistBio;
artist.ArtistName = resource.name; //artist.ArtistName = resource.name;
foreach(var genre in resource.genreNames) //foreach(var genre in resource.genreNames)
{ //{
artist.Genres.Add(genre); // artist.Genres.Add(genre);
} //}
return artist; 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,15 +75,15 @@ namespace NzbDrone.Core.Music
try try
{ {
tuple = _artistInfo.GetArtistInfo(newArtist.ItunesId); tuple = _artistInfo.GetArtistInfo(newArtist.SpotifyId);
} }
catch (SeriesNotFoundException) catch (SeriesNotFoundException)
{ {
_logger.Error("iTunesId {1} was not found, it may have been removed from iTunes.", 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("iTunesId", "An artist with this ID was not found", newArtist.ItunesId) new ValidationFailure("SpotifyId", "An artist with this ID was not found", newArtist.SpotifyId)
}); });
} }

@ -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,7 +22,7 @@ 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; }
@ -32,44 +32,28 @@ namespace NzbDrone.Core.Music
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;
@ -88,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));
} }
} }
} }

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

@ -8,7 +8,7 @@
<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="artist/{{artistName}}" target="_blank"> <a href="artist/{{artistNameSlug}}" target="_blank">
<h2>{{artistName}}</h2> <h2>{{artistName}}</h2>
</a> </a>
</div> </div>
@ -50,6 +50,9 @@
<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>

Loading…
Cancel
Save