New: Grouped Album Import Notification (#265)

* New: Grouped Album Import Notification

* fixup: Add Emby and Kodi Notify for Album Download
This commit is contained in:
Qstick 2018-04-03 23:45:59 -04:00 committed by GitHub
parent fa63b962ea
commit 226f884233
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 345 additions and 51 deletions

View file

@ -39,10 +39,12 @@ function EditNotificationModalContent(props) {
name, name,
onGrab, onGrab,
onDownload, onDownload,
onAlbumDownload,
onUpgrade, onUpgrade,
onRename, onRename,
supportsOnGrab, supportsOnGrab,
supportsOnDownload, supportsOnDownload,
supportsOnAlbumDownload,
supportsOnUpgrade, supportsOnUpgrade,
supportsOnRename, supportsOnRename,
tags, tags,
@ -107,12 +109,25 @@ function EditNotificationModalContent(props) {
</FormGroup> </FormGroup>
<FormGroup> <FormGroup>
<FormLabel>On Import</FormLabel> <FormLabel>On Album Import</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="onAlbumDownload"
helpText="Be notified when complete albums are successfully imported"
isDisabled={!supportsOnAlbumDownload.value}
{...onAlbumDownload}
onChange={onInputChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>On Track Import</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="onDownload" name="onDownload"
helpText="Be notified when tracks are successfully imported" helpText="Be notified when track files are successfully imported"
isDisabled={!supportsOnDownload.value} isDisabled={!supportsOnDownload.value}
{...onDownload} {...onDownload}
onChange={onInputChange} onChange={onInputChange}
@ -122,7 +137,7 @@ function EditNotificationModalContent(props) {
{ {
onDownload.value && onDownload.value &&
<FormGroup> <FormGroup>
<FormLabel>On Upgrade</FormLabel> <FormLabel>On Track Upgrade</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}

View file

@ -68,10 +68,12 @@ class Notification extends Component {
name, name,
onGrab, onGrab,
onDownload, onDownload,
onAlbumDownload,
onUpgrade, onUpgrade,
onRename, onRename,
supportsOnGrab, supportsOnGrab,
supportsOnDownload, supportsOnDownload,
supportsOnAlbumDownload,
supportsOnUpgrade, supportsOnUpgrade,
supportsOnRename supportsOnRename
} = this.props; } = this.props;
@ -93,11 +95,18 @@ class Notification extends Component {
On Grab On Grab
</Label> </Label>
<Label
kind={getLabelKind(supportsOnAlbumDownload, onAlbumDownload)}
outline={supportsOnAlbumDownload && !onAlbumDownload}
>
On Album Download
</Label>
<Label <Label
kind={getLabelKind(supportsOnDownload, onDownload)} kind={getLabelKind(supportsOnDownload, onDownload)}
outline={supportsOnDownload && !onDownload} outline={supportsOnDownload && !onDownload}
> >
On Download On Track Download
</Label> </Label>
<Label <Label
@ -140,10 +149,12 @@ Notification.propTypes = {
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
onGrab: PropTypes.bool.isRequired, onGrab: PropTypes.bool.isRequired,
onDownload: PropTypes.bool.isRequired, onDownload: PropTypes.bool.isRequired,
onAlbumDownload: PropTypes.bool.isRequired,
onUpgrade: PropTypes.bool.isRequired, onUpgrade: PropTypes.bool.isRequired,
onRename: PropTypes.bool.isRequired, onRename: PropTypes.bool.isRequired,
supportsOnGrab: PropTypes.bool.isRequired, supportsOnGrab: PropTypes.bool.isRequired,
supportsOnDownload: PropTypes.bool.isRequired, supportsOnDownload: PropTypes.bool.isRequired,
supportsOnAlbumDownload: PropTypes.bool.isRequired,
supportsOnUpgrade: PropTypes.bool.isRequired, supportsOnUpgrade: PropTypes.bool.isRequired,
supportsOnRename: PropTypes.bool.isRequired, supportsOnRename: PropTypes.bool.isRequired,
onConfirmDeleteNotification: PropTypes.func.isRequired onConfirmDeleteNotification: PropTypes.func.isRequired

View file

@ -1,4 +1,4 @@
using NzbDrone.Core.Notifications; using NzbDrone.Core.Notifications;
namespace Lidarr.Api.V1.Notifications namespace Lidarr.Api.V1.Notifications
{ {
@ -7,10 +7,12 @@ namespace Lidarr.Api.V1.Notifications
public string Link { get; set; } public string Link { get; set; }
public bool OnGrab { get; set; } public bool OnGrab { get; set; }
public bool OnDownload { get; set; } public bool OnDownload { get; set; }
public bool OnAlbumDownload { get; set; }
public bool OnUpgrade { get; set; } public bool OnUpgrade { get; set; }
public bool OnRename { get; set; } public bool OnRename { get; set; }
public bool SupportsOnGrab { get; set; } public bool SupportsOnGrab { get; set; }
public bool SupportsOnDownload { get; set; } public bool SupportsOnDownload { get; set; }
public bool SupportsOnAlbumDownload { get; set; }
public bool SupportsOnUpgrade { get; set; } public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; } public bool SupportsOnRename { get; set; }
public string TestCommand { get; set; } public string TestCommand { get; set; }
@ -26,10 +28,12 @@ namespace Lidarr.Api.V1.Notifications
resource.OnGrab = definition.OnGrab; resource.OnGrab = definition.OnGrab;
resource.OnDownload = definition.OnDownload; resource.OnDownload = definition.OnDownload;
resource.OnAlbumDownload = definition.OnAlbumDownload;
resource.OnUpgrade = definition.OnUpgrade; resource.OnUpgrade = definition.OnUpgrade;
resource.OnRename = definition.OnRename; resource.OnRename = definition.OnRename;
resource.SupportsOnGrab = definition.SupportsOnGrab; resource.SupportsOnGrab = definition.SupportsOnGrab;
resource.SupportsOnDownload = definition.SupportsOnDownload; resource.SupportsOnDownload = definition.SupportsOnDownload;
resource.SupportsOnAlbumDownload = definition.SupportsOnAlbumDownload;
resource.SupportsOnUpgrade = definition.SupportsOnUpgrade; resource.SupportsOnUpgrade = definition.SupportsOnUpgrade;
resource.SupportsOnRename = definition.SupportsOnRename; resource.SupportsOnRename = definition.SupportsOnRename;
@ -44,10 +48,12 @@ namespace Lidarr.Api.V1.Notifications
definition.OnGrab = resource.OnGrab; definition.OnGrab = resource.OnGrab;
definition.OnDownload = resource.OnDownload; definition.OnDownload = resource.OnDownload;
definition.OnAlbumDownload = resource.OnAlbumDownload;
definition.OnUpgrade = resource.OnUpgrade; definition.OnUpgrade = resource.OnUpgrade;
definition.OnRename = resource.OnRename; definition.OnRename = resource.OnRename;
definition.SupportsOnGrab = resource.SupportsOnGrab; definition.SupportsOnGrab = resource.SupportsOnGrab;
definition.SupportsOnDownload = resource.SupportsOnDownload; definition.SupportsOnDownload = resource.SupportsOnDownload;
definition.SupportsOnAlbumDownload = resource.SupportsOnAlbumDownload;
definition.SupportsOnUpgrade = resource.SupportsOnUpgrade; definition.SupportsOnUpgrade = resource.SupportsOnUpgrade;
definition.SupportsOnRename = resource.SupportsOnRename; definition.SupportsOnRename = resource.SupportsOnRename;

View file

@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.NotificationTests
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override void OnDownload(DownloadMessage downloadMessage) public override void OnDownload(TrackDownloadMessage trackDownloadMessage)
{ {
TestLogger.Info("OnDownload was called"); TestLogger.Info("OnDownload was called");
} }
@ -55,11 +55,16 @@ namespace NzbDrone.Core.Test.NotificationTests
TestLogger.Info("OnGrab was called"); TestLogger.Info("OnGrab was called");
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
TestLogger.Info("OnDownload was called"); TestLogger.Info("OnDownload was called");
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
TestLogger.Info("OnAlbumDownload was called");
}
public override void OnRename(Artist artist) public override void OnRename(Artist artist)
{ {
TestLogger.Info("OnRename was called"); TestLogger.Info("OnRename was called");
@ -100,6 +105,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnGrab.Should().BeTrue(); notification.SupportsOnGrab.Should().BeTrue();
notification.SupportsOnDownload.Should().BeTrue(); notification.SupportsOnDownload.Should().BeTrue();
notification.SupportsOnAlbumDownload.Should().BeTrue();
notification.SupportsOnUpgrade.Should().BeTrue(); notification.SupportsOnUpgrade.Should().BeTrue();
notification.SupportsOnRename.Should().BeTrue(); notification.SupportsOnRename.Should().BeTrue();
} }
@ -112,6 +118,7 @@ namespace NzbDrone.Core.Test.NotificationTests
notification.SupportsOnGrab.Should().BeFalse(); notification.SupportsOnGrab.Should().BeFalse();
notification.SupportsOnDownload.Should().BeFalse(); notification.SupportsOnDownload.Should().BeFalse();
notification.SupportsOnAlbumDownload.Should().BeFalse();
notification.SupportsOnUpgrade.Should().BeFalse(); notification.SupportsOnUpgrade.Should().BeFalse();
notification.SupportsOnRename.Should().BeFalse(); notification.SupportsOnRename.Should().BeFalse();
} }

View file

@ -14,7 +14,7 @@ namespace NzbDrone.Core.Test.NotificationTests
public class SynologyIndexerFixture : CoreTest<SynologyIndexer> public class SynologyIndexerFixture : CoreTest<SynologyIndexer>
{ {
private Artist _artist; private Artist _artist;
private DownloadMessage _upgrade; private TrackDownloadMessage _upgrade;
[SetUp] [SetUp]
public void SetUp() public void SetUp()
@ -24,7 +24,7 @@ namespace NzbDrone.Core.Test.NotificationTests
Path = @"C:\Test\".AsOsAgnostic() Path = @"C:\Test\".AsOsAgnostic()
}; };
_upgrade = new DownloadMessage() _upgrade = new TrackDownloadMessage()
{ {
Artist = _artist, Artist = _artist,

View file

@ -14,7 +14,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
[TestFixture] [TestFixture]
public class OnDownloadFixture : CoreTest<Notifications.Xbmc.Xbmc> public class OnDownloadFixture : CoreTest<Notifications.Xbmc.Xbmc>
{ {
private DownloadMessage _downloadMessage; private TrackDownloadMessage _trackDownloadMessage;
[SetUp] [SetUp]
public void Setup() public void Setup()
@ -25,7 +25,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
var trackFile = Builder<TrackFile>.CreateNew() var trackFile = Builder<TrackFile>.CreateNew()
.Build(); .Build();
_downloadMessage = Builder<DownloadMessage>.CreateNew() _trackDownloadMessage = Builder<TrackDownloadMessage>.CreateNew()
.With(d => d.Artist = artist) .With(d => d.Artist = artist)
.With(d => d.TrackFile = trackFile) .With(d => d.TrackFile = trackFile)
.With(d => d.OldFiles = new List<TrackFile>()) .With(d => d.OldFiles = new List<TrackFile>())
@ -40,7 +40,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
private void GivenOldFiles() private void GivenOldFiles()
{ {
_downloadMessage.OldFiles = Builder<TrackFile>.CreateListOfSize(1) _trackDownloadMessage.OldFiles = Builder<TrackFile>.CreateListOfSize(1)
.Build() .Build()
.ToList(); .ToList();
@ -54,7 +54,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
[Test] [Test]
public void should_not_clean_if_no_episode_was_replaced() public void should_not_clean_if_no_episode_was_replaced()
{ {
Subject.OnDownload(_downloadMessage); Subject.OnDownload(_trackDownloadMessage);
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Never()); Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Never());
} }
@ -63,7 +63,7 @@ namespace NzbDrone.Core.Test.NotificationTests.Xbmc
public void should_clean_if_episode_was_replaced() public void should_clean_if_episode_was_replaced()
{ {
GivenOldFiles(); GivenOldFiles();
Subject.OnDownload(_downloadMessage); Subject.OnDownload(_trackDownloadMessage);
Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Once()); Mocker.GetMock<IXbmcService>().Verify(v => v.Clean(It.IsAny<XbmcSettings>()), Times.Once());
} }

View file

@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(13)]
public class album_download_notification : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("Notifications").AddColumn("OnAlbumDownload").AsBoolean().WithDefaultValue(0);
}
}
}

View file

@ -71,6 +71,7 @@ namespace NzbDrone.Core.Datastore
Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications") Mapper.Entity<NotificationDefinition>().RegisterDefinition("Notifications")
.Ignore(i => i.SupportsOnGrab) .Ignore(i => i.SupportsOnGrab)
.Ignore(i => i.SupportsOnDownload) .Ignore(i => i.SupportsOnDownload)
.Ignore(i => i.SupportsOnAlbumDownload)
.Ignore(i => i.SupportsOnUpgrade) .Ignore(i => i.SupportsOnUpgrade)
.Ignore(i => i.SupportsOnRename); .Ignore(i => i.SupportsOnRename);

View file

@ -0,0 +1,33 @@
using System.Collections.Generic;
using NzbDrone.Common.Messaging;
using NzbDrone.Core.Download;
using NzbDrone.Core.Music;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles.Events
{
public class AlbumImportedEvent : IEvent
{
public Artist Artist { get; private set; }
public Album Album { get; private set; }
public List<LocalTrack> ImportedTracks { get; private set; }
public bool NewDownload { get; private set; }
public string DownloadClient { get; private set; }
public string DownloadId { get; private set; }
public AlbumImportedEvent(Artist artist, Album album, List<LocalTrack> importedTracks, bool newDownload, DownloadClientItem downloadClientItem)
{
Artist = artist;
Album = album;
ImportedTracks = importedTracks;
NewDownload = newDownload;
if (downloadClientItem != null)
{
DownloadClient = downloadClientItem.DownloadClient;
DownloadId = downloadClientItem.DownloadId;
}
}
}
}

View file

@ -157,6 +157,19 @@ namespace NzbDrone.Core.MediaFiles.TrackImport
} }
} }
var albumImports = importResults.GroupBy(e => e.ImportDecision.LocalTrack.Album.Id).ToList();
foreach (var albumImport in albumImports)
{
var album = albumImport.First().ImportDecision.LocalTrack.Album;
var artist = albumImport.First().ImportDecision.LocalTrack.Artist;
if (albumImport.Where(e => e.Errors.Count == 0).ToList().Count > 0 && artist != null && album != null)
{
_eventAggregator.PublishEvent(new AlbumImportedEvent(artist, album, albumImport.Select(e => e.ImportDecision.LocalTrack).ToList(), newDownload, downloadClientItem));
}
}
//Adding all the rejected decisions //Adding all the rejected decisions
importResults.AddRange(decisions.Where(c => !c.Approved) importResults.AddRange(decisions.Where(c => !c.Approved)
.Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray()))); .Select(d => new ImportResult(d, d.Rejections.Select(r => r.Reason).ToArray())));

View file

@ -0,0 +1,21 @@
using System.Collections.Generic;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Music;
namespace NzbDrone.Core.Notifications
{
public class AlbumDownloadMessage
{
public string Message { get; set; }
public Artist Artist { get; set; }
public Album Album { get; set; }
public List<TrackFile> TrackFiles { get; set; }
public string DownloadClient { get; set; }
public string DownloadId { get; set; }
public override string ToString()
{
return Message;
}
}
}

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -21,7 +21,12 @@ namespace NzbDrone.Core.Notifications.Boxcar
_proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings); _proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE , message.Message, Settings);
}
public override void OnAlbumDownload(AlbumDownloadMessage message)
{ {
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings); _proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings);
} }

View file

@ -55,7 +55,7 @@ namespace NzbDrone.Core.Notifications.CustomScript
ExecuteScript(environmentVariables); ExecuteScript(environmentVariables);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
var artist = message.Artist; var artist = message.Artist;
var trackFile = message.TrackFile; var trackFile = message.TrackFile;
@ -94,6 +94,27 @@ namespace NzbDrone.Core.Notifications.CustomScript
ExecuteScript(environmentVariables); ExecuteScript(environmentVariables);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
var artist = message.Artist;
var album = message.Album;
var environmentVariables = new StringDictionary();
environmentVariables.Add("Lidarr_EventType", "AlbumDownload");
environmentVariables.Add("Lidarr_Artist_Id", artist.Id.ToString());
environmentVariables.Add("Lidarr_Artist_Name", artist.Name);
environmentVariables.Add("Lidarr_Artist_Path", artist.Path);
environmentVariables.Add("Lidarr_Artist_MBId", artist.ForeignArtistId);
environmentVariables.Add("Lidarr_Artist_Type", artist.ArtistType);
environmentVariables.Add("Lidarr_Album_Id", album.Id.ToString());
environmentVariables.Add("Lidarr_Album_Title", album.Title);
environmentVariables.Add("Lidarr_Album_MBId", album.ForeignAlbumId);
environmentVariables.Add("Lidarr_Download_Client", message.DownloadClient ?? string.Empty);
environmentVariables.Add("Lidarr_Download_Id", message.DownloadId ?? string.Empty);
ExecuteScript(environmentVariables);
}
public override void OnRename(Artist artist) public override void OnRename(Artist artist)
{ {
var environmentVariables = new StringDictionary(); var environmentVariables = new StringDictionary();

View file

@ -25,13 +25,19 @@ namespace NzbDrone.Core.Notifications.Email
_emailService.SendEmail(Settings, ALBUM_GRABBED_TITLE_BRANDED, body); _emailService.SendEmail(Settings, ALBUM_GRABBED_TITLE_BRANDED, body);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
var body = $"{message.Message} Downloaded and sorted."; var body = $"{message.Message} Downloaded and sorted.";
_emailService.SendEmail(Settings, TRACK_DOWNLOADED_TITLE_BRANDED, body); _emailService.SendEmail(Settings, TRACK_DOWNLOADED_TITLE_BRANDED, body);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
var body = $"{message.Message} Downloaded and sorted.";
_emailService.SendEmail(Settings, ALBUM_DOWNLOADED_TITLE_BRANDED, body);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {

View file

@ -23,11 +23,15 @@ namespace NzbDrone.Core.Notifications.Growl
_growlService.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, "GRAB", Settings.Host, Settings.Port, Settings.Password); _growlService.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, "GRAB", Settings.Host, Settings.Port, Settings.Password);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_growlService.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password); _growlService.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
_growlService.SendNotification(ALBUM_DOWNLOADED_TITLE, message.Message, "DOWNLOAD", Settings.Host, Settings.Port, Settings.Password);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {

View file

@ -8,10 +8,12 @@ namespace NzbDrone.Core.Notifications
string Link { get; } string Link { get; }
void OnGrab(GrabMessage grabMessage); void OnGrab(GrabMessage grabMessage);
void OnDownload(DownloadMessage message); void OnDownload(TrackDownloadMessage message);
void OnAlbumDownload(AlbumDownloadMessage message);
void OnRename(Artist artist); void OnRename(Artist artist);
bool SupportsOnGrab { get; } bool SupportsOnGrab { get; }
bool SupportsOnDownload { get; } bool SupportsOnDownload { get; }
bool SupportsOnAlbumDownload { get; }
bool SupportsOnUpgrade { get; } bool SupportsOnUpgrade { get; }
bool SupportsOnRename { get; } bool SupportsOnRename { get; }
} }

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -22,11 +22,16 @@ namespace NzbDrone.Core.Notifications.Join
_proxy.SendNotification(ALBUM_GRABBED_TITLE_BRANDED, message.Message, Settings); _proxy.SendNotification(ALBUM_GRABBED_TITLE_BRANDED, message.Message, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE_BRANDED, message.Message, Settings); _proxy.SendNotification(TRACK_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
_proxy.SendNotification(ALBUM_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

View file

@ -26,7 +26,15 @@ namespace NzbDrone.Core.Notifications.Emby
} }
} }
public override void OnDownload(DownloadMessage message) public override void OnAlbumDownload(AlbumDownloadMessage message)
{
if (Settings.Notify)
{
_mediaBrowserService.Notify(Settings, ALBUM_DOWNLOADED_TITLE_BRANDED, message.Message);
}
}
public override void OnDownload(TrackDownloadMessage message)
{ {
if (Settings.Notify) if (Settings.Notify)
{ {

View file

@ -10,9 +10,11 @@ namespace NzbDrone.Core.Notifications
{ {
protected const string ALBUM_GRABBED_TITLE = "Album Grabbed"; protected const string ALBUM_GRABBED_TITLE = "Album Grabbed";
protected const string TRACK_DOWNLOADED_TITLE = "Track Downloaded"; protected const string TRACK_DOWNLOADED_TITLE = "Track Downloaded";
protected const string ALBUM_DOWNLOADED_TITLE = "Album Downloaded";
protected const string ALBUM_GRABBED_TITLE_BRANDED = "Lidarr - " + ALBUM_GRABBED_TITLE; protected const string ALBUM_GRABBED_TITLE_BRANDED = "Lidarr - " + ALBUM_GRABBED_TITLE;
protected const string TRACK_DOWNLOADED_TITLE_BRANDED = "Lidarr - " + TRACK_DOWNLOADED_TITLE; protected const string TRACK_DOWNLOADED_TITLE_BRANDED = "Lidarr - " + TRACK_DOWNLOADED_TITLE;
protected const string ALBUM_DOWNLOADED_TITLE_BRANDED = "Lidarr - " + ALBUM_DOWNLOADED_TITLE;
public abstract string Name { get; } public abstract string Name { get; }
@ -32,12 +34,17 @@ namespace NzbDrone.Core.Notifications
} }
public virtual void OnDownload(DownloadMessage message) public virtual void OnDownload(TrackDownloadMessage message)
{ {
} }
public virtual void OnRename(Artist series) public virtual void OnAlbumDownload(AlbumDownloadMessage message)
{
}
public virtual void OnRename(Artist artist)
{ {
} }
@ -45,6 +52,7 @@ namespace NzbDrone.Core.Notifications
public bool SupportsOnGrab => HasConcreteImplementation("OnGrab"); public bool SupportsOnGrab => HasConcreteImplementation("OnGrab");
public bool SupportsOnRename => HasConcreteImplementation("OnRename"); public bool SupportsOnRename => HasConcreteImplementation("OnRename");
public bool SupportsOnDownload => HasConcreteImplementation("OnDownload"); public bool SupportsOnDownload => HasConcreteImplementation("OnDownload");
public bool SupportsOnAlbumDownload => HasConcreteImplementation("OnAlbumDownload");
public bool SupportsOnUpgrade => SupportsOnDownload; public bool SupportsOnUpgrade => SupportsOnDownload;
protected TSettings Settings => (TSettings)Definition.Settings; protected TSettings Settings => (TSettings)Definition.Settings;

View file

@ -7,13 +7,15 @@ namespace NzbDrone.Core.Notifications
public bool OnGrab { get; set; } public bool OnGrab { get; set; }
public bool OnDownload { get; set; } public bool OnDownload { get; set; }
public bool OnAlbumDownload { get; set; }
public bool OnUpgrade { get; set; } public bool OnUpgrade { get; set; }
public bool OnRename { get; set; } public bool OnRename { get; set; }
public bool SupportsOnGrab { get; set; } public bool SupportsOnGrab { get; set; }
public bool SupportsOnDownload { get; set; } public bool SupportsOnDownload { get; set; }
public bool SupportsOnAlbumDownload { get; set; }
public bool SupportsOnUpgrade { get; set; } public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; } public bool SupportsOnRename { get; set; }
public override bool Enable => OnGrab || OnDownload || (OnDownload && OnUpgrade); public override bool Enable => OnGrab || OnDownload || OnAlbumDownload || (OnDownload && OnUpgrade);
} }
} }

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NLog; using NLog;
using NzbDrone.Common.Composition; using NzbDrone.Common.Composition;
@ -11,6 +11,7 @@ namespace NzbDrone.Core.Notifications
{ {
List<INotification> OnGrabEnabled(); List<INotification> OnGrabEnabled();
List<INotification> OnDownloadEnabled(); List<INotification> OnDownloadEnabled();
List<INotification> OnAlbumDownloadEnabled();
List<INotification> OnUpgradeEnabled(); List<INotification> OnUpgradeEnabled();
List<INotification> OnRenameEnabled(); List<INotification> OnRenameEnabled();
} }
@ -32,6 +33,11 @@ namespace NzbDrone.Core.Notifications
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnDownload).ToList(); return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnDownload).ToList();
} }
public List<INotification> OnAlbumDownloadEnabled()
{
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnAlbumDownload).ToList();
}
public List<INotification> OnUpgradeEnabled() public List<INotification> OnUpgradeEnabled()
{ {
return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnUpgrade).ToList(); return GetAvailableProviders().Where(n => ((NotificationDefinition)n.Definition).OnUpgrade).ToList();
@ -48,6 +54,7 @@ namespace NzbDrone.Core.Notifications
definition.SupportsOnGrab = provider.SupportsOnGrab; definition.SupportsOnGrab = provider.SupportsOnGrab;
definition.SupportsOnDownload = provider.SupportsOnDownload; definition.SupportsOnDownload = provider.SupportsOnDownload;
definition.SupportsOnAlbumDownload = provider.SupportsOnAlbumDownload;
definition.SupportsOnUpgrade = provider.SupportsOnUpgrade; definition.SupportsOnUpgrade = provider.SupportsOnUpgrade;
definition.SupportsOnRename = provider.SupportsOnRename; definition.SupportsOnRename = provider.SupportsOnRename;
} }

View file

@ -9,12 +9,14 @@ using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
using NzbDrone.Core.ThingiProvider; using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.Notifications namespace NzbDrone.Core.Notifications
{ {
public class NotificationService public class NotificationService
: IHandle<AlbumGrabbedEvent>, : IHandle<AlbumGrabbedEvent>,
IHandle<TrackImportedEvent>, IHandle<TrackImportedEvent>,
IHandle<AlbumImportedEvent>,
IHandle<ArtistRenamedEvent> IHandle<ArtistRenamedEvent>
{ {
private readonly INotificationFactory _notificationFactory; private readonly INotificationFactory _notificationFactory;
@ -62,6 +64,14 @@ namespace NzbDrone.Core.Notifications
qualityString); qualityString);
} }
private string GetAlbumDownloadMessage(Artist artist, Album album, List<LocalTrack> tracks)
{
return string.Format("{0} - {1} ({2} Tracks Imported)",
artist.Name,
album.Title,
tracks.Count);
}
private bool ShouldHandleArtist(ProviderDefinition definition, Artist artist) private bool ShouldHandleArtist(ProviderDefinition definition, Artist artist)
{ {
if (definition.Tags.Empty()) if (definition.Tags.Empty())
@ -115,7 +125,7 @@ namespace NzbDrone.Core.Notifications
return; return;
} }
var downloadMessage = new DownloadMessage var downloadMessage = new TrackDownloadMessage
{ {
Message = GetTrackMessage(message.TrackInfo.Artist, message.TrackInfo.Tracks, message.TrackInfo.Quality), Message = GetTrackMessage(message.TrackInfo.Artist, message.TrackInfo.Tracks, message.TrackInfo.Quality),
@ -147,6 +157,40 @@ namespace NzbDrone.Core.Notifications
} }
} }
public void Handle(AlbumImportedEvent message)
{
if (!message.NewDownload)
{
return;
}
var downloadMessage = new AlbumDownloadMessage
{
Message = GetAlbumDownloadMessage(message.Artist, message.Album, message.ImportedTracks),
Artist = message.Artist,
Album = message.Album,
DownloadClient = message.DownloadClient,
DownloadId = message.DownloadId
};
foreach (var notification in _notificationFactory.OnAlbumDownloadEnabled())
{
try
{
if (ShouldHandleArtist(notification.Definition, message.Artist))
{
notification.OnAlbumDownload(downloadMessage);
}
}
catch (Exception ex)
{
_logger.Warn(ex, "Unable to send OnDownload notification to: " + notification.Definition.Name);
}
}
}
public void Handle(ArtistRenamedEvent message) public void Handle(ArtistRenamedEvent message)
{ {
foreach (var notification in _notificationFactory.OnRenameEnabled()) foreach (var notification in _notificationFactory.OnRenameEnabled())

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -21,11 +21,16 @@ namespace NzbDrone.Core.Notifications.NotifyMyAndroid
_proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); _proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority); _proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
_proxy.SendNotification(ALBUM_DOWNLOADED_TITLE, message.Message, Settings.ApiKey, (NotifyMyAndroidPriority)Settings.Priority);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

View file

@ -21,7 +21,7 @@ namespace NzbDrone.Core.Notifications.Plex
_plexClientService.Notify(Settings, ALBUM_GRABBED_TITLE_BRANDED, grabMessage.Message); _plexClientService.Notify(Settings, ALBUM_GRABBED_TITLE_BRANDED, grabMessage.Message);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_plexClientService.Notify(Settings, TRACK_DOWNLOADED_TITLE_BRANDED, message.Message); _plexClientService.Notify(Settings, TRACK_DOWNLOADED_TITLE_BRANDED, message.Message);
} }

View file

@ -26,7 +26,7 @@ namespace NzbDrone.Core.Notifications.Plex
Notify(Settings, ALBUM_GRABBED_TITLE_BRANDED, grabMessage.Message); Notify(Settings, ALBUM_GRABBED_TITLE_BRANDED, grabMessage.Message);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
Notify(Settings, TRACK_DOWNLOADED_TITLE_BRANDED, message.Message); Notify(Settings, TRACK_DOWNLOADED_TITLE_BRANDED, message.Message);
} }

View file

@ -17,7 +17,7 @@ namespace NzbDrone.Core.Notifications.Plex
public override string Link => "https://www.plex.tv/"; public override string Link => "https://www.plex.tv/";
public override string Name => "Plex Media Server"; public override string Name => "Plex Media Server";
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
UpdateIfEnabled(message.Artist); UpdateIfEnabled(message.Artist);
} }

View file

@ -22,11 +22,16 @@ namespace NzbDrone.Core.Notifications.Prowl
_prowlService.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority); _prowlService.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_prowlService.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority); _prowlService.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
_prowlService.SendNotification(ALBUM_DOWNLOADED_TITLE, message.Message, Settings.ApiKey, (NotificationPriority)Settings.Priority);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -22,11 +22,16 @@ namespace NzbDrone.Core.Notifications.PushBullet
_proxy.SendNotification(ALBUM_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings); _proxy.SendNotification(ALBUM_GRABBED_TITLE_BRANDED, grabMessage.Message, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE_BRANDED, message.Message, Settings); _proxy.SendNotification(TRACK_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
_proxy.SendNotification(ALBUM_DOWNLOADED_TITLE_BRANDED, message.Message, Settings);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

View file

@ -21,11 +21,15 @@ namespace NzbDrone.Core.Notifications.Pushalot
_proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings); _proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings); _proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
_proxy.SendNotification(ALBUM_DOWNLOADED_TITLE, message.Message, Settings);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -21,11 +21,16 @@ namespace NzbDrone.Core.Notifications.Pushover
_proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings); _proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings); _proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings);
} }
public override void OnAlbumDownload(AlbumDownloadMessage message)
{
_proxy.SendNotification(ALBUM_DOWNLOADED_TITLE, message.Message, Settings);
}
public override ValidationResult Test() public override ValidationResult Test()
{ {
var failures = new List<ValidationFailure>(); var failures = new List<ValidationFailure>();

View file

@ -45,7 +45,24 @@ namespace NzbDrone.Core.Notifications.Slack
_proxy.SendPayload(payload, Settings); _proxy.SendPayload(payload, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{
var attachments = new List<Attachment>
{
new Attachment
{
Fallback = message.Message,
Title = message.Artist.Name,
Text = message.Message,
Color = "good"
}
};
var payload = CreatePayload($"Imported: {message.Message}", attachments);
_proxy.SendPayload(payload, Settings);
}
public override void OnAlbumDownload(AlbumDownloadMessage message)
{ {
var attachments = new List<Attachment> var attachments = new List<Attachment>
{ {

View file

@ -20,7 +20,7 @@ namespace NzbDrone.Core.Notifications.Synology
public override string Name => "Synology Indexer"; public override string Name => "Synology Indexer";
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
if (Settings.UpdateLibrary) if (Settings.UpdateLibrary)
{ {

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@ -21,7 +21,12 @@ namespace NzbDrone.Core.Notifications.Telegram
_proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings); _proxy.SendNotification(ALBUM_GRABBED_TITLE, grabMessage.Message, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings);
}
public override void OnAlbumDownload(AlbumDownloadMessage message)
{ {
_proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings); _proxy.SendNotification(TRACK_DOWNLOADED_TITLE, message.Message, Settings);
} }

View file

@ -4,7 +4,7 @@ using NzbDrone.Core.Music;
namespace NzbDrone.Core.Notifications namespace NzbDrone.Core.Notifications
{ {
public class DownloadMessage public class TrackDownloadMessage
{ {
public string Message { get; set; } public string Message { get; set; }
public Artist Artist { get; set; } public Artist Artist { get; set; }

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation.Results; using FluentValidation.Results;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Exceptions; using NzbDrone.Core.Exceptions;
@ -24,7 +24,12 @@ namespace NzbDrone.Core.Notifications.Twitter
_twitterService.SendNotification($"Grabbed: {message.Message}", Settings); _twitterService.SendNotification($"Grabbed: {message.Message}", Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{
_twitterService.SendNotification($"Imported: {message.Message}", Settings);
}
public override void OnAlbumDownload(AlbumDownloadMessage message)
{ {
_twitterService.SendNotification($"Imported: {message.Message}", Settings); _twitterService.SendNotification($"Imported: {message.Message}", Settings);
} }

View file

@ -41,7 +41,7 @@ namespace NzbDrone.Core.Notifications.Webhook
_proxy.SendWebhook(payload, Settings); _proxy.SendWebhook(payload, Settings);
} }
public override void OnDownload(DownloadMessage message) public override void OnDownload(TrackDownloadMessage message)
{ {
var trackFile = message.TrackFile; var trackFile = message.TrackFile;

View file

@ -28,7 +28,14 @@ namespace NzbDrone.Core.Notifications.Xbmc
Notify(Settings, header, grabMessage.Message); Notify(Settings, header, grabMessage.Message);
} }
public override void OnDownload(DownloadMessage message) public override void OnAlbumDownload(AlbumDownloadMessage message)
{
const string header = "Lidarr - Downloaded";
Notify(Settings, header, message.Message);
}
public override void OnDownload(TrackDownloadMessage message)
{ {
const string header = "Lidarr - Downloaded"; const string header = "Lidarr - Downloaded";

View file

@ -179,6 +179,7 @@
<Compile Include="Datastore\Migration\003_add_medium_support.cs" /> <Compile Include="Datastore\Migration\003_add_medium_support.cs" />
<Compile Include="Datastore\Migration\006_separate_automatic_and_interactive_search.cs" /> <Compile Include="Datastore\Migration\006_separate_automatic_and_interactive_search.cs" />
<Compile Include="Datastore\Migration\007_change_album_path_to_relative.cs" /> <Compile Include="Datastore\Migration\007_change_album_path_to_relative.cs" />
<Compile Include="Datastore\Migration\013_album_download_notification.cs" />
<Compile Include="Datastore\Migration\009_album_releases.cs" /> <Compile Include="Datastore\Migration\009_album_releases.cs" />
<Compile Include="Datastore\Migration\010_album_releases_fix.cs" /> <Compile Include="Datastore\Migration\010_album_releases_fix.cs" />
<Compile Include="Datastore\Migration\011_import_lists.cs" /> <Compile Include="Datastore\Migration\011_import_lists.cs" />
@ -684,6 +685,7 @@
<Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" /> <Compile Include="MediaFiles\Commands\CleanUpRecycleBinCommand.cs" />
<Compile Include="MediaFiles\Commands\DownloadedAlbumsScanCommand.cs" /> <Compile Include="MediaFiles\Commands\DownloadedAlbumsScanCommand.cs" />
<Compile Include="MediaFiles\Commands\RenameArtistCommand.cs" /> <Compile Include="MediaFiles\Commands\RenameArtistCommand.cs" />
<Compile Include="MediaFiles\Events\AlbumImportedEvent.cs" />
<Compile Include="MediaFiles\Events\TrackFileRenamedEvent.cs" /> <Compile Include="MediaFiles\Events\TrackFileRenamedEvent.cs" />
<Compile Include="MediaFiles\Events\TrackFolderCreatedEvent.cs" /> <Compile Include="MediaFiles\Events\TrackFolderCreatedEvent.cs" />
<Compile Include="MediaFiles\Events\TrackImportFailedEvent.cs" /> <Compile Include="MediaFiles\Events\TrackImportFailedEvent.cs" />
@ -858,6 +860,7 @@
<Compile Include="Music\Track.cs" /> <Compile Include="Music\Track.cs" />
<Compile Include="Music\TrackRepository.cs" /> <Compile Include="Music\TrackRepository.cs" />
<Compile Include="Music\TrackService.cs" /> <Compile Include="Music\TrackService.cs" />
<Compile Include="Notifications\AlbumDownloadMessage.cs" />
<Compile Include="Notifications\Join\JoinAuthException.cs" /> <Compile Include="Notifications\Join\JoinAuthException.cs" />
<Compile Include="Notifications\Join\JoinInvalidDeviceException.cs" /> <Compile Include="Notifications\Join\JoinInvalidDeviceException.cs" />
<Compile Include="Notifications\Join\JoinResponseModel.cs" /> <Compile Include="Notifications\Join\JoinResponseModel.cs" />
@ -953,7 +956,7 @@
<Compile Include="RemotePathMappings\RemotePathMappingRepository.cs" /> <Compile Include="RemotePathMappings\RemotePathMappingRepository.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingService.cs" /> <Compile Include="RemotePathMappings\RemotePathMappingService.cs" />
<Compile Include="MediaFiles\TorrentInfo\TorrentFileInfoReader.cs" /> <Compile Include="MediaFiles\TorrentInfo\TorrentFileInfoReader.cs" />
<Compile Include="Notifications\DownloadMessage.cs" /> <Compile Include="Notifications\TrackDownloadMessage.cs" />
<Compile Include="Notifications\Email\Email.cs"> <Compile Include="Notifications\Email\Email.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>