diff --git a/src/Configuration/PluginConfiguration.cs b/src/Configuration/PluginConfiguration.cs
new file mode 100644
index 0000000..f7fe0cc
--- /dev/null
+++ b/src/Configuration/PluginConfiguration.cs
@@ -0,0 +1,48 @@
+using System;
+using MediaBrowser.Model.Plugins;
+
+namespace MediaBrowser.Plugins.VuPlus.Configuration
+{
+ ///
+ /// Class PluginConfiguration
+ ///
+ public class PluginConfiguration : BasePluginConfiguration
+ {
+ public string HostName { get; set; }
+ public string StreamingPort { get; set; }
+ public string WebInterfacePort { get; set; }
+ public string WebInterfaceUsername { get; set; }
+ public string WebInterfacePassword { get; set; }
+ public Boolean UseSecureHTTPS { get; set; }
+ public Boolean OnlyOneBouquet { get; set; }
+ public string TVBouquet { get; set; }
+ public Boolean ZapToChannel { get; set; }
+ public Boolean FetchPiconsFromWebInterface { get; set; }
+ public string PiconsPath { get; set; }
+
+ public string RecordingPath { get; set; }
+
+ public Boolean EnableDebugLogging { get; set; }
+
+
+ public PluginConfiguration()
+ {
+ HostName = "http://localhost";
+ StreamingPort = "8001";
+ WebInterfacePort = "8000";
+ WebInterfaceUsername= "";
+ WebInterfacePassword = "";
+ UseSecureHTTPS = false;
+ OnlyOneBouquet = true;
+ TVBouquet = "Favourites (TV)";
+ ZapToChannel = false;
+ FetchPiconsFromWebInterface = true;
+ PiconsPath = "";
+
+ RecordingPath = "";
+
+ EnableDebugLogging = false;
+
+ }
+ }
+}
diff --git a/src/Configuration/VuPlusConfigurationPage.cs b/src/Configuration/VuPlusConfigurationPage.cs
new file mode 100644
index 0000000..ad307db
--- /dev/null
+++ b/src/Configuration/VuPlusConfigurationPage.cs
@@ -0,0 +1,44 @@
+using System.IO;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller.Plugins;
+
+namespace MediaBrowser.Plugins.VuPlus.Configuration
+{
+ ///
+ /// Class VuPlusConfigurationPage
+ ///
+ class VuPlusConfigurationPage : IPluginConfigurationPage
+ {
+ ///
+ /// Gets the type of the configuration page.
+ ///
+ /// The type of the configuration page.
+ public ConfigurationPageType ConfigurationPageType
+ {
+ get { return ConfigurationPageType.PluginConfiguration; }
+ }
+
+ ///
+ /// Gets the HTML stream.
+ ///
+ /// Stream.
+ public Stream GetHtmlStream()
+ {
+ return GetType().Assembly.GetManifestResourceStream("MediaBrowser.Plugins.VuPlus.Configuration.configPage.html");
+ }
+
+ ///
+ /// Gets the name.
+ ///
+ /// The name.
+ public string Name
+ {
+ get { return "VuPlus"; }
+ }
+
+ public IPlugin Plugin
+ {
+ get { return VuPlus.Plugin.Instance; }
+ }
+ }
+}
diff --git a/src/Configuration/configPage.html b/src/Configuration/configPage.html
new file mode 100644
index 0000000..9ed4517
--- /dev/null
+++ b/src/Configuration/configPage.html
@@ -0,0 +1,209 @@
+
+
+
+ Vu+
+
+
+
+
+
diff --git a/src/Helpers/EncryptionHelper.cs b/src/Helpers/EncryptionHelper.cs
new file mode 100644
index 0000000..ecca2cd
--- /dev/null
+++ b/src/Helpers/EncryptionHelper.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace MediaBrowser.Plugins.VuPlus.Helpers
+{
+ public static class EncryptionHelper
+ {
+ public static string GetMd5Hash(string value)
+ {
+ HashAlgorithm hashAlgorithm = HashAlgorithm.Create("MD5");
+
+ if (hashAlgorithm != null)
+ {
+ byte[] hashValue = hashAlgorithm.ComputeHash(new UTF8Encoding().GetBytes(value));
+ //Bit convertor return the byte to string as all caps hex values seperated by "-"
+ return BitConverter.ToString(hashValue).Replace("-", "").ToLowerInvariant();
+ }
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/src/Helpers/GeneralHelpers.cs b/src/Helpers/GeneralHelpers.cs
new file mode 100644
index 0000000..8d229a8
--- /dev/null
+++ b/src/Helpers/GeneralHelpers.cs
@@ -0,0 +1,71 @@
+using MediaBrowser.Model.LiveTv;
+using System;
+using MediaBrowser.Model.Logging;
+
+namespace MediaBrowser.Plugins.VuPlus.Helpers
+{
+ public static class ChannelHelper
+ {
+ public static ChannelType GetChannelType(string channelType)
+ {
+ ChannelType type = new ChannelType();
+
+ if (channelType == "0x1")
+ {
+ type = ChannelType.TV;
+ }
+ else if (channelType == "0xa")
+ {
+ type = ChannelType.Radio;
+ }
+
+ return type;
+ }
+ }
+
+ public static class UtilsHelper
+ {
+ public static void DebugInformation(ILogger logger, string message)
+ {
+ var config = Plugin.Instance.Configuration;
+ bool enableDebugLogging = config.EnableDebugLogging;
+
+ if (enableDebugLogging)
+ {
+ logger.Debug(message);
+ }
+ }
+
+ }
+
+ public static class RecordingHelper
+ {
+
+ }
+
+ public static class ApiHelper
+ {
+ private static readonly DateTime UnixEpoch =
+ new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ public static long GetCurrentUnixTimestampMillis()
+ {
+ return (long)(DateTime.UtcNow - UnixEpoch).TotalMilliseconds;
+ }
+
+ public static DateTime DateTimeFromUnixTimestampMillis(long millis)
+ {
+ return UnixEpoch.AddMilliseconds(millis);
+ }
+
+ public static long GetCurrentUnixTimestampSeconds(DateTime date)
+ {
+ return (long)(date - UnixEpoch).TotalSeconds;
+ }
+
+ public static DateTime DateTimeFromUnixTimestampSeconds(long seconds)
+ {
+ return UnixEpoch.AddSeconds(seconds);
+ }
+ }
+}
diff --git a/src/Helpers/XmlHelper.cs b/src/Helpers/XmlHelper.cs
new file mode 100644
index 0000000..baab916
--- /dev/null
+++ b/src/Helpers/XmlHelper.cs
@@ -0,0 +1,23 @@
+using System.Xml;
+
+namespace MediaBrowser.Plugins.VuPlus.Helpers
+{
+ public static class XmlHelper
+ {
+ public static XmlNode GetSingleNode(string input, string xPath)
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(input);
+
+ return xml.SelectSingleNode(xPath);
+ }
+
+ public static XmlNodeList GetMultipleNodes(string input, string xPath)
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(input);
+
+ return xml.SelectNodes(xPath);
+ }
+ }
+}
diff --git a/src/Images/Vu+.png b/src/Images/Vu+.png
new file mode 100644
index 0000000..2774065
Binary files /dev/null and b/src/Images/Vu+.png differ
diff --git a/src/LiveTvService.cs b/src/LiveTvService.cs
new file mode 100644
index 0000000..fc65cd4
--- /dev/null
+++ b/src/LiveTvService.cs
@@ -0,0 +1,1735 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Channels;
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
+using MediaBrowser.Model.Serialization;
+
+using MediaBrowser.Plugins.VuPlus.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using System.Xml;
+using MediaBrowser.Model.LiveTv;
+using System.Net;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.ApiClient;
+
+namespace MediaBrowser.Plugins.VuPlus
+{
+ ///
+ /// Class LiveTvService
+ ///
+ public class LiveTvService : ILiveTvService
+ {
+ private readonly IHttpClient _httpClient;
+ private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private readonly ILogger _logger;
+ private int _liveStreams;
+ private readonly Dictionary _heartBeat = new Dictionary();
+
+ private String tvBouquetSRef;
+ List tvChannelInfos = new List();
+
+
+ public LiveTvService(IHttpClient httpClient, IJsonSerializer jsonSerializer,ILogger logger)
+ {
+ _httpClient = httpClient;
+ _logger = logger;
+ }
+
+
+ ///
+ /// Ensure that we are connected to the VuPlus server
+ ///
+ ///
+ ///
+ private async Task EnsureConnectionAsync(CancellationToken cancellationToken)
+ {
+ _logger.Info("[VuPlus] Start EnsureConnectionAsync");
+
+ var config = Plugin.Instance.Configuration;
+
+ // log settings
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync HostName: {0}", config.HostName));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync StreamingPort: {0}", config.StreamingPort));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync WebInterfacePort: {0}", config.WebInterfacePort));
+ if (string.IsNullOrEmpty(config.WebInterfaceUsername))
+ _logger.Info("[VuPlus] EnsureConnectionAsync WebInterfaceUsername: ");
+ else
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync WebInterfaceUsername: {0}", "********"));
+ if (string.IsNullOrEmpty(config.WebInterfacePassword))
+ _logger.Info("[VuPlus] EnsureConnectionAsync WebInterfacePassword: ");
+ else
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync WebInterfaceUsername: {0}", "********"));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync UseSecureHTTPS: {0}", config.UseSecureHTTPS));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync OnlyOneBouquet: {0}", config.OnlyOneBouquet));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync TVBouquet: {0}", config.TVBouquet));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync ZapToChannel: {0}", config.ZapToChannel));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync FetchPiconsFromWebInterface: {0}", config.FetchPiconsFromWebInterface));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync PiconsPath: {0}", config.PiconsPath));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync RecordingPath: {0}", config.RecordingPath));
+ _logger.Info(string.Format("[VuPlus] EnsureConnectionAsync EnableDebugLogging: {0}", config.EnableDebugLogging));
+
+ // validate settings
+ if (string.IsNullOrEmpty(config.HostName))
+ {
+ _logger.Error("[VuPlus] HostName must be configured.");
+ throw new InvalidOperationException("VuPlus HostName must be configured.");
+ }
+
+ if (string.IsNullOrEmpty(config.StreamingPort))
+ {
+ _logger.Error("[VuPlus] Streaming Port must be configured.");
+ throw new InvalidOperationException("VuPlus Streaming Port must be configured.");
+ }
+
+ if (string.IsNullOrEmpty(config.WebInterfacePort))
+ {
+ _logger.Error("[VuPlus] Web Interface Port must be configured.");
+ throw new InvalidOperationException("VuPlus Web Interface Port must be configured.");
+ }
+
+ if (config.OnlyOneBouquet)
+ {
+ if (string.IsNullOrEmpty(config.TVBouquet))
+ {
+ _logger.Error("[VuPlus] TV Bouquet must be configured if Fetch only one TV bouquet selected.");
+ throw new InvalidOperationException("VuPlus TVBouquet must be configured if Fetch only one TV bouquet selected.");
+ }
+ }
+
+ if (!config.FetchPiconsFromWebInterface)
+ {
+ if (string.IsNullOrEmpty(config.PiconsPath))
+ {
+ _logger.Error("[VuPlus] Picons location must be configured if Fetch Picons from Web Service is disabled.");
+ throw new InvalidOperationException("VuPlus Picons location must be configured if Fetch Picons from Web Service is disabled.");
+ }
+ }
+
+ _logger.Info("[VuPlus] EnsureConnectionAsync Validation of config parameters completed");
+
+ if (config.OnlyOneBouquet)
+ {
+ // connect to VuPlus box to test connectivity and at same time get sRef for TV Bouquet.
+ tvBouquetSRef = await InitiateSession(cancellationToken, config.TVBouquet).ConfigureAwait(false);
+ } else
+ {
+ // connect to VuPlus box to test connectivity.
+ String resultNotRequired = await InitiateSession(cancellationToken, null).ConfigureAwait(false);
+ tvBouquetSRef = null;
+ }
+ }
+
+
+ ///
+ /// Checks connection to VuPlus and retrieves service reference for channel if only one bouquet.
+ ///
+ /// The cancellation token.
+ /// The TV Bouquet.
+ /// Task{String>}.
+ public async Task InitiateSession(CancellationToken cancellationToken, String tvBouquet)
+ {
+ _logger.Info("[VuPlus] Start InitiateSession, validates connection and returns Bouquet reference if required");
+ //await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/getservices", baseUrl);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] InitiateSession url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url,
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] InitiateSession response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ String tvBouquetReference = null;
+
+ XmlNodeList e2services = xml.GetElementsByTagName("e2service");
+
+ // If TV Bouquet passed find associated service reference
+ if (!string.IsNullOrEmpty(tvBouquet))
+ {
+ foreach (XmlNode xmlNode in e2services)
+ {
+ var channelInfo = new ChannelInfo();
+
+ var e2servicereference = "?";
+ var e2servicename = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2servicereference")
+ {
+ e2servicereference = node.InnerText;
+ }
+ else if (node.Name == "e2servicename")
+ {
+ e2servicename = node.InnerText;
+ }
+ }
+ if (tvBouquet == e2servicename)
+ {
+ tvBouquetReference = e2servicereference;
+ return tvBouquetReference;
+ }
+ }
+ // make sure we have found the TV Bouquet
+ if (!string.IsNullOrEmpty(tvBouquet))
+ {
+ _logger.Error("[VuPlus] Failed to find TV Bouquet specified in VuPlus configuration.");
+ throw new ApplicationException("Failed to find TV Bouquet specified in VuPlus configuration.");
+ }
+ }
+ return tvBouquetReference;
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse services information.");
+ _logger.Error(string.Format("[VuPlus] InitiateSession error: {0}", e.Message));
+ throw new ApplicationException("Failed to connect to VuPlus.");
+ }
+
+ }
+ }
+ }
+
+
+ ///
+ /// Gets the channels async.
+ ///
+ /// The cancellation token.
+ /// Task{IEnumerable{ChannelInfo}}.
+ public async Task> GetChannelsAsync(CancellationToken cancellationToken)
+ {
+ _logger.Info("[VuPlus] Start GetChannelsAsync, retrieve all channels");
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var baseUrlPicon = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = "";
+ if (string.IsNullOrEmpty(tvBouquetSRef))
+ {
+ url = string.Format("{0}/web/getservices", baseUrl);
+ }
+ else
+ {
+ url = string.Format("{0}/web/getservices?sRef={1}", baseUrl, tvBouquetSRef);
+ }
+
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetChannelsAsync url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url,
+ UserAgent = "vuplus-pvraddon-agent/1.0"
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+
+ baseUrlPicon = protocol + "://" + Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword + "@" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetChannelsAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ List channelInfos = new List();
+
+ if (string.IsNullOrEmpty(tvBouquetSRef))
+ {
+ // Load channels from all TV Bouquets
+ _logger.Info("[VuPlus] GetChannelsAsync for all TV Bouquets");
+
+ XmlNodeList e2services = xml.GetElementsByTagName("e2service");
+ foreach (XmlNode xmlNode in e2services)
+ {
+ var channelInfo = new ChannelInfo();
+ var e2servicereference = "?";
+ var e2servicename = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2servicereference")
+ {
+ e2servicereference = node.InnerText;
+ }
+ else if (node.Name == "e2servicename")
+ {
+ e2servicename = node.InnerText;
+ }
+ }
+
+ // get all channels for TV Bouquet
+ List channelInfosForBouquet = await GetChannelsForTVBouquetAsync(cancellationToken, e2servicereference).ConfigureAwait(false);
+
+ // store all channels for TV Bouquet
+ channelInfos.AddRange(channelInfosForBouquet);
+ }
+
+ return channelInfos;
+ }
+ else
+ {
+ // Load channels for specified TV Bouquet only
+ _logger.Info("[VuPlus] GetChannelsAsync for specified TV Bouquets");
+ int count = 1;
+
+ XmlNodeList e2services = xml.GetElementsByTagName("e2service");
+ foreach (XmlNode xmlNode in e2services)
+ {
+ var channelInfo = new ChannelInfo();
+
+ var e2servicereference = "?";
+ var e2servicename = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2servicereference")
+ {
+ e2servicereference = node.InnerText;
+ }
+ else if (node.Name == "e2servicename")
+ {
+ e2servicename = node.InnerText;
+ }
+ }
+
+ // Check whether the current element is not just a label
+ if (!e2servicereference.StartsWith("1:64:"))
+ {
+ //check for radio channel
+ if (e2servicereference.ToUpper().Contains("RADIO"))
+ channelInfo.ChannelType = ChannelType.Radio;
+ else
+ channelInfo.ChannelType = ChannelType.TV;
+
+ channelInfo.HasImage = true;
+ channelInfo.Id = e2servicereference;
+
+ // image name is name is e2servicereference with last char removed, then replace all : with _, then add .png
+ var imageName = e2servicereference.Remove(e2servicereference.Length - 1);
+ imageName = imageName.Replace(":", "_");
+ imageName = imageName + ".png";
+ //var imageUrl = string.Format("{0}/picon/{1}", baseUrl, imageName);
+ var imageUrl = string.Format("{0}/picon/{1}", baseUrlPicon, imageName);
+
+ if (Plugin.Instance.Configuration.FetchPiconsFromWebInterface)
+ {
+ channelInfo.ImagePath = null;
+ //channelInfo.ImageUrl = WebUtility.UrlEncode(imageUrl);
+ channelInfo.ImageUrl = imageUrl;
+ }
+ else
+ {
+ channelInfo.ImagePath = Plugin.Instance.Configuration.PiconsPath + imageName;
+ channelInfo.ImageUrl = null;
+ }
+
+ channelInfo.Name = e2servicename;
+ channelInfo.Number = count.ToString();
+
+ channelInfos.Add(channelInfo);
+ count = count + 1;
+ }
+ else
+ {
+ _logger.Info("[VuPlus] ignoring channel label " + e2servicereference);
+ }
+ }
+ }
+ tvChannelInfos = channelInfos;
+ return channelInfos;
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse channel information.");
+ _logger.Error(string.Format("[VuPlus] GetChannelsAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse channel information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Gets the channels async.
+ ///
+ /// The cancellation token.
+ /// Task{List}.
+ public async Task> GetChannelsForTVBouquetAsync(CancellationToken cancellationToken, String sRef)
+ {
+ _logger.Info("[VuPlus] Start GetChannelsForTVBouquetAsync, retrieve all channels for TV Bouquet " + sRef);
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var baseUrlPicon = protocol + "://" + Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword + "@" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/getservices?sRef={1}", baseUrl, sRef);
+
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetChannelsForTVBouquetAsync url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url,
+ UserAgent = "vuplus-pvraddon-agent/1.0"
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+
+ baseUrlPicon = protocol + "://" + Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword + "@" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetChannelsForTVBouquetAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ List channelInfos = new List();
+
+ // Load channels for specified TV Bouquet only
+
+ int count = 1;
+
+ XmlNodeList e2services = xml.GetElementsByTagName("e2service");
+ foreach (XmlNode xmlNode in e2services)
+ {
+ var channelInfo = new ChannelInfo();
+
+ var e2servicereference = "?";
+ var e2servicename = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2servicereference")
+ {
+ e2servicereference = node.InnerText;
+ }
+ else if (node.Name == "e2servicename")
+ {
+ e2servicename = node.InnerText;
+ }
+ }
+
+ // Check whether the current element is not just a label
+ if (!e2servicereference.StartsWith("1:64:"))
+ {
+ //check for radio channel
+ if (e2servicereference.Contains("radio"))
+ channelInfo.ChannelType = ChannelType.Radio;
+ else
+ channelInfo.ChannelType = ChannelType.TV;
+
+ channelInfo.HasImage = true;
+ channelInfo.Id = e2servicereference;
+
+ // image name is name is e2servicereference with last char removed, then replace all : with _, then add .png
+ var imageName = e2servicereference.Remove(e2servicereference.Length - 1);
+ imageName = imageName.Replace(":", "_");
+ imageName = imageName + ".png";
+ //var imageUrl = string.Format("{0}/picon/{1}", baseUrl, imageName);
+ var imageUrl = string.Format("{0}/picon/{1}", baseUrlPicon, imageName);
+
+ if (Plugin.Instance.Configuration.FetchPiconsFromWebInterface)
+ {
+ channelInfo.ImagePath = null;
+ //channelInfo.ImageUrl = WebUtility.UrlEncode(imageUrl);
+ channelInfo.ImageUrl = imageUrl;
+ }
+ else
+ {
+ channelInfo.ImagePath = Plugin.Instance.Configuration.PiconsPath + imageName;
+ channelInfo.ImageUrl = null;
+ }
+
+ channelInfo.Name = e2servicename;
+ channelInfo.Number = count.ToString();
+
+ channelInfos.Add(channelInfo);
+ count = count + 1;
+ }
+ else
+ {
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] ignoring channel {0}", e2servicereference));
+ }
+ }
+ return channelInfos;
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse channel information.");
+ _logger.Error(string.Format("[VuPlus] GetChannelsForTVBouquetAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse channel information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Gets the Recordings async
+ ///
+ /// The cancellation token.
+ /// Task{IEnumerable{RecordingInfo}}
+ public async Task> GetRecordingsAsync(CancellationToken cancellationToken)
+ {
+ _logger.Info("[VuPlus] Start GetRecordingsAsync, retrieve all 'Inprogress' and 'Completed' recordings ");
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/movielist", baseUrl);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetRecordingsAsync url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetRecordingsAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ List recordingInfos = new List();
+
+ int count = 1;
+
+ XmlNodeList e2movie = xml.GetElementsByTagName("e2movie");
+
+ foreach (XmlNode xmlNode in e2movie)
+ {
+ var recordingInfo = new RecordingInfo();
+
+ var e2servicereference = "?";
+ var e2title = "?";
+ var e2description = "?";
+ var e2servicename = "?";
+ var e2time = "?";
+ var e2length = "?";
+ var e2filename = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2servicereference")
+ {
+ e2servicereference = node.InnerText;
+ }
+ else if (node.Name == "e2title")
+ {
+ e2title = node.InnerText;
+ }
+ else if (node.Name == "e2description")
+ {
+ e2description = node.InnerText;
+ }
+ else if (node.Name == "e2servicename")
+ {
+ e2servicename = node.InnerText;
+ }
+ else if (node.Name == "e2time")
+ {
+ e2time = node.InnerText;
+ }
+ else if (node.Name == "e2length")
+ {
+ e2length = node.InnerText;
+ }
+ else if (node.Name == "e2filename")
+ {
+ e2filename = node.InnerText;
+ }
+ }
+
+ recordingInfo.Audio = null;
+
+ recordingInfo.ChannelId = null;
+ //check for radio channel
+ if (e2servicereference.ToUpper().Contains("RADIO"))
+ recordingInfo.ChannelType = ChannelType.Radio;
+ else
+ recordingInfo.ChannelType = ChannelType.TV;
+ recordingInfo.HasImage = false;
+ recordingInfo.ImagePath = null;
+ recordingInfo.ImageUrl = null;
+
+ foreach (ChannelInfo channelInfo in tvChannelInfos)
+ {
+ if (channelInfo.Name == e2servicename)
+ {
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetRecordingsAsync match on channel name : {0} for recording {1}", e2servicename, e2title));
+ recordingInfo.ChannelId = channelInfo.Id;
+ recordingInfo.ChannelType = channelInfo.ChannelType;
+ recordingInfo.HasImage = true;
+ recordingInfo.ImagePath = channelInfo.ImagePath;
+ recordingInfo.ImageUrl = channelInfo.ImageUrl;
+ break;
+ }
+ }
+
+ if (recordingInfo.ChannelId == null)
+ {
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetRecordingsAsync no match on channel name : {0} for recording {1}", e2servicename, e2title));
+ }
+
+ recordingInfo.CommunityRating = 0;
+
+ Double sdated = Convert.ToDouble(e2time);
+ DateTime sdate = new DateTime(1970, 1, 1, 0, 0, 0, 0); //Set default date 1/1/1970
+ sdate = sdate.AddSeconds(sdated); //add seconds
+ recordingInfo.StartDate = sdate.ToUniversalTime();
+
+ //length in format mm:ss
+ string[] words = e2length.Split(':');
+ Double mins = Convert.ToDouble(words[0]);
+ Double seconds = Convert.ToDouble(words[1]);
+
+ Double edated = Convert.ToDouble(e2time) + (mins * 60) + (seconds);
+ DateTime edate = new DateTime(1970, 1, 1, 0, 0, 0, 0); //Set default date 1/1/1970
+ edate = edate.AddSeconds(edated); //add seconds
+ recordingInfo.EndDate = edate.ToUniversalTime();
+
+ recordingInfo.EpisodeTitle = null;
+
+ recordingInfo.Overview = e2description;
+
+ List genre = new List();
+ genre.Add("Unknown");
+ recordingInfo.Genres = genre;
+
+ recordingInfo.Id = e2servicereference;
+ recordingInfo.IsHD = false;
+ recordingInfo.IsKids = false;
+ recordingInfo.IsLive = false;
+ recordingInfo.IsMovie = false;
+ recordingInfo.IsNews = false;
+ recordingInfo.IsPremiere = false;
+ recordingInfo.IsRepeat = false;
+ recordingInfo.IsSeries = false;
+ recordingInfo.IsSports = false;
+ recordingInfo.Name = e2title;
+ recordingInfo.OfficialRating = null;
+ recordingInfo.OriginalAirDate = null;
+ recordingInfo.Overview = e2description;
+ recordingInfo.Path = null;
+ recordingInfo.ProgramId = null;
+ recordingInfo.SeriesTimerId = null;
+ recordingInfo.Url = baseUrl + "/file?file=" + WebUtility.UrlEncode(e2filename);
+
+ recordingInfos.Add(recordingInfo);
+ count = count + 1;
+ }
+ return recordingInfos;
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse timer information.");
+ _logger.Error(string.Format("[VuPlus] GetRecordingsAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse timer information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Delete the Recording async from the disk
+ ///
+ /// The recordingId
+ /// The cancellationToken
+ ///
+ public async Task DeleteRecordingAsync(string recordingId, CancellationToken cancellationToken)
+ {
+ _logger.Info(string.Format("[VuPlus] Start Delete Recording Async for recordingId: {0}",recordingId));
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl =protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/moviedelete?sRef={1}", baseUrl, recordingId);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] DeleteRecordingAsync url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] DeleteRecordingAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ XmlNodeList e2simplexmlresult = xml.GetElementsByTagName("e2simplexmlresult");
+ foreach (XmlNode xmlNode in e2simplexmlresult)
+ {
+ var recordingInfo = new RecordingInfo();
+
+ var e2state = "?";
+ var e2statetext = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2state")
+ {
+ e2state = node.InnerText;
+ }
+ else if (node.Name == "e2statetext")
+ {
+ e2statetext = node.InnerText;
+ }
+ }
+
+ if (e2state != "True")
+ {
+ _logger.Error("[VuPlus] Failed to delete recording information.");
+ _logger.Error(string.Format("[VuPlus] DeleteRecordingAsync e2statetext: {0}", e2statetext));
+ throw new ApplicationException("Failed to delete recording.");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse delete recording information.");
+ _logger.Error(string.Format("[VuPlus] DeleteRecordingAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse delete recording information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Cancel pending scheduled Recording
+ ///
+ /// The timerId
+ /// The cancellationToken
+ ///
+ public async Task CancelTimerAsync(string timerId, CancellationToken cancellationToken)
+ {
+ _logger.Info(string.Format("[VuPlus] Start CancelTimerAsync for recordingId: {0}", timerId));
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ // extract sRef, id, begin and end from passed timerId
+ string[] words = timerId.Split('~');
+ var sRef = words[0];
+ var id = words[1];
+ var begin = words[2];
+ var end = words[3];
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/timerdelete?sRef={1}&begin={2}&end={3}", baseUrl, sRef, begin, end);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] CancelTimerAsync url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] CancelTimerAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ XmlNodeList e2simplexmlresult = xml.GetElementsByTagName("e2simplexmlresult");
+ foreach (XmlNode xmlNode in e2simplexmlresult)
+ {
+ var recordingInfo = new RecordingInfo();
+
+ var e2state = "?";
+ var e2statetext = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2state")
+ {
+ e2state = node.InnerText;
+ }
+ else if (node.Name == "e2statetext")
+ {
+ e2statetext = node.InnerText;
+ }
+ }
+
+ if (e2state != "True")
+ {
+ _logger.Error("[VuPlus] Failed to cancel timer.");
+ _logger.Error(string.Format("[VuPlus] CancelTimerAsync e2statetext: {0}", e2statetext));
+ throw new ApplicationException("Failed to cancel timer.");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse cancel timer information.");
+ _logger.Error(string.Format("[VuPlus] CancelTimerAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse cancel timer information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Create a new recording
+ ///
+ /// The TimerInfo
+ /// The cancellationToken
+ ///
+ public async Task CreateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
+ {
+ _logger.Info(string.Format("[VuPlus] Start CreateTimerAsync for ChannelId: {0} & Name: {1}",info.ChannelId, info.Name));
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ // extract eventid from info.ProgramId
+ string[] words = info.ProgramId.Split('~');
+ var eventid = words[1];
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/timeraddbyeventid?sRef={1}&eventid={2}", baseUrl, info.ChannelId, eventid);
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.RecordingPath))
+ {
+ url = url + string.Format("&dirname={0}", WebUtility.UrlEncode(Plugin.Instance.Configuration.RecordingPath));
+ }
+
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] CreateTimerAsync url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] CancelTimerAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ XmlNodeList e2simplexmlresult = xml.GetElementsByTagName("e2simplexmlresult");
+ foreach (XmlNode xmlNode in e2simplexmlresult)
+ {
+ var recordingInfo = new RecordingInfo();
+
+ var e2state = "?";
+ var e2statetext = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2state")
+ {
+ e2state = node.InnerText;
+ }
+ else if (node.Name == "e2statetext")
+ {
+ e2statetext = node.InnerText;
+ }
+ }
+
+ if (e2state != "True")
+ {
+ _logger.Error("[VuPlus] Failed to create timer.");
+ _logger.Error(string.Format("[VuPlus] CreateTimerAsync e2statetext: {0}", e2statetext));
+ throw new ApplicationException("Failed to create timer.");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse create timer information.");
+ _logger.Error(string.Format("[VuPlus] CreateTimerAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse create timer information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Get the pending Recordings.
+ ///
+ /// The CancellationToken
+ /// IEnumerable
+ public async Task> GetTimersAsync(CancellationToken cancellationToken)
+ {
+ _logger.Info("[VuPlus] Start GetTimerAsync, retrieve the 'Pending' recordings");
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/timerlist", baseUrl);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetTimersAsync url: {0}", url));
+
+ var options = new HttpRequestOptions
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetTimersAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ List timerInfos = new List();
+
+ int count = 1;
+
+ XmlNodeList e2timer = xml.GetElementsByTagName("e2timer");
+ foreach (XmlNode xmlNode in e2timer)
+ {
+ var timerInfo = new TimerInfo();
+
+ var e2servicereference = "?";
+ var e2name = "?";
+ var e2description = "?";
+ var e2eit = "?";
+ var e2timebegin = "?";
+ var e2timeend = "?";
+ var e2state = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2servicereference")
+ {
+ e2servicereference = node.InnerText;
+ }
+ else if (node.Name == "e2name")
+ {
+ e2name = node.InnerText;
+ }
+ else if (node.Name == "e2description")
+ {
+ e2description = node.InnerText;
+ }
+ else if (node.Name == "e2eit")
+ {
+ e2eit = node.InnerText;
+ }
+ else if (node.Name == "e2timebegin")
+ {
+ e2timebegin = node.InnerText;
+ }
+ else if (node.Name == "e2timeend")
+ {
+ e2timeend = node.InnerText;
+ }
+ else if (node.Name == "e2state")
+ {
+ e2state = node.InnerText;
+ }
+ }
+
+ // only interested in pending timers and ones recording now
+ if (e2state == "0" || e2state == "2")
+ {
+
+ timerInfo.ChannelId = e2servicereference;
+
+ Double edated = Convert.ToDouble(e2timeend);
+ DateTime edate = new DateTime(1970, 1, 1, 0, 0, 0, 0); //Set default date 1/1/1970
+ edate = edate.AddSeconds(edated); //add seconds
+ timerInfo.EndDate = edate.ToUniversalTime();
+
+ timerInfo.Id = e2servicereference + "~" + e2eit + "~" + e2timebegin + "~" + e2timeend + "~" + count;
+
+ timerInfo.IsPostPaddingRequired = false;
+ timerInfo.IsPrePaddingRequired = false;
+ timerInfo.Name = e2name;
+ timerInfo.Overview = e2description;
+ timerInfo.PostPaddingSeconds = 0;
+ timerInfo.PrePaddingSeconds = 0;
+ timerInfo.Priority = 0;
+ timerInfo.ProgramId = null;
+ timerInfo.SeriesTimerId = null;
+
+ Double sdated = Convert.ToDouble(e2timebegin);
+ DateTime sdate = new DateTime(1970, 1, 1, 0, 0, 0, 0); //Set default date 1/1/1970
+ sdate = sdate.AddSeconds(sdated); //add seconds
+ timerInfo.StartDate = sdate.ToUniversalTime();
+ if (e2state == "0")
+ timerInfo.Status = RecordingStatus.Scheduled;
+ if (e2state == "2")
+ timerInfo.Status = RecordingStatus.InProgress;
+
+ timerInfos.Add(timerInfo);
+ count = count + 1;
+ }
+ else
+ {
+ _logger.Info("[VuPlus] ignoring timer " + e2name);
+ }
+ }
+ return timerInfos;
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse timer information.");
+ _logger.Error(string.Format("[VuPlus] GetTimersAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse timer information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Get the live channel stream, zap to channel if required.
+ ///
+ /// The CancellationToken
+ /// ChannelMediaInfo
+ public async Task GetChannelStream(string channelOid, CancellationToken cancellationToken)
+ {
+ _logger.Info("[VuPlus] Start GetChannelStream");
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.StreamingPort;
+
+ //check if we need to zap to channel - single tuner
+ if (Plugin.Instance.Configuration.ZapToChannel)
+ {
+ await ZapToChannel(cancellationToken, channelOid).ConfigureAwait(false);
+ }
+
+ _liveStreams++;
+ string streamUrl = string.Format("{0}/{1}", baseUrl, channelOid);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetChannelStream url: {0}", streamUrl));
+
+ return new ChannelMediaInfo
+ {
+ Id = _liveStreams.ToString(CultureInfo.InvariantCulture),
+ Path = streamUrl,
+ Protocol = MediaProtocol.Http
+ };
+ throw new ResourceNotFoundException(string.Format("Could not stream channel {0}", channelOid));
+ }
+
+
+ ///
+ /// zap to channel.
+ ///
+ /// The CancellationToken
+ /// The channel id
+ ///
+ public async Task ZapToChannel(CancellationToken cancellationToken, String channelOid)
+ {
+ _logger.Info("[VuPlus] Start ZapToChannel");
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/zap?sRef={1}", baseUrl, channelOid);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] ZapToChannel url: {0}", url));
+
+ var options = new HttpRequestOptions()
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] ZapToChannel response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ XmlNodeList e2simplexmlresult = xml.GetElementsByTagName("e2simplexmlresult");
+ foreach (XmlNode xmlNode in e2simplexmlresult)
+ {
+ var recordingInfo = new RecordingInfo();
+
+ var e2state = "?";
+ var e2statetext = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2state")
+ {
+ e2state = node.InnerText;
+ }
+ else if (node.Name == "e2statetext")
+ {
+ e2statetext = node.InnerText;
+ }
+ }
+
+ if (e2state != "True")
+ {
+ _logger.Error("[VuPlus] Failed to zap to channel.");
+ _logger.Error(string.Format("[VuPlus] ZapToChannel e2statetext: {0}", e2statetext));
+ throw new ApplicationException("Failed to zap to channel.");
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse create timer information.");
+ _logger.Error(string.Format("[VuPlus] ZapToChannel error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse zap to channel information.");
+ }
+ }
+ }
+
+
+ }
+
+
+ ///
+ /// Get new timer defaults.
+ ///
+ /// The CancellationToken
+ /// null
+ /// SeriesTimerInfo
+ public async Task GetNewTimerDefaultsAsync(CancellationToken cancellationToken, ProgramInfo program = null)
+ {
+ _logger.Info("[VuPlus] Start GetNewTimerDefaultsAsync");
+
+ SeriesTimerInfo seriesTimerInfo = new SeriesTimerInfo();
+
+ return seriesTimerInfo;
+ }
+
+
+ ///
+ /// Get programs for specified channel within start and end date.
+ ///
+ /// channel id
+ /// start date/time
+ /// end date/time
+ /// The CancellationToken
+ /// IEnumerable
+ public async Task> GetProgramsAsync(string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
+ {
+ _logger.Info(string.Format("[VuPlus] Start GetProgramsAsync for channelId: {0}",channelId));
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ Random rnd = new Random();
+
+ var imagePath = "";
+ var imageUrl = "";
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/epgservice?sRef={1}", baseUrl, channelId);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetProgramsAsync url: {0}", url));
+
+ var options = new HttpRequestOptions()
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetProgramsAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ List programInfos = new List();
+
+ int count = 1;
+
+ XmlNodeList e2event = xml.GetElementsByTagName("e2event");
+ foreach (XmlNode xmlNode in e2event)
+ {
+ var programInfo = new ProgramInfo();
+
+ var e2eventid = "?";
+ var e2eventstart = "?";
+ var e2eventduration = "?";
+ var e2eventcurrenttime = "?";
+ var e2eventtitle = "?";
+ var e2eventdescription = "?";
+ var e2eventdescriptionextended = "?";
+ var e2eventservicereference = "?";
+ var e2eventservicename = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2eventid")
+ {
+ e2eventid = node.InnerText;
+ }
+ else if (node.Name == "e2eventstart")
+ {
+ e2eventstart = node.InnerText;
+ }
+ else if (node.Name == "e2eventduration")
+ {
+ e2eventduration = node.InnerText;
+ }
+ else if (node.Name == "e2eventcurrenttime")
+ {
+ e2eventcurrenttime = node.InnerText;
+ }
+ else if (node.Name == "e2eventtitle")
+ {
+ e2eventtitle = node.InnerText;
+ }
+ else if (node.Name == "e2eventdescription")
+ {
+ e2eventdescription = node.InnerText;
+ }
+ else if (node.Name == "e2eventdescriptionextended")
+ {
+ e2eventdescriptionextended = node.InnerText;
+ }
+ else if (node.Name == "e2eventservicereference")
+ {
+ e2eventservicereference = node.InnerText;
+ }
+ else if (node.Name == "e2eventservicename")
+ {
+ e2eventservicename = node.InnerText;
+ }
+ }
+
+ Double sdated = Convert.ToDouble(e2eventstart);
+ DateTime sdate = new DateTime(1970, 1, 1, 0, 0, 0, 0); //Set default date 1/1/1970
+ sdate = sdate.AddSeconds(sdated); //add seconds
+
+ // Check whether the current element is within the time range passed
+ if (sdate > endDateUtc)
+ {
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetProgramsAsync epc full ending without adding channel name : {0} program : {1}", e2eventservicename, e2eventtitle));
+ return programInfos;
+ }
+ else
+ {
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetProgramsAsync adding program for channel name : {0} program : {1}", e2eventservicename, e2eventtitle));
+ programInfo.HasImage = false;
+ if (count == 1)
+ {
+ foreach (ChannelInfo channelInfo in tvChannelInfos)
+ {
+ if (channelInfo.Name == e2eventservicename)
+ {
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetProgramsAsync match on channel name : {0}", e2eventservicename));
+ //programInfo.HasImage = true;
+ //programInfo.ImagePath = channelInfo.ImagePath;
+ //programInfo.ImageUrl = channelInfo.ImageUrl;
+ imagePath = channelInfo.ImagePath;
+ imageUrl = channelInfo.ImageUrl;
+ break;
+ }
+ }
+ }
+
+ programInfo.HasImage = true;
+ programInfo.ImagePath = imagePath;
+ programInfo.ImageUrl = imageUrl;
+
+ programInfo.ChannelId = e2eventservicereference;
+
+ // the Id appears to have to be unique so will try to make it so
+ programInfo.Id = e2eventservicereference + "~" + e2eventid + "~" + count + "~" + rnd.Next();
+
+ programInfo.Overview = e2eventdescriptionextended;
+
+ Double edated = Convert.ToDouble(e2eventstart) + Convert.ToDouble(e2eventduration);
+ DateTime edate = new DateTime(1970, 1, 1, 0, 0, 0, 0); //Set default date 1/1/1970
+ edate = edate.AddSeconds(edated); //add seconds
+
+ programInfo.StartDate = sdate.ToUniversalTime();
+ programInfo.EndDate = edate.ToUniversalTime();
+
+ List genre = new List();
+ genre.Add("Unknown");
+ programInfo.Genres = genre;
+
+ programInfo.OriginalAirDate = null;
+ programInfo.Name = e2eventtitle;
+ programInfo.OfficialRating = null;
+ programInfo.CommunityRating = null;
+ programInfo.EpisodeTitle = null;
+ programInfo.Audio = null;
+ programInfo.IsHD = false;
+ programInfo.IsRepeat = false;
+ programInfo.IsSeries = false;
+ programInfo.IsNews = false;
+ programInfo.IsMovie = false;
+ programInfo.IsKids = false;
+ programInfo.IsSports = false;
+
+ programInfos.Add(programInfo);
+ count = count + 1;
+ }
+ }
+ return programInfos;
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse program information.");
+ _logger.Error(string.Format("[VuPlus] GetProgramsAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse channel information.");
+ }
+ }
+ }
+ }
+
+
+ ///
+ /// Get server status info.
+ ///
+ /// The CancellationToken
+ /// LiveTvServiceStatusInfo
+ public async Task GetStatusInfoAsync(CancellationToken cancellationToken)
+ {
+ _logger.Info("[VuPlus] Start GetStatusInfoAsync Async, retrieve status details");
+ await EnsureConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ //TODO: Version check
+
+ bool upgradeAvailable = false;
+ string serverVersion = "Unknown";
+
+ var protocol = "http";
+ if (Plugin.Instance.Configuration.UseSecureHTTPS)
+ protocol = "https";
+
+ var baseUrl = protocol + "://" + Plugin.Instance.Configuration.HostName + ":" + Plugin.Instance.Configuration.WebInterfacePort;
+
+ var url = string.Format("{0}/web/deviceinfo", baseUrl);
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetStatusInfoAsync url: {0}", url));
+
+ var options = new HttpRequestOptions()
+ {
+ CancellationToken = cancellationToken,
+ Url = url
+ };
+
+ if (!string.IsNullOrEmpty(Plugin.Instance.Configuration.WebInterfaceUsername))
+ {
+ string authInfo = Plugin.Instance.Configuration.WebInterfaceUsername + ":" + Plugin.Instance.Configuration.WebInterfacePassword;
+ authInfo = Convert.ToBase64String(Encoding.Default.GetBytes(authInfo));
+ options.RequestHeaders["Authorization"] = "Basic " + authInfo;
+ }
+
+ List liveTvTunerInfos = new List();
+
+ using (var stream = await _httpClient.Get(options).ConfigureAwait(false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ string xmlResponse = reader.ReadToEnd();
+ UtilsHelper.DebugInformation(_logger, string.Format("[VuPlus] GetStatusInfoAsync response: {0}", xmlResponse));
+
+ try
+ {
+ var xml = new XmlDocument();
+ xml.LoadXml(xmlResponse);
+
+ XmlNodeList e2frontend = xml.GetElementsByTagName("e2frontend");
+ foreach (XmlNode xmlNode in e2frontend)
+ {
+ var liveTvTunerInfo = new LiveTvTunerInfo();
+
+ var e2name = "?";
+ var e2model = "?";
+
+ foreach (XmlNode node in xmlNode.ChildNodes)
+ {
+ if (node.Name == "e2name")
+ {
+ e2name = node.InnerText;
+ }
+ else if (node.Name == "e2model")
+ {
+ e2model = node.InnerText;
+ }
+ }
+
+ liveTvTunerInfo.Id = e2model;
+ liveTvTunerInfo.Name = e2name;
+ liveTvTunerInfo.SourceType = "";
+
+ liveTvTunerInfos.Add(liveTvTunerInfo);
+ }
+
+ return new LiveTvServiceStatusInfo
+ {
+ HasUpdateAvailable = upgradeAvailable,
+ Version = serverVersion,
+ Tuners = liveTvTunerInfos
+ };
+
+ }
+ catch (Exception e)
+ {
+ _logger.Error("[VuPlus] Failed to parse tuner information.");
+ _logger.Error(string.Format("[VuPlus] GetStatusInfoAsync error: {0}", e.Message));
+ throw new ApplicationException("Failed to parse tuner information.");
+ }
+
+ }
+ }
+ }
+
+
+ ///
+ /// Gets the homepage url.
+ ///
+ /// The homepage url.
+ public string HomePageUrl
+ {
+ get { return "http://www.VuPlus.com/"; }
+ }
+
+
+ ///
+ /// Gets the name.
+ ///
+ /// The name.
+ public string Name
+ {
+ get { return "VuPlus"; }
+ }
+
+
+ public async Task GetRecordingStream(string recordingId, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ public async Task CloseLiveStream(string id, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ public async Task CopyFilesAsync(StreamReader source, StreamWriter destination)
+ {
+ _logger.Info("[VuPlus] Start CopyFiles Async");
+ char[] buffer = new char[0x1000];
+ int numRead;
+ while ((numRead = await source.ReadAsync(buffer, 0, buffer.Length)) != 0)
+ {
+ await destination.WriteAsync(buffer, 0, numRead);
+ }
+ }
+
+
+ public Task RecordLiveStream(string id, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ ///
+ /// Get the recurrent recordings
+ ///
+ /// The CancellationToken
+ ///
+ public async Task> GetSeriesTimersAsync(CancellationToken cancellationToken)
+ {
+ _logger.Info("[VuPlus] Start GetSeriesTimersAsync");
+
+ List seriesTimerInfo = new List();
+ return seriesTimerInfo;
+ }
+
+
+ ///
+ /// Create a recurrent recording
+ ///
+ /// The recurrend program info
+ /// The CancelationToken
+ ///
+ public async Task CreateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ ///
+ /// Update the series Timer
+ ///
+ /// The series program info
+ /// The CancellationToken
+ ///
+ public async Task UpdateSeriesTimerAsync(SeriesTimerInfo info, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ ///
+ /// Update a single Timer
+ ///
+ /// The program info
+ /// The CancellationToken
+ ///
+ public async Task UpdateTimerAsync(TimerInfo info, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ ///
+ /// Cancel the Series Timer
+ ///
+ /// The Timer Id
+ /// The CancellationToken
+ ///
+ public async Task CancelSeriesTimerAsync(string timerId, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ ///
+ /// Get the DefaultScheduleSettings
+ ///
+ /// The CancellationToken
+ ///
+ //private async Task GetDefaultScheduleSettings(CancellationToken cancellationToken)
+ //{
+ // throw new NotImplementedException();
+ //}
+
+
+ public event EventHandler DataSourceChanged;
+
+
+ public event EventHandler RecordingStatusChanged;
+
+
+ public Task ResetTuner(string id, CancellationToken cancellationToken)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ public Task GetChannelImageAsync(string channelId, CancellationToken cancellationToken)
+ {
+ // Leave as is. This is handled by supplying image url to ChannelInfo
+ throw new NotImplementedException();
+ }
+
+
+ public Task GetProgramImageAsync(string programId, string channelId, CancellationToken cancellationToken)
+ {
+ // Leave as is. This is handled by supplying image url to ProgramInfo
+ throw new NotImplementedException();
+ }
+
+
+ public Task GetRecordingImageAsync(string recordingId, CancellationToken cancellationToken)
+ {
+ // Leave as is. This is handled by supplying image url to RecordingInfo
+ throw new NotImplementedException();
+ }
+
+ public Task GetLiveTvChannelsAsync(string recordingId, CancellationToken cancellationToken)
+ {
+ // Leave as is. This is handled by supplying image url to RecordingInfo
+ throw new NotImplementedException();
+ }
+
+
+ }
+}
diff --git a/src/MediaBrowser.Plugins.VuPlus.csproj b/src/MediaBrowser.Plugins.VuPlus.csproj
new file mode 100644
index 0000000..e394e04
--- /dev/null
+++ b/src/MediaBrowser.Plugins.VuPlus.csproj
@@ -0,0 +1,88 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {815AB3F4-6B55-4FC4-9441-9297A3A116CB}
+ Library
+ Properties
+ MediaBrowser.Plugins.VuPlus
+ MediaBrowser.Plugins.VuPlus
+ v4.5
+ 512
+ ..\
+ true
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\packages\MediaBrowser.Common.3.0.566\lib\net45\MediaBrowser.Common.dll
+
+
+ False
+ ..\packages\MediaBrowser.Server.Core.3.0.566\lib\net45\MediaBrowser.Controller.dll
+
+
+ False
+ ..\packages\MediaBrowser.Common.3.0.566\lib\net45\MediaBrowser.Model.dll
+
+
+
+
+
+
+
+
+
+
+
+ Properties\SharedVersion.cs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Designer
+
+
+
+
+
+ xcopy "$(TargetPath)" "$(SolutionDir)\..\MediaBrowser\ProgramData-Server\Plugins\" /y
+
+
+
\ No newline at end of file
diff --git a/src/Plugin.cs b/src/Plugin.cs
new file mode 100644
index 0000000..ca7d8c6
--- /dev/null
+++ b/src/Plugin.cs
@@ -0,0 +1,46 @@
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Model.Serialization;
+using MediaBrowser.Plugins.VuPlus.Configuration;
+
+namespace MediaBrowser.Plugins.VuPlus
+{
+ ///
+ /// Class Plugin
+ ///
+ public class Plugin : BasePlugin
+ {
+ public Plugin(IApplicationPaths applicationPaths, IXmlSerializer xmlSerializer)
+ : base(applicationPaths, xmlSerializer)
+ {
+ Instance = this;
+ }
+
+ ///
+ /// Gets the name of the plugin
+ ///
+ /// The name.
+ public override string Name
+ {
+ get { return "VuPlus"; }
+ }
+
+ ///
+ /// Gets the description.
+ ///
+ /// The description.
+ public override string Description
+ {
+ get
+ {
+ return "Provides live tv using Vu+ / Enigma2 pvr as a back-end.";
+ }
+ }
+
+ ///
+ /// Gets the instance.
+ ///
+ /// The instance.
+ public static Plugin Instance { get; private set; }
+ }
+}
diff --git a/src/Properties/AssemblyInfo.cs b/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a331800
--- /dev/null
+++ b/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("MediaBrowser.Plugins.VuPlus")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Plugins.VuPlus")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("9564ac10-af23-39bc-449f-624f23cfa48f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+
diff --git a/src/packages.config b/src/packages.config
new file mode 100644
index 0000000..4276370
--- /dev/null
+++ b/src/packages.config
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file