Merge pull request #23 from tidusjar/dev

Release 1.3.0
pull/41/head v1.3.0
Jamie 8 years ago
commit 2dee30bfc7

@ -0,0 +1,43 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: IPushbulletApi.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using PlexRequests.Api.Models.Notifications;
namespace PlexRequests.Api.Interfaces
{
public interface IPushbulletApi
{
/// <summary>
/// Pushes the specified message.
/// </summary>
/// <param name="accessToken">The access token.</param>
/// <param name="title">The title.</param>
/// <param name="message">The message.</param>
/// <param name="deviceIdentifier">The device identifier.</param>
/// <returns></returns>
PushbulletResponse Push(string accessToken, string title, string message, string deviceIdentifier = default(string));
}
}

@ -48,6 +48,7 @@
<Compile Include="IApiRequest.cs" />
<Compile Include="ICouchPotatoApi.cs" />
<Compile Include="IPlexApi.cs" />
<Compile Include="IPushbulletApi.cs" />
<Compile Include="ISonarrApi.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

@ -19,65 +19,65 @@ namespace PlexRequests.Api.Models.Movie
public class Images
{
public List<object> disc_art { get; set; }
public List<string> poster { get; set; }
public List<object> extra_thumbs { get; set; }
public List<string> poster_original { get; set; }
public List<object> landscape { get; set; }
public string[] actors { get; set; }
public List<string> backdrop_original { get; set; }
public List<object> clear_art { get; set; }
public List<object> logo { get; set; }
public List<object> banner { get; set; }
public List<string> backdrop { get; set; }
public List<object> extra_fanart { get; set; }
}
public class Images
{
public List<object> disc_art { get; set; }
public List<string> poster { get; set; }
public List<object> extra_thumbs { get; set; }
public List<string> poster_original { get; set; }
public List<object> landscape { get; set; }
public string[] actors { get; set; }
public List<string> backdrop_original { get; set; }
public List<object> clear_art { get; set; }
public List<object> logo { get; set; }
public List<object> banner { get; set; }
public List<string> backdrop { get; set; }
public List<object> extra_fanart { get; set; }
}
public class Info
{
public Rating rating { get; set; }
public List<string> genres { get; set; }
public int tmdb_id { get; set; }
public string plot { get; set; }
public string tagline { get; set; }
public string original_title { get; set; }
public string[] actor_roles { get; set; }
public bool via_imdb { get; set; }
public string mpaa { get; set; }
public bool via_tmdb { get; set; }
public List<string> directors { get; set; }
public List<string> titles { get; set; }
public string imdb { get; set; }
public int year { get; set; }
public Images images { get; set; }
public List<string> actors { get; set; }
public List<string> writers { get; set; }
public int runtime { get; set; }
public string type { get; set; }
public string released { get; set; }
}
public class Info
{
public Rating rating { get; set; }
public List<string> genres { get; set; }
public int tmdb_id { get; set; }
public string plot { get; set; }
public string tagline { get; set; }
public string original_title { get; set; }
public string[] actor_roles { get; set; }
public bool via_imdb { get; set; }
public string mpaa { get; set; }
public bool via_tmdb { get; set; }
public List<string> directors { get; set; }
public List<string> titles { get; set; }
public string imdb { get; set; }
public int year { get; set; }
public Images images { get; set; }
public List<string> actors { get; set; }
public List<string> writers { get; set; }
public int runtime { get; set; }
public string type { get; set; }
public string released { get; set; }
}
public class Identifiers
{
public string imdb { get; set; }
}
public class Identifiers
{
public string imdb { get; set; }
}
public class Movie
{
public string status { get; set; }
public Info info { get; set; }
public string _t { get; set; }
public List<object> releases { get; set; }
public string title { get; set; }
public string _rev { get; set; }
public string profile_id { get; set; }
public string _id { get; set; }
public object category_id { get; set; }
public string type { get; set; }
public Identifiers identifiers { get; set; }
}
public class Movie
{
public string status { get; set; }
public Info info { get; set; }
public string _t { get; set; }
public List<object> releases { get; set; }
public string title { get; set; }
public string _rev { get; set; }
public string profile_id { get; set; }
public string _id { get; set; }
public object category_id { get; set; }
public string type { get; set; }
public Identifiers identifiers { get; set; }
}
}

@ -0,0 +1,38 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PushbulletPush.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Newtonsoft.Json;
namespace PlexRequests.Api.Models.Notifications
{
public class PushbulletPush
{
public string body { get; set; }
public string title { get; set; }
public string type { get; set; }
public string device_iden { get; set; }
}
}

@ -0,0 +1,48 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PushbulletResponse.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Api.Models.Notifications
{
public class PushbulletResponse
{
public bool active { get; set; }
public string iden { get; set; }
public double created { get; set; }
public double modified { get; set; }
public string type { get; set; }
public bool dismissed { get; set; }
public string direction { get; set; }
public string sender_iden { get; set; }
public string sender_email { get; set; }
public string sender_email_normalized { get; set; }
public string sender_name { get; set; }
public string receiver_iden { get; set; }
public string receiver_email { get; set; }
public string receiver_email_normalized { get; set; }
public string title { get; set; }
public string body { get; set; }
}
}

@ -31,6 +31,10 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.8.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@ -43,6 +47,8 @@
<ItemGroup>
<Compile Include="Movie\CouchPotatoAdd.cs" />
<Compile Include="Movie\CouchPotatoStatus.cs" />
<Compile Include="Notifications\PushbulletPush.cs" />
<Compile Include="Notifications\PushbulletResponse.cs" />
<Compile Include="Plex\PlexAuthentication.cs" />
<Compile Include="Plex\PlexError.cs" />
<Compile Include="Plex\PlexFriends.cs" />
@ -58,6 +64,9 @@
<Compile Include="Tv\TvShow.cs" />
<Compile Include="Tv\TvShowImages.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="8.0.2" targetFramework="net46" />
</packages>

@ -26,6 +26,7 @@
#endregion
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

@ -72,6 +72,7 @@
<DependentUpon>MockApiData.resx</DependentUpon>
</Compile>
<Compile Include="Mocks\MockSonarrApi.cs" />
<Compile Include="PushbulletApi.cs" />
<Compile Include="SonarrApi.cs" />
<Compile Include="CouchPotatoApi.cs" />
<Compile Include="MovieBase.cs" />

@ -0,0 +1,63 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PlexApi.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using PlexRequests.Api.Interfaces;
using PlexRequests.Api.Models.Notifications;
using RestSharp;
namespace PlexRequests.Api
{
public class PushbulletApi : IPushbulletApi
{
public PushbulletResponse Push(string accessToken, string title, string message, string deviceIdentifier = default(string))
{
var request = new RestRequest
{
Method = Method.POST,
};
request.AddHeader("Access-Token", accessToken);
request.AddHeader("Content-Type", "application/json");
var push = new PushbulletPush { title = title, body = message, type = "note"};
if (!string.IsNullOrEmpty(deviceIdentifier))
{
push.device_iden = deviceIdentifier;
}
request.AddJsonBody(push);
var api = new ApiRequest();
return api.ExecuteJson<PushbulletResponse>(request, new Uri("https://api.pushbullet.com/v2/pushes"));
}
}
}

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0"/>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6"/></startup></configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6" /></startup></configuration>

@ -35,7 +35,7 @@ namespace PlexRequests.Core
long AddRequest(int providerId, RequestedModel model);
bool CheckRequest(int providerId);
void DeleteRequest(int tmdbId);
void UpdateRequest(int originalId, RequestedModel model);
void UpdateRequest(RequestedModel model);
RequestedModel Get(int id);
IEnumerable<RequestedModel> GetAll();
bool BatchUpdate(List<RequestedModel> model);

@ -0,0 +1,100 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: JsonRequestService.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Newtonsoft.Json;
using PlexRequests.Store;
using PlexRequests.Store.Models;
namespace PlexRequests.Core
{
public class JsonRequestService : IRequestService
{
public JsonRequestService(IRequestRepository repo)
{
Repo = repo;
}
private IRequestRepository Repo { get; }
public long AddRequest(int providerId, RequestedModel model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ReturnBytes(model), ProviderId = model.ProviderId};
return Repo.Insert(entity);
}
public bool CheckRequest(int providerId)
{
var blobs = Repo.GetAll();
return blobs.Any(x => x.ProviderId == providerId);
}
public void DeleteRequest(int tmdbId)
{
var blob = Repo.GetAll().FirstOrDefault(x => x.ProviderId == tmdbId);
Repo.Delete(blob);
}
public void UpdateRequest(RequestedModel model)
{
var entity = new RequestBlobs { Type = model.Type, Content = ReturnBytes(model), ProviderId = model.ProviderId, Id = model.Id};
Repo.Update(entity);
}
public RequestedModel Get(int id)
{
var blob = Repo.Get(id);
var json = Encoding.UTF8.GetString(blob.Content);
var model = JsonConvert.DeserializeObject<RequestedModel>(json);
return model;
}
public IEnumerable<RequestedModel> GetAll()
{
var blobs = Repo.GetAll();
return blobs.Select(b => Encoding.UTF8.GetString(b.Content))
.Select(JsonConvert.DeserializeObject<RequestedModel>)
.ToList();
}
public bool BatchUpdate(List<RequestedModel> model)
{
var entities = model.Select(m => new RequestBlobs { Type = m.Type, Content = ReturnBytes(m), ProviderId = m.ProviderId }).ToList();
return Repo.UpdateAll(entities);
}
public byte[] ReturnBytes(object obj)
{
var json = JsonConvert.SerializeObject(obj);
var bytes = Encoding.UTF8.GetBytes(json);
return bytes;
}
}
}

@ -70,8 +70,10 @@
<Compile Include="CacheKeys.cs" />
<Compile Include="IRequestService.cs" />
<Compile Include="ISettingsService.cs" />
<Compile Include="JsonRequestService.cs" />
<Compile Include="Models\StatusModel.cs" />
<Compile Include="SettingModels\AuthenticationSettings.cs" />
<Compile Include="SettingModels\PushBulletNotificationSettings.cs" />
<Compile Include="SettingModels\EmailNotificationSettings.cs" />
<Compile Include="SettingModels\PlexSettings.cs" />
<Compile Include="SettingModels\SonarrSettings.cs" />

@ -56,9 +56,9 @@ namespace PlexRequests.Core
Repo.Delete(entity);
}
public void UpdateRequest(int originalId, RequestedModel model)
public void UpdateRequest(RequestedModel model)
{
model.Id = originalId;
Repo.Update(model);
}

@ -4,7 +4,7 @@
{
public string EmailHost { get; set; }
public int EmailPort { get; set; }
public bool EmailAuthentication { get; set; }
public bool Ssl { get; set; }
public string RecipientEmail { get; set; }
public string EmailUsername { get; set; }
public string EmailPassword { get; set; }

@ -35,13 +35,14 @@ namespace PlexRequests.Core.SettingModels
{
public string Ip { get; set; }
public int Port { get; set; }
public bool Ssl { get; set; }
[JsonIgnore]
public Uri FullUri
{
get
{
var formatted = Ip.ReturnUri(Port);
var formatted = Ip.ReturnUri(Port, Ssl);
return formatted;
}
}

@ -0,0 +1,9 @@
namespace PlexRequests.Core.SettingModels
{
public class PushbulletNotificationSettings : Settings
{
public bool Enabled { get; set; }
public string AccessToken { get; set; }
public string DeviceIdentifier { get; set; }
}
}

@ -60,7 +60,7 @@ namespace PlexRequests.Core
SearchForTvShows = true,
WeeklyRequestLimit = 0
};
var s = new SettingsServiceV2<PlexRequestSettings>(new JsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var s = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
s.SaveSettings(defaultSettings);
}
}

@ -34,15 +34,24 @@ namespace PlexRequests.Helpers.Tests
public class UriHelperTests
{
[TestCaseSource(nameof(UriData))]
public void CreateUri(string uri, Uri expected)
public void CreateUri1(string uri, Uri expected)
{
var result = uri.ReturnUri();
Assert.That(result, Is.EqualTo(expected));
}
[Test]
public void CreateUriWithSsl()
{
var uri = "192.168.1.69";
var result = uri.ReturnUri(8080, true);
Assert.That(result, Is.EqualTo(new Uri("https://192.168.1.69:8080")));
}
[TestCaseSource(nameof(UriDataWithPort))]
public void CreateUri(string uri, int port, Uri expected)
public void CreateUri2(string uri, int port, Uri expected)
{
var result = uri.ReturnUri(port);

@ -48,8 +48,6 @@ namespace PlexRequests.Helpers
}
}
/// <summary>
/// Returns the URI.
/// </summary>
@ -57,7 +55,7 @@ namespace PlexRequests.Helpers
/// <param name="port">The port.</param>
/// <returns></returns>
/// <exception cref="System.Exception"></exception>
public static Uri ReturnUri(this string val, int port)
public static Uri ReturnUri(this string val, int port, bool ssl = default(bool))
{
if (val == null)
{
@ -75,7 +73,13 @@ namespace PlexRequests.Helpers
else if (val.StartsWith("https://", StringComparison.Ordinal))
{
var split = val.Split('/');
uri = split.Length >= 4 ? new UriBuilder(Uri.UriSchemeHttps, split[2], port, "/" + split[3]) : new UriBuilder(Uri.UriSchemeHttps, split[2], port);
uri = split.Length >= 4
? new UriBuilder(Uri.UriSchemeHttps, split[2], port, "/" + split[3])
: new UriBuilder(Uri.UriSchemeHttps, split[2], port);
}
else if(ssl)
{
uri = new UriBuilder(Uri.UriSchemeHttps, val, port);
}
else
{

@ -0,0 +1,122 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: NotificationServiceTests.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Moq;
using NUnit.Framework;
using PlexRequests.Services.Notification;
namespace PlexRequests.Services.Tests
{
[TestFixture]
public class NotificationServiceTests
{
[Test]
[Ignore("Need to rework due to static class")]
public void SubscribeNewNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
[Ignore("Need to rework due to static class")]
public void SubscribeExistingNotifier()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock1.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers["Notification1"], Is.Not.Null);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
}
[Test]
[Ignore("Need to rework due to static class")]
public void UnSubscribeMissingNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
[Ignore("Need to rework due to static class")]
public void UnSubscribeNotifier()
{
var notificationMock = new Mock<INotification>();
notificationMock.SetupGet(x => x.NotificationName).Returns("Notification1");
NotificationService.Subscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(1));
NotificationService.UnSubscribe(notificationMock.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(0));
}
[Test]
[Ignore("Need to rework due to static class")]
public void PublishWithNoObservers()
{
Assert.DoesNotThrow(
() =>
{ NotificationService.Publish(string.Empty, string.Empty); });
}
[Test]
[Ignore("Need to rework due to static class")]
public void PublishAllNotifiers()
{
var notificationMock1 = new Mock<INotification>();
var notificationMock2 = new Mock<INotification>();
notificationMock1.SetupGet(x => x.NotificationName).Returns("Notification1");
notificationMock2.SetupGet(x => x.NotificationName).Returns("Notification2");
NotificationService.Subscribe(notificationMock1.Object);
NotificationService.Subscribe(notificationMock2.Object);
Assert.That(NotificationService.Observers.Count, Is.EqualTo(2));
NotificationService.Publish("a","b");
notificationMock1.Verify(x => x.Notify("a","b"), Times.Once);
notificationMock2.Verify(x => x.Notify("a","b"), Times.Once);
}
}
}

@ -55,6 +55,7 @@
<Otherwise />
</Choose>
<ItemGroup>
<Compile Include="NotificationServiceTests.cs" />
<Compile Include="PlexAvailabilityCheckerTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>

@ -26,8 +26,6 @@
#endregion
using System;
using System.Reactive.Linq;
using System.Runtime.Remoting.Messaging;
using System.Threading.Tasks;
using System.Web.Hosting;
using FluentScheduler;
@ -51,7 +49,7 @@ namespace PlexRequests.Services
public AvailabilityUpdateService()
{
ConfigurationReader = new ConfigurationReader();
var repo = new JsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider());
var repo = new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider());
Checker = new PlexAvailabilityChecker(new SettingsServiceV2<PlexSettings>(repo), new SettingsServiceV2<AuthenticationSettings>(repo), new RequestService(new GenericRepository<RequestedModel>(new DbConfiguration(new SqliteFactory()))), new PlexApi());
HostingEnvironment.RegisterObject(this);
}
@ -76,7 +74,7 @@ namespace PlexRequests.Services
public void Stop(bool immediate)
{
throw new System.NotImplementedException();
HostingEnvironment.UnregisterObject(this);
}
}

@ -30,8 +30,6 @@ namespace PlexRequests.Services.Interfaces
{
public interface IIntervals
{
TimeSpan CriticalNotification { get; } // notification interval for critical load
TimeSpan Measurement { get; } // how often to measure
TimeSpan Notification { get; } // notification interval for high load
}
}

@ -0,0 +1,108 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: EmailMessageNotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using System.Net;
using System.Net.Mail;
using NLog;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Services.Notification
{
public class EmailMessageNotification : INotification
{
public EmailMessageNotification(ISettingsService<EmailNotificationSettings> settings)
{
EmailNotificationSettings = settings;
}
private static Logger Log = LogManager.GetCurrentClassLogger();
private ISettingsService<EmailNotificationSettings> EmailNotificationSettings { get; }
public string NotificationName => "EmailMessageNotification";
public bool Notify(string title, string requester)
{
var configuration = GetConfiguration();
if (!ValidateConfiguration(configuration))
{
return false;
}
var message = new MailMessage
{
IsBodyHtml = true,
To = { new MailAddress(configuration.RecipientEmail) },
Body = $"User {requester} has requested {title}!",
From = new MailAddress(configuration.EmailUsername),
Subject = $"New Request for {title}!"
};
try
{
using (var smtp = new SmtpClient(configuration.EmailHost, configuration.EmailPort))
{
smtp.Credentials = new NetworkCredential(configuration.EmailUsername, configuration.EmailPassword);
smtp.EnableSsl = configuration.Ssl;
smtp.Send(message);
return true;
}
}
catch (SmtpException smtp)
{
Log.Fatal(smtp);
}
catch (Exception e)
{
Log.Fatal(e);
}
return false;
}
private EmailNotificationSettings GetConfiguration()
{
var settings = EmailNotificationSettings.GetSettings();
return settings;
}
private bool ValidateConfiguration(EmailNotificationSettings settings)
{
if (!settings.Enabled)
{
return false;
}
if (string.IsNullOrEmpty(settings.EmailHost) || string.IsNullOrEmpty(settings.EmailUsername)
|| string.IsNullOrEmpty(settings.EmailPassword) || string.IsNullOrEmpty(settings.RecipientEmail)
|| string.IsNullOrEmpty(settings.EmailPort.ToString()))
{
return false;
}
return true;
}
}
}

@ -0,0 +1,46 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: INotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
namespace PlexRequests.Services.Notification
{
public interface INotification
{
/// <summary>
/// Gets the name of the notification.
/// </summary>
/// <value>
/// The name of the notification.
/// </value>
string NotificationName { get; }
/// <summary>
/// Notifies the specified title.
/// </summary>
/// <param name="title">The title.</param>
/// <param name="requester">The requester.</param>
/// <returns></returns>
bool Notify(string title, string requester);
}
}

@ -0,0 +1,90 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: NotificationService.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Threading;
using NLog;
using PlexRequests.Helpers;
namespace PlexRequests.Services.Notification
{
public static class NotificationService
{
private static Logger Log = LogManager.GetCurrentClassLogger();
public static Dictionary<string, INotification> Observers { get; }
static NotificationService()
{
Observers = new Dictionary<string, INotification>();
}
public static void Publish(string title, string requester)
{
Log.Trace("Notifying all observers: ");
Log.Trace(Observers.DumpJson());
foreach (var observer in Observers)
{
var notification = observer.Value;
new Thread(() =>
{
Thread.CurrentThread.IsBackground = true;
notification.Notify(title, requester);
}).Start();
}
}
public static void Subscribe(INotification notification)
{
Log.Trace("Subscribing Observer {0}", notification.NotificationName);
INotification notificationValue;
if (Observers.TryGetValue(notification.NotificationName, out notificationValue))
{
Log.Trace("Observer {0} already exists", notification.NotificationName);
// Observer already exists
return;
}
Observers[notification.NotificationName] = notification;
}
public static void UnSubscribe(INotification notification)
{
Log.Trace("Unsubscribing Observer {0}", notification.NotificationName);
INotification notificationValue;
if (!Observers.TryGetValue(notification.NotificationName, out notificationValue))
{
Log.Trace("Observer {0} doesn't exist to Unsubscribe", notification.NotificationName);
// Observer doesn't exists
return;
}
Observers.Remove(notification.NotificationName);
}
}
}

@ -0,0 +1,80 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: PushbulletNotification.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System;
using NLog;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.Services.Notification
{
public class PushbulletNotification : INotification
{
public PushbulletNotification(IPushbulletApi pushbulletApi, ISettingsService<PushbulletNotificationSettings> settings)
{
PushbulletApi = pushbulletApi;
Settings = settings;
}
private IPushbulletApi PushbulletApi { get; }
private ISettingsService<PushbulletNotificationSettings> Settings { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public string NotificationName => "PushbulletNotification";
public bool Notify(string title, string requester)
{
var settings = GetSettings();
if (!settings.Enabled)
{
return false;
}
var message = $"{title} has been requested by {requester}";
var pushTitle = $"Plex Requests: {title}";
try
{
var result = PushbulletApi.Push(settings.AccessToken, pushTitle, message, settings.DeviceIdentifier);
if (result != null)
{
return true;
}
}
catch (Exception e)
{
Log.Fatal(e);
}
return false;
}
private PushbulletNotificationSettings GetSettings()
{
return Settings.GetSettings();
}
}
}

@ -77,6 +77,10 @@
<Compile Include="Interfaces\IAvailabilityChecker.cs" />
<Compile Include="Interfaces\IConfigurationReader.cs" />
<Compile Include="Interfaces\IIntervals.cs" />
<Compile Include="Notification\INotification.cs" />
<Compile Include="Notification\EmailMessageNotification.cs" />
<Compile Include="Notification\NotificationService.cs" />
<Compile Include="Notification\PushbulletNotification.cs" />
<Compile Include="PlexAvailabilityChecker.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UpdateInterval.cs" />

@ -32,9 +32,7 @@ namespace PlexRequests.Services
{
public class UpdateInterval : IIntervals
{
public TimeSpan Measurement => TimeSpan.FromSeconds(1);
public TimeSpan CriticalNotification { get; }
public TimeSpan Notification => TimeSpan.FromMinutes(2);
public TimeSpan Notification => TimeSpan.FromMinutes(5);
}
}

@ -0,0 +1,65 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: ISettingsRepository.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using PlexRequests.Store.Models;
namespace PlexRequests.Store
{
public interface IRequestRepository
{
/// <summary>
/// Inserts the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
long Insert(RequestBlobs entity);
/// <summary>
/// Gets all.
/// </summary>
/// <returns></returns>
IEnumerable<RequestBlobs> GetAll();
RequestBlobs Get(int id);
/// <summary>
/// Deletes the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool Delete(RequestBlobs entity);
/// <summary>
/// Updates the specified entity.
/// </summary>
/// <param name="entity">The entity.</param>
/// <returns></returns>
bool Update(RequestBlobs entity);
bool UpdateAll(IEnumerable<RequestBlobs> entity);
}
}

@ -0,0 +1,38 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: RequestBlobs.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using Dapper.Contrib.Extensions;
namespace PlexRequests.Store.Models
{
[Table("RequestBlobs")]
public class RequestBlobs : Entity
{
public int ProviderId { get; set; }
public byte[] Content { get; set; }
public RequestType Type { get; set; }
}
}

@ -58,13 +58,16 @@
<ItemGroup>
<Compile Include="DbConfiguration.cs" />
<Compile Include="Entity.cs" />
<Compile Include="IRequestRepository.cs" />
<Compile Include="ISettingsRepository.cs" />
<Compile Include="ISqliteConfiguration.cs" />
<Compile Include="IRepository.cs" />
<Compile Include="Models\GlobalSettings.cs" />
<Compile Include="Models\LogEntity.cs" />
<Compile Include="Models\RequestBlobs.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Repository\JsonRepository.cs" />
<Compile Include="Repository\SettingsJsonRepository.cs" />
<Compile Include="Repository\RequestJsonRepository.cs" />
<Compile Include="SettingsModel.cs" />
<Compile Include="GenericRepository.cs" />
<Compile Include="RequestedModel.cs" />

@ -0,0 +1,127 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SettingsJsonRepository.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using System.Collections.Generic;
using System.Linq;
using Dapper.Contrib.Extensions;
using PlexRequests.Helpers;
using PlexRequests.Store.Models;
namespace PlexRequests.Store.Repository
{
public class RequestJsonRepository : IRequestRepository
{
private ICacheProvider Cache { get; }
private string TypeName { get; }
public RequestJsonRepository(ISqliteConfiguration config, ICacheProvider cacheProvider)
{
Db = config;
Cache = cacheProvider;
TypeName = typeof(RequestJsonRepository).Name;
}
private ISqliteConfiguration Db { get; }
public long Insert(RequestBlobs entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return con.Insert(entity);
}
}
public IEnumerable<RequestBlobs> GetAll()
{
var key = TypeName + "GetAll";
var item = Cache.GetOrSet(key, () =>
{
using (var con = Db.DbConnection())
{
var page = con.GetAll<RequestBlobs>();
return page;
}
}, 5);
return item;
}
public RequestBlobs Get(int id)
{
var key = TypeName + "Get";
var item = Cache.GetOrSet(key, () =>
{
using (var con = Db.DbConnection())
{
var page = con.Get<RequestBlobs>(id);
return page;
}
}, 5);
return item;
}
public bool Delete(RequestBlobs entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return con.Delete(entity);
}
}
public bool Update(RequestBlobs entity)
{
ResetCache();
using (var con = Db.DbConnection())
{
return con.Update(entity);
}
}
private void ResetCache()
{
Cache.Remove("Get");
Cache.Remove(TypeName + "GetAll");
}
public bool UpdateAll(IEnumerable<RequestBlobs> entity)
{
var result = new HashSet<bool>();
using (var db = Db.DbConnection())
{
db.Open();
foreach (var e in entity)
{
result.Add(db.Update(e));
}
}
return result.All(x => true);
}
}
}

@ -1,7 +1,7 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: JsonRepository.cs
// File: SettingsJsonRepository.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
@ -34,16 +34,16 @@ using PlexRequests.Store.Models;
namespace PlexRequests.Store.Repository
{
public class JsonRepository : ISettingsRepository
public class SettingsJsonRepository : ISettingsRepository
{
private ICacheProvider Cache { get; set; }
private string TypeName { get; set; }
public JsonRepository(ISqliteConfiguration config, ICacheProvider cacheProvider)
public SettingsJsonRepository(ISqliteConfiguration config, ICacheProvider cacheProvider)
{
Db = config;
Cache = cacheProvider;
TypeName = typeof(JsonRepository).Name;
TypeName = typeof(SettingsJsonRepository).Name;
}
private ISqliteConfiguration Db { get; set; }

@ -16,6 +16,7 @@ CREATE TABLE IF NOT EXISTS Settings
PlexAuthToken varchar(50)
);
CREATE TABLE IF NOT EXISTS Requested
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -45,6 +46,14 @@ CREATE TABLE IF NOT EXISTS GlobalSettings
Content varchar(100) NOT NULL
);
CREATE TABLE IF NOT EXISTS RequestBlobs
(
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ProviderId INTEGER NOT NULL,
Type INTEGER NOT NULL,
Content BLOB NOT NULL
);
CREATE TABLE IF NOT EXISTS Log
(

@ -53,8 +53,10 @@ namespace PlexRequests.UI.Tests
private Mock<ISettingsService<PlexSettings>> PlexSettingsMock { get; set; }
private Mock<ISettingsService<SonarrSettings>> SonarrSettingsMock { get; set; }
private Mock<ISettingsService<EmailNotificationSettings>> EmailMock { get; set; }
private Mock<ISettingsService<PushbulletNotificationSettings>> PushbulletSettings { get; set; }
private Mock<IPlexApi> PlexMock { get; set; }
private Mock<ISonarrApi> SonarrApiMock { get; set; }
private Mock<IPushbulletApi> PushbulletApi { get; set; }
private ConfigurableBootstrapper Bootstrapper { get; set; }
@ -75,6 +77,8 @@ namespace PlexRequests.UI.Tests
SonarrApiMock = new Mock<ISonarrApi>();
SonarrSettingsMock = new Mock<ISettingsService<SonarrSettings>>();
EmailMock = new Mock<ISettingsService<EmailNotificationSettings>>();
PushbulletApi = new Mock<IPushbulletApi>();
PushbulletSettings = new Mock<ISettingsService<PushbulletNotificationSettings>>();
Bootstrapper = new ConfigurableBootstrapper(with =>
{
@ -87,6 +91,8 @@ namespace PlexRequests.UI.Tests
with.Dependency(SonarrSettingsMock.Object);
with.Dependency(PlexMock.Object);
with.Dependency(EmailMock.Object);
with.Dependency(PushbulletApi.Object);
with.Dependency(PushbulletSettings.Object);
with.RootPathProvider<TestRootPathProvider>();
with.RequestStartup((container, pipelines, context) =>
{

@ -25,6 +25,7 @@
// ************************************************************************/
#endregion
using System.Net;
using FluentScheduler;
using Mono.Data.Sqlite;
@ -44,6 +45,7 @@ using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Services;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.Store.Repository;
using PlexRequests.UI.Jobs;
@ -63,7 +65,7 @@ namespace PlexRequests.UI
container.Register<ISqliteConfiguration, DbConfiguration>(new DbConfiguration(new SqliteFactory()));
container.Register<ISettingsRepository, JsonRepository>();
container.Register<ISettingsRepository, SettingsJsonRepository>();
container.Register<ICacheProvider, MemoryCacheProvider>();
container.Register<ISettingsService<PlexRequestSettings>, SettingsServiceV2<PlexRequestSettings>>();
@ -72,6 +74,7 @@ namespace PlexRequests.UI
container.Register<ISettingsService<PlexSettings>, SettingsServiceV2<PlexSettings>>();
container.Register<ISettingsService<SonarrSettings>, SettingsServiceV2<SonarrSettings>>();
container.Register<ISettingsService<EmailNotificationSettings>, SettingsServiceV2<EmailNotificationSettings>>();
container.Register<ISettingsService<PushbulletNotificationSettings>, SettingsServiceV2<PushbulletNotificationSettings>>();
container.Register<IRepository<RequestedModel>, GenericRepository<RequestedModel>>();
container.Register<IRequestService, RequestService>();
@ -80,25 +83,27 @@ namespace PlexRequests.UI
container.Register<IIntervals, UpdateInterval>();
container.Register<ICouchPotatoApi, CouchPotatoApi>();
container.Register<IPushbulletApi, PushbulletApi>();
container.Register<ISonarrApi, SonarrApi>();
//container.Register<ISonarrApi, MockSonarrApi>();
container.Register<IPlexApi, PlexApi>();
SubscribeAllObservers(container);
base.ConfigureRequestContainer(container, context);
}
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
TaskManager.TaskFactory = new Jobs.PlexTaskFactory();
TaskManager.TaskFactory = new PlexTaskFactory();
TaskManager.Initialize(new PlexRegistry());
CookieBasedSessions.Enable(pipelines, CryptographyConfiguration.Default);
StaticConfiguration.DisableErrorTraces = false;
base.ApplicationStartup(container, pipelines);
// Enable forms auth
@ -109,8 +114,30 @@ namespace PlexRequests.UI
};
FormsAuthentication.Enable(pipelines, formsAuthConfiguration);
ServicePointManager.ServerCertificateValidationCallback +=
(sender, certificate, chain, sslPolicyErrors) => true;
}
protected override DiagnosticsConfiguration DiagnosticsConfiguration => new DiagnosticsConfiguration { Password = @"password" };
private void SubscribeAllObservers(TinyIoCContainer container)
{
var emailSettingsService = container.Resolve<ISettingsService<EmailNotificationSettings>>();
var emailSettings = emailSettingsService.GetSettings();
if (emailSettings.Enabled)
{
NotificationService.Subscribe(new EmailMessageNotification(emailSettingsService));
}
var pushbulletService = container.Resolve<ISettingsService<PushbulletNotificationSettings>>();
var pushbulletSettings = pushbulletService.GetSettings();
if (pushbulletSettings.Enabled)
{
NotificationService.Subscribe(new PushbulletNotification(container.Resolve<IPushbulletApi>(), container.Resolve<ISettingsService<PushbulletNotificationSettings>>()));
}
}
}
}

@ -36,10 +36,12 @@ using Nancy.Validation;
using NLog;
using PlexRequests.Api;
using PlexRequests.Api.Interfaces;
using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Services.Notification;
using PlexRequests.UI.Helpers;
using PlexRequests.UI.Models;
@ -53,8 +55,10 @@ namespace PlexRequests.UI.Modules
private ISettingsService<PlexSettings> PlexService { get; }
private ISettingsService<SonarrSettings> SonarrService { get; }
private ISettingsService<EmailNotificationSettings> EmailService { get; }
private ISettingsService<PushbulletNotificationSettings> PushbulletService { get; }
private IPlexApi PlexApi { get; }
private ISonarrApi SonarrApi { get; }
private PushbulletApi PushbulletApi { get; }
private static Logger Log = LogManager.GetCurrentClassLogger();
public AdminModule(ISettingsService<PlexRequestSettings> rpService,
@ -64,7 +68,9 @@ namespace PlexRequests.UI.Modules
ISettingsService<SonarrSettings> sonarr,
ISonarrApi sonarrApi,
ISettingsService<EmailNotificationSettings> email,
IPlexApi plexApi) : base("admin")
IPlexApi plexApi,
ISettingsService<PushbulletNotificationSettings> pbSettings,
PushbulletApi pbApi) : base("admin")
{
RpService = rpService;
CpService = cpService;
@ -74,6 +80,8 @@ namespace PlexRequests.UI.Modules
SonarrApi = sonarrApi;
EmailService = email;
PlexApi = plexApi;
PushbulletService = pbSettings;
PushbulletApi = pbApi;
#if !DEBUG
this.RequiresAuthentication();
@ -103,6 +111,9 @@ namespace PlexRequests.UI.Modules
Get["/emailnotification"] = _ => EmailNotifications();
Post["/emailnotification"] = _ => SaveEmailNotifications();
Get["/status"] = _ => Status();
Get["/pushbulletnotification"] = _ => PushbulletNotifications();
Post["/pushbulletnotification"] = _ => SavePushbulletNotifications();
}
private Negotiator Authentication()
@ -294,11 +305,21 @@ namespace PlexRequests.UI.Modules
private Response SaveEmailNotifications()
{
var settings = this.Bind<EmailNotificationSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
Log.Trace(settings.DumpJson());
var result = EmailService.SaveSettings(settings);
NotificationService.Subscribe(new EmailMessageNotification(EmailService));
Log.Info("Saved email settings, result: {0}", result);
return Context.GetRedirect("~/admin/emailnotification");
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Email Notifications!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
private Negotiator Status()
@ -307,5 +328,31 @@ namespace PlexRequests.UI.Modules
var status = checker.GetStatus();
return View["Status", status];
}
private Negotiator PushbulletNotifications()
{
var settings = PushbulletService.GetSettings();
return View["PushbulletNotifications", settings];
}
private Response SavePushbulletNotifications()
{
var settings = this.Bind<PushbulletNotificationSettings>();
var valid = this.Validate(settings);
if (!valid.IsValid)
{
return Response.AsJson(valid.SendJsonError());
}
Log.Trace(settings.DumpJson());
var result = PushbulletService.SaveSettings(settings);
NotificationService.Subscribe(new PushbulletNotification(PushbulletApi, PushbulletService));
Log.Info("Saved email settings, result: {0}", result);
return Response.AsJson(result
? new JsonResponseModel { Result = true, Message = "Successfully Updated the Settings for Pushbullet Notifications!" }
: new JsonResponseModel { Result = false, Message = "Could not update the settings, take a look at the logs." });
}
}
}

@ -37,6 +37,7 @@ using PlexRequests.Core;
using PlexRequests.Core.SettingModels;
using PlexRequests.Helpers;
using PlexRequests.Services.Interfaces;
using PlexRequests.Services.Notification;
using PlexRequests.Store;
using PlexRequests.UI.Models;
@ -235,7 +236,8 @@ namespace PlexRequests.UI.Modules
{
Log.Debug("Adding movie to database requests");
var id = RequestService.AddRequest(movieId, model);
//BackgroundJob.Enqueue(() => Checker.CheckAndUpdate(model.Title, (int)id));
NotificationService.Publish(model.Title, model.RequestedBy);
return Response.AsJson(new JsonResponseModel { Result = true });
}
@ -291,7 +293,6 @@ namespace PlexRequests.UI.Modules
LatestTv = latest
};
RequestService.AddRequest(showId, model);
var settings = PrService.GetSettings();
if (!settings.RequireApproval)
@ -302,11 +303,20 @@ namespace PlexRequests.UI.Modules
var result = SonarrApi.AddSeries(model.ProviderId, model.Title, qualityProfile,
sonarrSettings.SeasonFolders, sonarrSettings.RootPath, model.LatestTv, sonarrSettings.ApiKey,
sonarrSettings.FullUri);
Log.Info("Added series {0} to Sonarr, Result: {1}", model.Title, result);
Log.Trace("Model sent to Sonarr: ");
Log.Trace(model.DumpJson());
if (result != null)
{
model.Approved = true;
Log.Debug("Adding tv to database requests (No approval required)");
RequestService.AddRequest(showId, model);
return Response.AsJson(new JsonResponseModel { Result = true });
}
return Response.AsJson(new JsonResponseModel { Result = false, Message = "Something went wrong adding the movie to CouchPotato! Please check your settings." });
}
RequestService.AddRequest(showId, model);
NotificationService.Publish(model.Title, model.RequestedBy);
return Response.AsJson(new { Result = true });
}
private string GetTvDbAuthToken(TheTvDbApi api)

@ -161,6 +161,8 @@
<ItemGroup>
<Compile Include="Bootstrapper.cs" />
<Compile Include="Helpers\ValidationHelper.cs" />
<Compile Include="Validators\PushbulletSettingsValidator.cs" />
<Compile Include="Validators\EmailNotificationSettingsValidator.cs" />
<Compile Include="Validators\CouchPotatoValidator.cs" />
<Compile Include="Validators\PlexValidator.cs" />
<Compile Include="Validators\SonarrValidator.cs" />
@ -315,6 +317,9 @@
<Content Include="Views\Admin\Status.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Views\Admin\PushbulletNotifications.cshtml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Include="Web.Debug.config">
<DependentUpon>web.config</DependentUpon>
</None>

@ -105,7 +105,7 @@ namespace PlexRequests.UI
{
Log.Trace("Getting startup URI");
var uri = "http://*:3579/";
var service = new SettingsServiceV2<PlexRequestSettings>(new JsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var service = new SettingsServiceV2<PlexRequestSettings>(new SettingsJsonRepository(new DbConfiguration(new SqliteFactory()), new MemoryCacheProvider()));
var settings = service.GetSettings();
Log.Trace("Port: {0}", settings.Port);
if (settings.Port != 0)

@ -0,0 +1,46 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SonarrValidator.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using FluentValidation;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.UI.Validators
{
public class EmailNotificationSettingsValidator : AbstractValidator<EmailNotificationSettings>
{
public EmailNotificationSettingsValidator()
{
RuleFor(request => request.EmailHost).NotEmpty().WithMessage("You must specify a Host name.");
RuleFor(request => request.EmailPort).NotEmpty().WithMessage("You must specify a Port.");
RuleFor(request => request.RecipientEmail).NotEmpty().WithMessage("You must specify a Recipient.");
RuleFor(request => request.RecipientEmail).EmailAddress().WithMessage("You must specify a valid Recipient.");
RuleFor(request => request.EmailUsername).EmailAddress().WithMessage("You must specify a valid Username.");
RuleFor(request => request.EmailUsername).NotEmpty().WithMessage("You must specify a Username.");
RuleFor(request => request.EmailPassword).NotEmpty().WithMessage("You must specify a valid password.");
}
}
}

@ -0,0 +1,40 @@
#region Copyright
// /************************************************************************
// Copyright (c) 2016 Jamie Rees
// File: SonarrValidator.cs
// Created By: Jamie Rees
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ************************************************************************/
#endregion
using FluentValidation;
using PlexRequests.Core.SettingModels;
namespace PlexRequests.UI.Validators
{
public class PushbulletSettingsValidator : AbstractValidator<PushbulletNotificationSettings>
{
public PushbulletSettingsValidator()
{
RuleFor(request => request.AccessToken).NotEmpty().WithMessage("You must specify a Access Token.");
}
}
}

@ -29,6 +29,20 @@
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Ssl)
{
<input type="checkbox" id="Ssl" name="Ssl" checked="checked"><text>SSL Enabled</text>
}
else
{
<input type="checkbox" id="Ssl" name="Ssl"><text>SSL Enabled</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="EmailHost" class="control-label">SMTP Hostname or IP</label>
<div class="">
@ -52,21 +66,6 @@
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.EmailAuthentication)
{
<input type="checkbox" id="EmailAuthentication" name="EmailAuthentication" checked="checked"><text>Authenticate</text>
}
else
{
<input type="checkbox" id="EmailAuthentication" name="EmailAuthentication"><text>Authenticate</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="EmailUsername" class="control-label">Username</label>
<div>
@ -84,10 +83,45 @@
<div class="form-group">
<div>
<button type="submit" class="btn btn-primary-outline">Submit</button>
<button id="save" type="submit" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
<script>
$(function () {
$('#save').click(function (e) {
e.preventDefault();
var port = $('#EmailPort').val();
if (isNaN(port)) {
generateNotify("You must specify a valid Port.", "warning");
return;
}
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -29,6 +29,20 @@
<input type="text" class="form-control form-control-custom " id="portNumber" name="Port" placeholder="Port Number" value="@port">
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Ssl)
{
<input type="checkbox" id="Ssl" name="Ssl" checked="checked"><text>SSL</text>
}
else
{
<input type="checkbox" id="Ssl" name="Ssl"><text>SSL</text>
}
</label>
</div>
</div>
<div class="form-group">
<div>
<button id="testPlex" type="submit" class="btn btn-primary-outline">Test Connectivity</button>
@ -46,7 +60,7 @@
<script>
$(function() {
$(function () {
$('#testPlex').click(function (e) {
e.preventDefault();

@ -0,0 +1,74 @@
@Html.Partial("_Sidebar")
<div class="col-sm-8 col-sm-push-1">
<form class="form-horizontal" method="POST" id="mainForm">
<fieldset>
<legend>Pushbullet Notifications</legend>
<div class="form-group">
<div class="checkbox">
<label>
@if (Model.Enabled)
{
<input type="checkbox" id="Enabled" name="Enabled" checked="checked"><text>Enabled</text>
}
else
{
<input type="checkbox" id="Enabled" name="Enabled"><text>Enabled</text>
}
</label>
</div>
</div>
<div class="form-group">
<label for="AccessToken" class="control-label">Access Token</label>
<small class="control-label">You can get this by navigating to <a href="https://www.pushbullet.com/#settings">Pushbullet</a></small>
<div class="">
<input type="text" class="form-control form-control-custom " id="AccessToken" name="AccessToken" value="@Model.AccessToken">
</div>
</div>
<div class="form-group">
<label for="DeviceIdentifier" class="control-label">Device Identifier</label>
<small class="control-label">This is optional, if left blank we will send a Push notification to all devices.</small>
<div class="">
<input type="text" class="form-control form-control-custom " id="DeviceIdentifier" name="DeviceIdentifier" value="@Model.DeviceIdentifier">
</div>
</div>
<div class="form-group">
<div>
<button id="save" type="submit" class="btn btn-primary-outline">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
<script>
$(function () {
$('#save').click(function (e) {
e.preventDefault();
var $form = $("#mainForm");
$.ajax({
type: $form.prop("method"),
data: $form.serialize(),
url: $form.prop("action"),
dataType: "json",
success: function (response) {
if (response.result === true) {
generateNotify(response.message, "success");
} else {
generateNotify(response.message, "warning");
}
},
error: function (e) {
console.log(e);
generateNotify("Something went wrong!", "danger");
}
});
});
});
</script>

@ -46,14 +46,23 @@
}
@*<a class="list-group-item" href="/admin/sickbeard">Sickbeard Settings</a>*@
@*@if (Context.Request.Path == "/admin/emailnotification")
@if (Context.Request.Path == "/admin/emailnotification")
{
<a class="list-group-item active" href="/admin/emailnotification">Email Notifications</a>
}
else
{
<a class="list-group-item" href="/admin/emailnotification">Email Notifications</a>
}*@
}
@if (Context.Request.Path == "/admin/pushbulletnotification")
{
<a class="list-group-item active" href="/admin/pushbulletnotification">Pushbullet Notifications</a>
}
else
{
<a class="list-group-item" href="/admin/pushbulletnotification">Pushbullet Notifications</a>
}
@if (Context.Request.Path == "/admin/status")
{

@ -3,6 +3,9 @@
[![Gitter](https://badges.gitter.im/tidusjar/PlexRequest.NET.svg)](https://gitter.im/tidusjar/PlexRequests.Net?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Build status](https://ci.appveyor.com/api/projects/status/hgj8j6lcea7j0yhn?svg=true)](https://ci.appveyor.com/project/tidusjar/requestplex)
[![Linux Status](https://travis-ci.org/tidusjar/PlexRequests.Net.svg)](https://travis-ci.org/tidusjar/PlexRequests.Net)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/tidusjar/plexrequests.net.svg)](http://isitmaintained.com/project/tidusjar/plexrequests.net "Percentage of issues still open")
[![Github All Releases](https://img.shields.io/github/downloads/tidusjar/PlexRequests.net/total.svg)](https://github.com/tidusjar/PlexRequests.Net)
This is based off [Plex Requests by lokenx](https://github.com/lokenx/plexrequests-meteor) so big props to that guy!
I wanted to write a similar application in .Net!

@ -1,11 +1,11 @@
version: 1.2.{build}
version: 1.3.{build}
configuration: Release
assembly_info:
patch: true
file: '**\AssemblyInfo.*'
assembly_version: '1.2.1'
assembly_version: '1.3.0'
assembly_file_version: '{version}'
assembly_informational_version: '1.2.1'
assembly_informational_version: '1.3.0'
before_build:
- cmd: appveyor-retry nuget restore
build:

Loading…
Cancel
Save