diff options
93 files changed, 5325 insertions, 0 deletions
diff --git a/.hgignore b/.hgignore new file mode 100644 index 000000000..06171f67d --- /dev/null +++ b/.hgignore @@ -0,0 +1,37 @@ +# use glob syntax
+syntax: glob
+
+*.obj
+*.pdb
+*.user
+*.aps
+*.pch
+*.vspscc
+*.vssscc
+*_i.c
+*_p.c
+*.ncb
+*.suo
+*.tlb
+*.tlh
+*.bak
+*.cache
+*.ilk
+*.log
+*.lib
+*.sbr
+*.scc
+[Bb]in
+[Dd]ebug*/
+obj/
+[Rr]elease*/
+_ReSharper*/
+[Tt]humbs.db
+[Tt]est[Rr]esult*
+[Bb]uild[Ll]og.*
+*.[Pp]ublish.xml
+*.resharper
+
+# ncrunch files
+*.ncrunchsolution
+*.ncrunchproject
\ No newline at end of file diff --git a/MediaBrowser.Api/HttpHandlers/ImageHandler.cs b/MediaBrowser.Api/HttpHandlers/ImageHandler.cs new file mode 100644 index 000000000..ee4b04c11 --- /dev/null +++ b/MediaBrowser.Api/HttpHandlers/ImageHandler.cs @@ -0,0 +1,14 @@ +using MediaBrowser.Controller.Net;
+using System;
+using System.IO;
+using System.IO.Compression;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller;
+
+namespace MediaBrowser.Api.HttpHandlers
+{
+ class ImageHandler
+ {
+ }
+}
diff --git a/MediaBrowser.Api/HttpHandlers/ItemHandler.cs b/MediaBrowser.Api/HttpHandlers/ItemHandler.cs new file mode 100644 index 000000000..40824bd2c --- /dev/null +++ b/MediaBrowser.Api/HttpHandlers/ItemHandler.cs @@ -0,0 +1,93 @@ +using MediaBrowser.Controller.Net;
+using System;
+using System.IO;
+using System.IO.Compression;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller;
+
+namespace MediaBrowser.Api.HttpHandlers
+{
+ public class ItemHandler : Response
+ {
+ public ItemHandler(RequestContext ctx)
+ : base(ctx)
+ {
+ ContentType = "application/json";
+
+ Headers["Content-Encoding"] = "gzip";
+
+ WriteStream = s =>
+ {
+ WriteReponse(s);
+ s.Close();
+ };
+ }
+
+ private Guid ItemId
+ {
+ get
+ {
+ string id = RequestContext.Request.QueryString["id"];
+
+ if (string.IsNullOrEmpty(id))
+ {
+ return Guid.Empty;
+ }
+
+ return Guid.Parse(id);
+ }
+ }
+
+ BaseItem Item
+ {
+ get
+ {
+ Guid id = ItemId;
+
+ if (id == Guid.Empty)
+ {
+ return Kernel.Instance.RootFolder;
+ }
+
+ return Kernel.Instance.RootFolder.FindById(id);
+ }
+ }
+
+ private void WriteReponse(Stream stream)
+ {
+ BaseItem item = Item;
+
+ object returnObject;
+
+ Folder folder = item as Folder;
+
+ if (folder != null)
+ {
+ returnObject = new
+ {
+ Item = item,
+ Children = folder.Children
+ };
+ }
+ else
+ {
+ returnObject = new
+ {
+ Item = item
+ };
+ }
+
+ WriteJsonResponse(returnObject, stream);
+ }
+
+ private void WriteJsonResponse(object obj, Stream stream)
+ {
+ using (GZipStream gzipStream = new GZipStream(stream, CompressionMode.Compress, false))
+ {
+ JsonSerializer.Serialize(obj, gzipStream);
+ //gzipStream.Flush();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj new file mode 100644 index 000000000..933cb3827 --- /dev/null +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.Api</RootNamespace>
+ <AssemblyName>MediaBrowser.Api</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Reactive, Version=1.0.10621.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Rx-Main.1.0.11226\lib\Net4\System.Reactive.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="HttpHandlers\ImageHandler.cs" />
+ <Compile Include="HttpHandlers\ItemHandler.cs" />
+ <Compile Include="Plugin.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{9b1ddd79-5134-4df3-ace3-d1957a7350d8}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <PropertyGroup>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>
+ </PropertyGroup>
+ <!-- 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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.Api/Plugin.cs b/MediaBrowser.Api/Plugin.cs new file mode 100644 index 000000000..3137978fe --- /dev/null +++ b/MediaBrowser.Api/Plugin.cs @@ -0,0 +1,33 @@ +using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Api.HttpHandlers;
+
+namespace MediaBrowser.Api
+{
+ public class Plugin : BasePlugin<BasePluginConfiguration>
+ {
+ List<IDisposable> HttpHandlers = new List<IDisposable>();
+
+ protected override void InitInternal()
+ {
+ HttpHandlers.Add(Kernel.Instance.HttpServer.Where(ctx => ctx.Request.Url.LocalPath.EndsWith("mediabrowser/api/item")).Subscribe(ctx => ctx.Respond(new ItemHandler(ctx))));
+ HttpHandlers.Add(Kernel.Instance.HttpServer.Where(ctx => ctx.Request.Url.LocalPath.EndsWith("mediabrowser/api/image")).Subscribe(ctx => ctx.Respond(new ItemHandler(ctx))));
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+
+ foreach (var handler in HttpHandlers)
+ {
+ handler.Dispose();
+ }
+
+ HttpHandlers.Clear();
+ }
+ }
+}
diff --git a/MediaBrowser.Api/Properties/AssemblyInfo.cs b/MediaBrowser.Api/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0a7a85dc6 --- /dev/null +++ b/MediaBrowser.Api/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Api")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Api")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("13464b02-f033-48b8-9e1c-d071f8860935")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.Api/packages.config b/MediaBrowser.Api/packages.config new file mode 100644 index 000000000..47102a263 --- /dev/null +++ b/MediaBrowser.Api/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Rx-Main" version="1.0.11226" targetFramework="net45" />
+</packages>
\ No newline at end of file diff --git a/MediaBrowser.Common/Events/GenericItemEventArgs.cs b/MediaBrowser.Common/Events/GenericItemEventArgs.cs new file mode 100644 index 000000000..ae7b2f574 --- /dev/null +++ b/MediaBrowser.Common/Events/GenericItemEventArgs.cs @@ -0,0 +1,9 @@ +using System;
+
+namespace MediaBrowser.Common.Events
+{
+ public class GenericItemEventArgs<TItemType> : EventArgs
+ {
+ public TItemType Item { get; set; }
+ }
+}
diff --git a/MediaBrowser.Common/Json/JsonSerializer.cs b/MediaBrowser.Common/Json/JsonSerializer.cs new file mode 100644 index 000000000..c9b8d8e85 --- /dev/null +++ b/MediaBrowser.Common/Json/JsonSerializer.cs @@ -0,0 +1,55 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+
+namespace MediaBrowser.Common.Json
+{
+ public class JsonSerializer
+ {
+ public static void Serialize<T>(T o, Stream stream)
+ {
+ using (StreamWriter streamWriter = new StreamWriter(stream))
+ {
+ using (Newtonsoft.Json.JsonTextWriter writer = new Newtonsoft.Json.JsonTextWriter(streamWriter))
+ {
+ var settings = new Newtonsoft.Json.JsonSerializerSettings()
+ {
+ NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
+ };
+
+ Newtonsoft.Json.JsonSerializer.Create(settings).Serialize(writer, o);
+ }
+ }
+ }
+
+ public static void Serialize<T>(T o, string file)
+ {
+ using (StreamWriter streamWriter = new StreamWriter(file))
+ {
+ using (Newtonsoft.Json.JsonTextWriter writer = new Newtonsoft.Json.JsonTextWriter(streamWriter))
+ {
+ var settings = new Newtonsoft.Json.JsonSerializerSettings()
+ {
+ NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore
+ };
+
+ Newtonsoft.Json.JsonSerializer.Create(settings).Serialize(writer, o);
+ }
+ }
+ }
+
+ public static T Deserialize<T>(string file)
+ {
+ using (StreamReader streamReader = new StreamReader(file))
+ {
+ using (Newtonsoft.Json.JsonTextReader reader = new Newtonsoft.Json.JsonTextReader(streamReader))
+ {
+ return Newtonsoft.Json.JsonSerializer.Create(new Newtonsoft.Json.JsonSerializerSettings() { }).Deserialize<T>(reader);
+ }
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Logging/BaseLogger.cs b/MediaBrowser.Common/Logging/BaseLogger.cs new file mode 100644 index 000000000..51c6632d5 --- /dev/null +++ b/MediaBrowser.Common/Logging/BaseLogger.cs @@ -0,0 +1,80 @@ +using System;
+using System.Diagnostics;
+using System.Text;
+using System.Threading;
+
+namespace MediaBrowser.Common.Logging
+{
+ public abstract class BaseLogger
+ {
+ public LogSeverity LogSeverity { get; set; }
+
+ public void LogInfo(string message, params object[] paramList)
+ {
+ LogEntry(message, LogSeverity.Info, paramList);
+ }
+
+ public void LogDebugInfo(string message, params object[] paramList)
+ {
+ LogEntry(message, LogSeverity.Debug, paramList);
+ }
+
+ public void LogError(string message, params object[] paramList)
+ {
+ LogEntry(message, LogSeverity.Error, paramList);
+ }
+
+ public void LogException(string message, Exception exception, params object[] paramList)
+ {
+ StringBuilder builder = new StringBuilder();
+
+ if (exception != null)
+ {
+ var trace = new StackTrace(exception, true);
+ builder.AppendFormat("Exception. Type={0} Msg={1} Src={2} Method={5} Line={6} Col={7}{4}StackTrace={4}{3}",
+ exception.GetType().FullName,
+ exception.Message,
+ exception.Source,
+ exception.StackTrace,
+ Environment.NewLine,
+ trace.GetFrame(0).GetMethod().Name,
+ trace.GetFrame(0).GetFileLineNumber(),
+ trace.GetFrame(0).GetFileColumnNumber());
+ }
+
+ StackFrame frame = new StackFrame(1);
+
+ message = string.Format(message, paramList);
+
+ LogError(string.Format("{0} ( {1} )", message, builder));
+ }
+
+ public void LogWarning(string message, params object[] paramList)
+ {
+ LogEntry(message, LogSeverity.Warning, paramList);
+ }
+
+ private void LogEntry(string message, LogSeverity severity, params object[] paramList)
+ {
+ if (severity < LogSeverity) return;
+
+ message = string.Format(message, paramList);
+
+ Thread currentThread = Thread.CurrentThread;
+
+ LogRow row = new LogRow()
+ {
+ Severity = severity,
+ Message = message,
+ Category = string.Empty,
+ ThreadId = currentThread.ManagedThreadId,
+ ThreadName = currentThread.Name,
+ Time = DateTime.Now
+ };
+
+ LogEntry(row);
+ }
+
+ protected abstract void LogEntry(LogRow row);
+ }
+}
diff --git a/MediaBrowser.Common/Logging/FileLogger.cs b/MediaBrowser.Common/Logging/FileLogger.cs new file mode 100644 index 000000000..33c64b139 --- /dev/null +++ b/MediaBrowser.Common/Logging/FileLogger.cs @@ -0,0 +1,55 @@ +using System;
+using System.IO;
+using System.Text;
+
+namespace MediaBrowser.Common.Logging
+{
+ public class FileLogger : BaseLogger, IDisposable
+ {
+ private string LogDirectory { get; set; }
+ private string CurrentLogFile { get; set; }
+
+ private FileStream FileStream { get; set; }
+
+ public FileLogger(string logDirectory)
+ {
+ LogDirectory = logDirectory;
+ }
+
+ private void EnsureStream()
+ {
+ if (FileStream == null)
+ {
+ if (!Directory.Exists(LogDirectory))
+ {
+ Directory.CreateDirectory(LogDirectory);
+ }
+
+ DateTime now = DateTime.Now;
+
+ CurrentLogFile = Path.Combine(LogDirectory, now.ToString("dMyyyy") + "-" + now.Ticks + ".log");
+
+ FileStream = new FileStream(CurrentLogFile, FileMode.Append, FileAccess.Write, FileShare.Read);
+ }
+ }
+
+ protected override void LogEntry(LogRow row)
+ {
+ EnsureStream();
+
+ byte[] bytes = new UTF8Encoding().GetBytes(row.ToString() + Environment.NewLine);
+
+ FileStream.Write(bytes, 0, bytes.Length);
+
+ FileStream.Flush();
+ }
+
+ public void Dispose()
+ {
+ if (FileStream != null)
+ {
+ FileStream.Dispose();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Logging/LogRow.cs b/MediaBrowser.Common/Logging/LogRow.cs new file mode 100644 index 000000000..39c69eb45 --- /dev/null +++ b/MediaBrowser.Common/Logging/LogRow.cs @@ -0,0 +1,118 @@ +using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace MediaBrowser.Common.Logging
+{
+ public struct LogRow
+ {
+
+ const string TimePattern = "h:mm:ss.fff tt d/M/yyyy";
+
+
+ public LogSeverity Severity { get; set; }
+ public string Message { get; set; }
+ public string Category { get; set; }
+ public int ThreadId { get; set; }
+ public string ThreadName { get; set; }
+ public DateTime Time { get; set; }
+
+ public string ShortMessage
+ {
+ get
+ {
+ var message = Message;
+ if (message.Length > 120)
+ {
+ message = Message.Substring(0, 120).Replace(Environment.NewLine, " ") + " ... ";
+ }
+ return message;
+ }
+ }
+
+ public string FullDescription
+ {
+ get
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendFormat("Time: {0}", Time);
+ sb.AppendLine();
+ sb.AppendFormat("Thread Id: {0} {1}", ThreadId, ThreadName);
+ sb.AppendLine();
+ sb.AppendLine(Message);
+ return sb.ToString();
+ }
+ }
+
+ public override string ToString()
+ {
+ StringBuilder builder = new StringBuilder();
+ builder.Append(Time.ToString(TimePattern))
+ .Append(" , ")
+ .Append(Enum.GetName(typeof(LogSeverity), Severity))
+ .Append(" , ")
+ .Append(Encode(Message))
+ .Append(" , ")
+ .Append(Encode(Category))
+ .Append(" , ")
+ .Append(ThreadId)
+ .Append(" , ")
+ .Append(Encode(ThreadName));
+ return builder.ToString();
+ }
+
+ private string Encode(string str)
+ {
+ return (str ?? "").Replace(",", ",,").Replace(Environment.NewLine, " [n] ");
+ }
+
+ public static LogRow FromString(string message)
+ {
+ var split = splitString(message);
+ return new LogRow()
+ {
+ Time = DateTime.ParseExact(split[0], TimePattern, null),
+ Severity = (LogSeverity)Enum.Parse(typeof(LogSeverity), split[1]),
+ Message = split[2],
+ Category = split[3],
+ ThreadId = int.Parse(split[4]),
+ ThreadName = split[5]
+ };
+ }
+
+ static string[] splitString(string message)
+ {
+ List<string> items = new List<string>();
+ bool gotComma = false;
+
+ StringBuilder currentItem = new StringBuilder();
+
+ foreach (var chr in message)
+ {
+
+ if (chr == ',' && gotComma)
+ {
+ gotComma = false;
+ currentItem.Append(',');
+ }
+ else if (chr == ',')
+ {
+ gotComma = true;
+ }
+ else if (gotComma)
+ {
+ items.Add(currentItem.ToString().Replace(" [n] ", Environment.NewLine).Trim());
+ currentItem = new StringBuilder();
+ gotComma = false;
+ }
+ else
+ {
+ currentItem.Append(chr);
+ }
+
+ }
+ items.Add(currentItem.ToString().Replace("[n]", Environment.NewLine).Trim());
+ return items.ToArray();
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Logging/LogSeverity.cs b/MediaBrowser.Common/Logging/LogSeverity.cs new file mode 100644 index 000000000..b9578522e --- /dev/null +++ b/MediaBrowser.Common/Logging/LogSeverity.cs @@ -0,0 +1,18 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.Logging
+{
+ [Flags]
+ public enum LogSeverity
+ {
+ None = 0,
+ Debug = 1,
+ Info = 2,
+ Warning = 4,
+ Error = 8
+ }
+}
diff --git a/MediaBrowser.Common/Logging/Logger.cs b/MediaBrowser.Common/Logging/Logger.cs new file mode 100644 index 000000000..5f4c2ff79 --- /dev/null +++ b/MediaBrowser.Common/Logging/Logger.cs @@ -0,0 +1,38 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.Logging
+{
+ public static class Logger
+ {
+ public static BaseLogger LoggerInstance { get; set; }
+
+ public static void LogInfo(string message, params object[] paramList)
+ {
+ LoggerInstance.LogInfo(message, paramList);
+ }
+
+ public static void LogDebugInfo(string message, params object[] paramList)
+ {
+ LoggerInstance.LogDebugInfo(message, paramList);
+ }
+
+ public static void LogError(string message, params object[] paramList)
+ {
+ LoggerInstance.LogError(message, paramList);
+ }
+
+ public static void LogException(string message, Exception ex, params object[] paramList)
+ {
+ LoggerInstance.LogException(message, ex, paramList);
+ }
+
+ public static void LogWarning(string message, params object[] paramList)
+ {
+ LoggerInstance.LogWarning(message, paramList);
+ }
+ }
+}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj new file mode 100644 index 000000000..312f556b6 --- /dev/null +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.Common</RootNamespace>
+ <AssemblyName>MediaBrowser.Common</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json">
+ <HintPath>..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Events\GenericItemEventArgs.cs" />
+ <Compile Include="Json\JsonSerializer.cs" />
+ <Compile Include="Plugins\BasePluginConfiguration.cs" />
+ <Compile Include="Logging\BaseLogger.cs" />
+ <Compile Include="Logging\FileLogger.cs" />
+ <Compile Include="Logging\Logger.cs" />
+ <Compile Include="Logging\LogRow.cs" />
+ <Compile Include="Logging\LogSeverity.cs" />
+ <Compile Include="Plugins\BasePlugin.cs" />
+ <Compile Include="Plugins\PluginController.cs" />
+ <Compile Include="Properties\AssemblyInfo.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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.Common/Plugins/BasePlugin.cs b/MediaBrowser.Common/Plugins/BasePlugin.cs new file mode 100644 index 000000000..fb37afa08 --- /dev/null +++ b/MediaBrowser.Common/Plugins/BasePlugin.cs @@ -0,0 +1,58 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using MediaBrowser.Common.Json;
+
+namespace MediaBrowser.Common.Plugins
+{
+ public abstract class BasePlugin<TConfigurationType> : IDisposable, IPlugin
+ where TConfigurationType : BasePluginConfiguration, new()
+ {
+ public string Path { get; set; }
+ public TConfigurationType Configuration { get; private set; }
+
+ private string ConfigurationPath
+ {
+ get
+ {
+ return System.IO.Path.Combine(Path, "config.js");
+ }
+ }
+
+ public void Init()
+ {
+ Configuration = GetConfiguration();
+
+ if (Configuration.Enabled)
+ {
+ InitInternal();
+ }
+ }
+
+ protected abstract void InitInternal();
+
+ public virtual void Dispose()
+ {
+ }
+
+ private TConfigurationType GetConfiguration()
+ {
+ if (!File.Exists(ConfigurationPath))
+ {
+ return new TConfigurationType();
+ }
+
+ return JsonSerializer.Deserialize<TConfigurationType>(ConfigurationPath);
+ }
+ }
+
+ public interface IPlugin
+ {
+ string Path { get; set; }
+
+ void Init();
+ void Dispose();
+ }
+}
diff --git a/MediaBrowser.Common/Plugins/BasePluginConfiguration.cs b/MediaBrowser.Common/Plugins/BasePluginConfiguration.cs new file mode 100644 index 000000000..ad7972d94 --- /dev/null +++ b/MediaBrowser.Common/Plugins/BasePluginConfiguration.cs @@ -0,0 +1,18 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Common.Plugins
+{
+ public class BasePluginConfiguration
+ {
+ public bool Enabled { get; set; }
+
+ public BasePluginConfiguration()
+ {
+ Enabled = true;
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Plugins/PluginController.cs b/MediaBrowser.Common/Plugins/PluginController.cs new file mode 100644 index 000000000..9ce741ba1 --- /dev/null +++ b/MediaBrowser.Common/Plugins/PluginController.cs @@ -0,0 +1,90 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+
+namespace MediaBrowser.Common.Plugins
+{
+ public class PluginController
+ {
+ public string PluginsPath { get; set; }
+
+ public PluginController(string pluginFolderPath)
+ {
+ PluginsPath = pluginFolderPath;
+ }
+
+ public IEnumerable<IPlugin> GetAllPlugins()
+ {
+ AppDomain.CurrentDomain.AssemblyResolve -= new ResolveEventHandler(CurrentDomain_AssemblyResolve);
+ AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
+
+ if (!Directory.Exists(PluginsPath))
+ {
+ Directory.CreateDirectory(PluginsPath);
+ }
+
+ List<IPlugin> plugins = new List<IPlugin>();
+
+ foreach (string folder in Directory.GetDirectories(PluginsPath, "*", SearchOption.TopDirectoryOnly))
+ {
+ IPlugin plugin = GetPluginFromDirectory(folder);
+
+ plugin.Path = folder;
+
+ if (plugin != null)
+ {
+ plugins.Add(plugin);
+ }
+ }
+
+ return plugins;
+ }
+
+ private IPlugin GetPluginFromDirectory(string path)
+ {
+ string dll = Directory.GetFiles(path, "*.dll", SearchOption.TopDirectoryOnly).FirstOrDefault();
+
+ if (!string.IsNullOrEmpty(dll))
+ {
+ return GetPluginFromDll(dll);
+ }
+
+ return null;
+ }
+
+ private IPlugin GetPluginFromDll(string path)
+ {
+ return FindPlugin(Assembly.Load(File.ReadAllBytes(path)));
+ }
+
+ private IPlugin FindPlugin(Assembly assembly)
+ {
+ var plugin = assembly.GetTypes().Where(type => typeof(IPlugin).IsAssignableFrom(type)).FirstOrDefault();
+
+ if (plugin != null)
+ {
+ return plugin.GetConstructor(Type.EmptyTypes).Invoke(null) as IPlugin;
+ }
+
+ return null;
+ }
+
+ Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
+ {
+ AssemblyName assemblyName = new AssemblyName(args.Name);
+
+ IEnumerable<string> dllPaths = Directory.GetFiles(PluginsPath, "*.dll", SearchOption.AllDirectories);
+
+ string dll = dllPaths.FirstOrDefault(f => Path.GetFileNameWithoutExtension(f) == assemblyName.Name);
+
+ if (!string.IsNullOrEmpty(dll))
+ {
+ return Assembly.Load(File.ReadAllBytes(dll));
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.Common/Properties/AssemblyInfo.cs b/MediaBrowser.Common/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..53e9df0f6 --- /dev/null +++ b/MediaBrowser.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Common")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Common")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("cdec1bb7-6ffd-409f-b41f-0524a73df9be")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.Common/packages.config b/MediaBrowser.Common/packages.config new file mode 100644 index 000000000..9bfda3802 --- /dev/null +++ b/MediaBrowser.Common/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Newtonsoft.Json" version="4.5.7" targetFramework="net45" />
+</packages>
\ No newline at end of file diff --git a/MediaBrowser.Configuration/MediaBrowser.Configuration.csproj b/MediaBrowser.Configuration/MediaBrowser.Configuration.csproj new file mode 100644 index 000000000..356aa502f --- /dev/null +++ b/MediaBrowser.Configuration/MediaBrowser.Configuration.csproj @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{933CC468-E22B-48D8-8BCA-2E026F411CA2}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.Configuration</RootNamespace>
+ <AssemblyName>MediaBrowser.Configuration</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Reactive, Version=1.0.10621.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Rx-Main.1.0.11226\lib\Net4\System.Reactive.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Plugin.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{9b1ddd79-5134-4df3-ace3-d1957a7350d8}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <PropertyGroup>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>
+ </PropertyGroup>
+ <!-- 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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.Configuration/Plugin.cs b/MediaBrowser.Configuration/Plugin.cs new file mode 100644 index 000000000..a16ad064b --- /dev/null +++ b/MediaBrowser.Configuration/Plugin.cs @@ -0,0 +1,11 @@ +using MediaBrowser.Common.Plugins;
+
+namespace MediaBrowser.Configuration
+{
+ public class Plugin : BasePlugin<BasePluginConfiguration>
+ {
+ protected override void InitInternal()
+ {
+ }
+ }
+}
diff --git a/MediaBrowser.Configuration/Properties/AssemblyInfo.cs b/MediaBrowser.Configuration/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5212f26db --- /dev/null +++ b/MediaBrowser.Configuration/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Configuration")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Configuration")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("c109d2b1-2368-43a2-bed1-ec2cfb33e741")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.Configuration/packages.config b/MediaBrowser.Configuration/packages.config new file mode 100644 index 000000000..47102a263 --- /dev/null +++ b/MediaBrowser.Configuration/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Rx-Main" version="1.0.11226" targetFramework="net45" />
+</packages>
\ No newline at end of file diff --git a/MediaBrowser.Controller/Events/ItemResolveEventArgs.cs b/MediaBrowser.Controller/Events/ItemResolveEventArgs.cs new file mode 100644 index 000000000..831eb29d4 --- /dev/null +++ b/MediaBrowser.Controller/Events/ItemResolveEventArgs.cs @@ -0,0 +1,94 @@ +using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Controller.Events
+{
+ public class ItemResolveEventArgs : PreBeginResolveEventArgs
+ {
+ public IEnumerable<KeyValuePair<string, FileAttributes>> FileSystemChildren { get; set; }
+
+ public KeyValuePair<string, FileAttributes>? GetFolderByName(string name)
+ {
+ foreach (KeyValuePair<string, FileAttributes> entry in FileSystemChildren)
+ {
+ if (!entry.Value.HasFlag(FileAttributes.Directory))
+ {
+ continue;
+ }
+
+ if (System.IO.Path.GetFileName(entry.Key).Equals(name, StringComparison.OrdinalIgnoreCase))
+ {
+ return entry;
+ }
+ }
+
+ return null;
+ }
+
+ public KeyValuePair<string, FileAttributes>? GetFileByName(string name)
+ {
+ foreach (KeyValuePair<string, FileAttributes> entry in FileSystemChildren)
+ {
+ if (entry.Value.HasFlag(FileAttributes.Directory))
+ {
+ continue;
+ }
+
+ if (System.IO.Path.GetFileName(entry.Key).Equals(name, StringComparison.OrdinalIgnoreCase))
+ {
+ return entry;
+ }
+ }
+
+ return null;
+ }
+
+ public bool ContainsFile(string name)
+ {
+ return GetFileByName(name) != null;
+ }
+
+ public bool ContainsFolder(string name)
+ {
+ return GetFolderByName(name) != null;
+ }
+ }
+
+ public class PreBeginResolveEventArgs : EventArgs
+ {
+ public string Path { get; set; }
+ public BaseItem Parent { get; set; }
+
+ public bool Cancel { get; set; }
+
+ public FileAttributes FileAttributes { get; set; }
+
+ public bool IsFolder
+ {
+ get
+ {
+ return FileAttributes.HasFlag(FileAttributes.Directory);
+ }
+ }
+
+ public bool IsHidden
+ {
+ get
+ {
+ return FileAttributes.HasFlag(FileAttributes.Hidden);
+ }
+ }
+
+ public bool IsSystemFile
+ {
+ get
+ {
+ return FileAttributes.HasFlag(FileAttributes.System);
+ }
+ }
+
+ }
+}
diff --git a/MediaBrowser.Controller/IO/DirectoryWatchers.cs b/MediaBrowser.Controller/IO/DirectoryWatchers.cs new file mode 100644 index 000000000..dd2769583 --- /dev/null +++ b/MediaBrowser.Controller/IO/DirectoryWatchers.cs @@ -0,0 +1,152 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.IO
+{
+ public class DirectoryWatchers
+ {
+ private List<FileSystemWatcher> FileSystemWatchers = new List<FileSystemWatcher>();
+ private Timer updateTimer = null;
+ private List<string> affectedPaths = new List<string>();
+
+ private const int TimerDelayInSeconds = 5;
+
+ public void Start()
+ {
+ List<string> pathsToWatch = new List<string>();
+
+ var rootFolder = Kernel.Instance.RootFolder;
+
+ pathsToWatch.Add(rootFolder.Path);
+
+ foreach (Folder folder in rootFolder.FolderChildren)
+ {
+ foreach (Folder subFolder in folder.FolderChildren)
+ {
+ if (Path.IsPathRooted(subFolder.Path))
+ {
+ string parent = Path.GetDirectoryName(subFolder.Path);
+
+ if (!pathsToWatch.Contains(parent))
+ {
+ pathsToWatch.Add(parent);
+ }
+ }
+ }
+ }
+
+ foreach (string path in pathsToWatch)
+ {
+ FileSystemWatcher watcher = new FileSystemWatcher(path, "*");
+
+ watcher.IncludeSubdirectories = true;
+
+ watcher.Changed += watcher_Changed;
+
+ // All the others seem to trigger change events on the parent, so let's keep it simple for now.
+ //watcher.Created += watcher_Changed;
+ //watcher.Deleted += watcher_Changed;
+ //watcher.Renamed += watcher_Changed;
+
+ watcher.EnableRaisingEvents = true;
+ FileSystemWatchers.Add(watcher);
+ }
+ }
+
+ void watcher_Changed(object sender, FileSystemEventArgs e)
+ {
+ if (!affectedPaths.Contains(e.FullPath))
+ {
+ affectedPaths.Add(e.FullPath);
+ }
+
+ if (updateTimer == null)
+ {
+ updateTimer = new Timer(TimerStopped, null, TimeSpan.FromSeconds(TimerDelayInSeconds), TimeSpan.FromMilliseconds(-1));
+ }
+ else
+ {
+ updateTimer.Change(TimeSpan.FromSeconds(TimerDelayInSeconds), TimeSpan.FromMilliseconds(-1));
+ }
+ }
+
+ private void TimerStopped(object stateInfo)
+ {
+ updateTimer.Dispose();
+ updateTimer = null;
+
+ List<string> paths = affectedPaths;
+ affectedPaths = new List<string>();
+
+ ProcessPathChanges(paths);
+ }
+
+ private void ProcessPathChanges(IEnumerable<string> paths)
+ {
+ List<BaseItem> itemsToRefresh = new List<BaseItem>();
+
+ foreach (BaseItem item in paths.Select(p => GetAffectedBaseItem(p)))
+ {
+ if (item != null && !itemsToRefresh.Contains(item))
+ {
+ itemsToRefresh.Add(item);
+ }
+ }
+
+ if (itemsToRefresh.Any(i =>
+ {
+ var folder = i as Folder;
+
+ return folder != null && folder.IsRoot;
+ }))
+ {
+ Kernel.Instance.ReloadRoot();
+ }
+ else
+ {
+ Parallel.For(0, itemsToRefresh.Count, i =>
+ {
+ Kernel.Instance.ReloadItem(itemsToRefresh[i]);
+ });
+ }
+ }
+
+ private BaseItem GetAffectedBaseItem(string path)
+ {
+ BaseItem item = null;
+
+ while (item == null)
+ {
+ item = Kernel.Instance.RootFolder.FindByPath(path);
+
+ path = Path.GetDirectoryName(path);
+ }
+
+ return item;
+ }
+
+ public void Stop()
+ {
+ foreach (FileSystemWatcher watcher in FileSystemWatchers)
+ {
+ watcher.Changed -= watcher_Changed;
+ watcher.EnableRaisingEvents = false;
+ watcher.Dispose();
+ }
+
+ if (updateTimer != null)
+ {
+ updateTimer.Dispose();
+ updateTimer = null;
+ }
+
+ FileSystemWatchers.Clear();
+ affectedPaths.Clear();
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/IO/Shortcut.cs b/MediaBrowser.Controller/IO/Shortcut.cs new file mode 100644 index 000000000..376d16a79 --- /dev/null +++ b/MediaBrowser.Controller/IO/Shortcut.cs @@ -0,0 +1,182 @@ +using System;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace MediaBrowser.Controller.IO
+{
+ public static class Shortcut
+ {
+ #region Signitures were imported from http://pinvoke.net
+ [Flags()]
+ enum SLGP_FLAGS
+ {
+ /// <summary>Retrieves the standard short (8.3 format) file name</summary>
+ SLGP_SHORTPATH = 0x1,
+ /// <summary>Retrieves the Universal Naming Convention (UNC) path name of the file</summary>
+ SLGP_UNCPRIORITY = 0x2,
+ /// <summary>Retrieves the raw path name. A raw path is something that might not exist and may include environment variables that need to be expanded</summary>
+ SLGP_RAWPATH = 0x4
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ struct WIN32_FIND_DATAW
+ {
+ public uint dwFileAttributes;
+ public long ftCreationTime;
+ public long ftLastAccessTime;
+ public long ftLastWriteTime;
+ public uint nFileSizeHigh;
+ public uint nFileSizeLow;
+ public uint dwReserved0;
+ public uint dwReserved1;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
+ public string cFileName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
+ public string cAlternateFileName;
+ }
+
+ [Flags()]
+
+ enum SLR_FLAGS
+ {
+ /// <summary>
+ /// Do not display a dialog box if the link cannot be resolved. When SLR_NO_UI is set,
+ /// the high-order word of fFlags can be set to a time-out value that specifies the
+ /// maximum amount of time to be spent resolving the link. The function returns if the
+ /// link cannot be resolved within the time-out duration. If the high-order word is set
+ /// to zero, the time-out duration will be set to the default value of 3,000 milliseconds
+ /// (3 seconds). To specify a value, set the high word of fFlags to the desired time-out
+ /// duration, in milliseconds.
+ /// </summary>
+ SLR_NO_UI = 0x1,
+ /// <summary>Obsolete and no longer used</summary>
+ SLR_ANY_MATCH = 0x2,
+ /// <summary>If the link object has changed, update its path and list of identifiers.
+ /// If SLR_UPDATE is set, you do not need to call IPersistFile::IsDirty to determine
+ /// whether or not the link object has changed.</summary>
+ SLR_UPDATE = 0x4,
+ /// <summary>Do not update the link information</summary>
+ SLR_NOUPDATE = 0x8,
+ /// <summary>Do not execute the search heuristics</summary>
+ SLR_NOSEARCH = 0x10,
+ /// <summary>Do not use distributed link tracking</summary>
+ SLR_NOTRACK = 0x20,
+ /// <summary>Disable distributed link tracking. By default, distributed link tracking tracks
+ /// removable media across multiple devices based on the volume name. It also uses the
+ /// Universal Naming Convention (UNC) path to track remote file systems whose drive letter
+ /// has changed. Setting SLR_NOLINKINFO disables both types of tracking.</summary>
+ SLR_NOLINKINFO = 0x40,
+ /// <summary>Call the Microsoft Windows Installer</summary>
+ SLR_INVOKE_MSI = 0x80
+ }
+
+
+ /// <summary>The IShellLink interface allows Shell links to be created, modified, and resolved</summary>
+ [ComImport(), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("000214F9-0000-0000-C000-000000000046")]
+ interface IShellLinkW
+ {
+ /// <summary>Retrieves the path and file name of a Shell link object</summary>
+ void GetPath([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
+ /// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
+ void GetIDList(out IntPtr ppidl);
+ /// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
+ void SetIDList(IntPtr pidl);
+ /// <summary>Retrieves the description string for a Shell link object</summary>
+ void GetDescription([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
+ /// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
+ void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
+ /// <summary>Retrieves the name of the working directory for a Shell link object</summary>
+ void GetWorkingDirectory([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
+ /// <summary>Sets the name of the working directory for a Shell link object</summary>
+ void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
+ /// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
+ void GetArguments([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
+ /// <summary>Sets the command-line arguments for a Shell link object</summary>
+ void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
+ /// <summary>Retrieves the hot key for a Shell link object</summary>
+ void GetHotkey(out short pwHotkey);
+ /// <summary>Sets a hot key for a Shell link object</summary>
+ void SetHotkey(short wHotkey);
+ /// <summary>Retrieves the show command for a Shell link object</summary>
+ void GetShowCmd(out int piShowCmd);
+ /// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
+ void SetShowCmd(int iShowCmd);
+ /// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
+ void GetIconLocation([Out(), MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath,
+ int cchIconPath, out int piIcon);
+ /// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
+ void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
+ /// <summary>Sets the relative path to the Shell link object</summary>
+ void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
+ /// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
+ void Resolve(IntPtr hwnd, SLR_FLAGS fFlags);
+ /// <summary>Sets the path and file name of a Shell link object</summary>
+ void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
+
+ }
+
+ [ComImport, Guid("0000010c-0000-0000-c000-000000000046"),
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IPersist
+ {
+ [PreserveSig]
+ void GetClassID(out Guid pClassID);
+ }
+
+
+ [ComImport, Guid("0000010b-0000-0000-C000-000000000046"),
+ InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ public interface IPersistFile : IPersist
+ {
+ new void GetClassID(out Guid pClassID);
+ [PreserveSig]
+ int IsDirty();
+
+ [PreserveSig]
+ void Load([In, MarshalAs(UnmanagedType.LPWStr)]
+ string pszFileName, uint dwMode);
+
+ [PreserveSig]
+ void Save([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName,
+ [In, MarshalAs(UnmanagedType.Bool)] bool remember);
+
+ [PreserveSig]
+ void SaveCompleted([In, MarshalAs(UnmanagedType.LPWStr)] string pszFileName);
+
+ [PreserveSig]
+ void GetCurFile([In, MarshalAs(UnmanagedType.LPWStr)] string ppszFileName);
+ }
+
+ const uint STGM_READ = 0;
+ const int MAX_PATH = 260;
+
+ // CLSID_ShellLink from ShlGuid.h
+ [
+ ComImport(),
+ Guid("00021401-0000-0000-C000-000000000046")
+ ]
+ public class ShellLink
+ {
+ }
+
+ #endregion
+
+ public static string ResolveShortcut(string filename)
+ {
+ ShellLink link = new ShellLink();
+ ((IPersistFile)link).Load(filename, STGM_READ);
+ // TODO: if I can get hold of the hwnd call resolve first. This handles moved and renamed files.
+ // ((IShellLinkW)link).Resolve(hwnd, 0)
+ StringBuilder sb = new StringBuilder(MAX_PATH);
+ WIN32_FIND_DATAW data = new WIN32_FIND_DATAW();
+ ((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0);
+ return sb.ToString();
+ }
+
+ public static bool IsShortcut(string filename)
+ {
+ return Path.GetExtension(filename).EndsWith("lnk", StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Kernel.cs b/MediaBrowser.Controller/Kernel.cs new file mode 100644 index 000000000..2bb78e7e7 --- /dev/null +++ b/MediaBrowser.Controller/Kernel.cs @@ -0,0 +1,258 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Cryptography;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Common.Logging;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Users;
+
+namespace MediaBrowser.Controller
+{
+ public class Kernel
+ {
+ public static Kernel Instance { get; private set; }
+
+ public string DataPath { get; private set; }
+
+ public HttpServer HttpServer { get; private set; }
+ public ItemDataCache ItemDataCache { get; private set; }
+ public ItemController ItemController { get; private set; }
+ public UserController UserController { get; private set; }
+ public PluginController PluginController { get; private set; }
+
+ public Configuration Configuration { get; private set; }
+ public IEnumerable<IPlugin> Plugins { get; private set; }
+ public IEnumerable<User> Users { get; private set; }
+ public Folder RootFolder { get; private set; }
+
+ private DirectoryWatchers DirectoryWatchers { get; set; }
+
+ private string MediaRootFolderPath
+ {
+ get
+ {
+ return Path.Combine(DataPath, "Root");
+ }
+ }
+
+ /// <summary>
+ /// Creates a kernal based on a Data path, which is akin to our current programdata path
+ /// </summary>
+ public Kernel(string dataPath)
+ {
+ Instance = this;
+
+ DataPath = dataPath;
+
+ Logger.LoggerInstance = new FileLogger(Path.Combine(DataPath, "Logs"));
+
+ ItemController = new ItemController();
+ UserController = new UserController(Path.Combine(DataPath, "Users"));
+ PluginController = new PluginController(Path.Combine(DataPath, "Plugins"));
+ DirectoryWatchers = new DirectoryWatchers();
+ ItemDataCache = new ItemDataCache();
+
+ ItemController.PreBeginResolvePath += ItemController_PreBeginResolvePath;
+ ItemController.BeginResolvePath += ItemController_BeginResolvePath;
+
+ // Add support for core media types - audio, video, etc
+ AddBaseItemType<Folder, FolderResolver>();
+ AddBaseItemType<Audio, AudioResolver>();
+ AddBaseItemType<Video, VideoResolver>();
+ }
+
+ /// <summary>
+ /// Tells the kernel to start spinning up
+ /// </summary>
+ public void Init()
+ {
+ ReloadConfiguration();
+
+ ReloadHttpServer();
+
+ ReloadPlugins();
+
+ // Get users from users folder
+ // Load root media folder
+ Parallel.Invoke(ReloadUsers, ReloadRoot);
+ var b = true;
+ }
+
+ private void ReloadConfiguration()
+ {
+ // Deserialize config
+ Configuration = GetConfiguration(DataPath);
+
+ Logger.LoggerInstance.LogSeverity = Configuration.LogSeverity;
+ }
+
+ private void ReloadPlugins()
+ {
+ if (Plugins != null)
+ {
+ Parallel.For(0, Plugins.Count(), i =>
+ {
+ Plugins.ElementAt(i).Dispose();
+ });
+ }
+
+ // Find plugins
+ Plugins = PluginController.GetAllPlugins();
+
+ Parallel.For(0, Plugins.Count(), i =>
+ {
+ Plugins.ElementAt(i).Init();
+ });
+ }
+
+ private void ReloadHttpServer()
+ {
+ if (HttpServer != null)
+ {
+ HttpServer.Dispose();
+ }
+
+ HttpServer = new HttpServer(Configuration.HttpServerPortNumber);
+ }
+
+ /// <summary>
+ /// Registers a new BaseItem subclass
+ /// </summary>
+ public void AddBaseItemType<TBaseItemType, TResolverType>()
+ where TBaseItemType : BaseItem, new()
+ where TResolverType : BaseItemResolver<TBaseItemType>, new()
+ {
+ ItemController.AddResovler<TBaseItemType, TResolverType>();
+ }
+
+ /// <summary>
+ /// Unregisters a new BaseItem subclass
+ /// </summary>
+ public void RemoveBaseItemType<TBaseItemType, TResolverType>()
+ where TBaseItemType : BaseItem, new()
+ where TResolverType : BaseItemResolver<TBaseItemType>, new()
+ {
+ ItemController.RemoveResovler<TBaseItemType, TResolverType>();
+ }
+
+ /// <summary>
+ /// Fires when a path is about to be resolved, but before child folders and files
+ /// have been collected from the file system.
+ /// This gives us a chance to cancel it if needed, resulting in the path being ignored
+ /// </summary>
+ void ItemController_PreBeginResolvePath(object sender, PreBeginResolveEventArgs e)
+ {
+ if (e.IsHidden || e.IsSystemFile)
+ {
+ // Ignore hidden files and folders
+ e.Cancel = true;
+ }
+
+ else if (Path.GetFileName(e.Path).Equals("trailers", StringComparison.OrdinalIgnoreCase))
+ {
+ // Ignore any folders named "trailers"
+ e.Cancel = true;
+ }
+ }
+
+ /// <summary>
+ /// Fires when a path is about to be resolved, but after child folders and files
+ /// This gives us a chance to cancel it if needed, resulting in the path being ignored
+ /// </summary>
+ void ItemController_BeginResolvePath(object sender, ItemResolveEventArgs e)
+ {
+ if (e.IsFolder)
+ {
+ if (e.ContainsFile(".ignore"))
+ {
+ // Ignore any folders containing a file called .ignore
+ e.Cancel = true;
+ }
+ }
+ }
+
+ private void ReloadUsers()
+ {
+ Users = UserController.GetAllUsers();
+ }
+
+ /// <summary>
+ /// Reloads the root media folder
+ /// </summary>
+ public void ReloadRoot()
+ {
+ if (!Directory.Exists(MediaRootFolderPath))
+ {
+ Directory.CreateDirectory(MediaRootFolderPath);
+ }
+
+ DirectoryWatchers.Stop();
+
+ RootFolder = ItemController.GetItem(MediaRootFolderPath) as Folder;
+
+ DirectoryWatchers.Start();
+ }
+
+ private static MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
+ public static Guid GetMD5(string str)
+ {
+ lock (md5Provider)
+ {
+ return new Guid(md5Provider.ComputeHash(Encoding.Unicode.GetBytes(str)));
+ }
+ }
+
+ private static Configuration GetConfiguration(string directory)
+ {
+ string file = Path.Combine(directory, "config.js");
+
+ if (!File.Exists(file))
+ {
+ return new Configuration();
+ }
+
+ return JsonSerializer.Deserialize<Configuration>(file);
+ }
+
+ public void ReloadItem(BaseItem item)
+ {
+ Folder folder = item as Folder;
+
+ if (folder != null && folder.IsRoot)
+ {
+ ReloadRoot();
+ }
+ else
+ {
+ if (!Directory.Exists(item.Path) && !File.Exists(item.Path))
+ {
+ ReloadItem(item.Parent);
+ return;
+ }
+
+ BaseItem newItem = ItemController.GetItem(item.Parent, item.Path);
+
+ List<BaseItem> children = item.Parent.Children.ToList();
+
+ int index = children.IndexOf(item);
+
+ children.RemoveAt(index);
+
+ children.Insert(index, newItem);
+
+ item.Parent.Children = children.ToArray();
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Library/ItemController.cs b/MediaBrowser.Controller/Library/ItemController.cs new file mode 100644 index 000000000..422790c69 --- /dev/null +++ b/MediaBrowser.Controller/Library/ItemController.cs @@ -0,0 +1,326 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using MediaBrowser.Common.Events;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Library
+{
+ public class ItemController
+ {
+ private List<IBaseItemResolver> Resolvers = new List<IBaseItemResolver>();
+
+ /// <summary>
+ /// Registers a new BaseItem resolver.
+ /// </summary>
+ public void AddResovler<TBaseItemType, TResolverType>()
+ where TBaseItemType : BaseItem, new()
+ where TResolverType : BaseItemResolver<TBaseItemType>, new()
+ {
+ Resolvers.Insert(0, new TResolverType());
+ }
+
+ /// <summary>
+ /// Registers a new BaseItem resolver.
+ /// </summary>
+ public void RemoveResovler<TBaseItemType, TResolverType>()
+ where TBaseItemType : BaseItem, new()
+ where TResolverType : BaseItemResolver<TBaseItemType>, new()
+ {
+ IBaseItemResolver resolver = Resolvers.First(r => r.GetType() == typeof(TResolverType));
+
+ Resolvers.Remove(resolver);
+ }
+
+ #region PreBeginResolvePath Event
+ /// <summary>
+ /// Fires when a path is about to be resolved, but before child folders and files
+ /// have been collected from the file system.
+ /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
+ /// </summary>
+ public event EventHandler<PreBeginResolveEventArgs> PreBeginResolvePath;
+ private bool OnPreBeginResolvePath(Folder parent, string path, FileAttributes attributes)
+ {
+ PreBeginResolveEventArgs args = new PreBeginResolveEventArgs()
+ {
+ Path = path,
+ Parent = parent,
+ FileAttributes = attributes,
+ Cancel = false
+ };
+
+ if (PreBeginResolvePath != null)
+ {
+ PreBeginResolvePath(this, args);
+ }
+
+ return !args.Cancel;
+ }
+ #endregion
+
+ #region BeginResolvePath Event
+ /// <summary>
+ /// Fires when a path is about to be resolved, but after child folders and files
+ /// have been collected from the file system.
+ /// This gives listeners a chance to cancel the operation and cause the path to be ignored.
+ /// </summary>
+ public event EventHandler<ItemResolveEventArgs> BeginResolvePath;
+ private bool OnBeginResolvePath(ItemResolveEventArgs args)
+ {
+ if (BeginResolvePath != null)
+ {
+ BeginResolvePath(this, args);
+ }
+
+ return !args.Cancel;
+ }
+ #endregion
+
+ #region Item Events
+ /// <summary>
+ /// Called when an item is being created.
+ /// This should be used to fill item values, such as metadata
+ /// </summary>
+ public event EventHandler<GenericItemEventArgs<BaseItem>> ItemCreating;
+
+ /// <summary>
+ /// Called when an item has been created.
+ /// This should be used to process or modify item values.
+ /// </summary>
+ public event EventHandler<GenericItemEventArgs<BaseItem>> ItemCreated;
+ #endregion
+
+ /// <summary>
+ /// Called when an item has been created
+ /// </summary>
+ private void OnItemCreated(BaseItem item, Folder parent)
+ {
+ GenericItemEventArgs<BaseItem> args = new GenericItemEventArgs<BaseItem> { Item = item };
+
+ if (ItemCreating != null)
+ {
+ ItemCreating(this, args);
+ }
+
+ if (ItemCreated != null)
+ {
+ ItemCreated(this, args);
+ }
+ }
+
+ private void FireCreateEventsRecursive(Folder folder, Folder parent)
+ {
+ OnItemCreated(folder, parent);
+
+ int count = folder.Children.Length;
+
+ Parallel.For(0, count, i =>
+ {
+ BaseItem item = folder.Children[i];
+
+ Folder childFolder = item as Folder;
+
+ if (childFolder != null)
+ {
+ FireCreateEventsRecursive(childFolder, folder);
+ }
+ else
+ {
+ OnItemCreated(item, folder);
+ }
+ });
+ }
+
+ private BaseItem ResolveItem(ItemResolveEventArgs args)
+ {
+ // If that didn't pan out, try the slow ones
+ foreach (IBaseItemResolver resolver in Resolvers)
+ {
+ var item = resolver.ResolvePath(args);
+
+ if (item != null)
+ {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Resolves a path into a BaseItem
+ /// </summary>
+ public BaseItem GetItem(string path)
+ {
+ return GetItem(null, path);
+ }
+
+ /// <summary>
+ /// Resolves a path into a BaseItem
+ /// </summary>
+ public BaseItem GetItem(Folder parent, string path)
+ {
+ BaseItem item = GetItemInternal(parent, path, File.GetAttributes(path));
+
+ if (item != null)
+ {
+ var folder = item as Folder;
+
+ if (folder != null)
+ {
+ FireCreateEventsRecursive(folder, parent);
+ }
+ else
+ {
+ OnItemCreated(item, parent);
+ }
+ }
+
+ return item;
+ }
+
+ /// <summary>
+ /// Resolves a path into a BaseItem
+ /// </summary>
+ private BaseItem GetItemInternal(Folder parent, string path, FileAttributes attributes)
+ {
+ if (!OnPreBeginResolvePath(parent, path, attributes))
+ {
+ return null;
+ }
+
+ IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren;
+
+ // Gather child folder and files
+ if (attributes.HasFlag(FileAttributes.Directory))
+ {
+ fileSystemChildren = Directory.GetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair<string, FileAttributes>(f, File.GetAttributes(f)));
+
+ bool isVirtualFolder = parent != null && parent.IsRoot;
+ fileSystemChildren = FilterChildFileSystemEntries(fileSystemChildren, isVirtualFolder);
+ }
+ else
+ {
+ fileSystemChildren = new KeyValuePair<string, FileAttributes>[] { };
+ }
+
+ ItemResolveEventArgs args = new ItemResolveEventArgs()
+ {
+ Path = path,
+ FileAttributes = attributes,
+ FileSystemChildren = fileSystemChildren,
+ Parent = parent,
+ Cancel = false
+ };
+
+ // Fire BeginResolvePath to see if anyone wants to cancel this operation
+ if (!OnBeginResolvePath(args))
+ {
+ return null;
+ }
+
+ BaseItem item = ResolveItem(args);
+
+ var folder = item as Folder;
+
+ if (folder != null)
+ {
+ // If it's a folder look for child entities
+ AttachChildren(folder, fileSystemChildren);
+ }
+
+ return item;
+ }
+
+ /// <summary>
+ /// Finds child BaseItems for a given Folder
+ /// </summary>
+ private void AttachChildren(Folder folder, IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren)
+ {
+ List<BaseItem> baseItemChildren = new List<BaseItem>();
+
+ int count = fileSystemChildren.Count();
+
+ // Resolve the child folder paths into entities
+ Parallel.For(0, count, i =>
+ {
+ KeyValuePair<string, FileAttributes> child = fileSystemChildren.ElementAt(i);
+
+ BaseItem item = GetItemInternal(folder, child.Key, child.Value);
+
+ if (item != null)
+ {
+ lock (baseItemChildren)
+ {
+ baseItemChildren.Add(item);
+ }
+ }
+ });
+
+ // Sort them
+ folder.Children = baseItemChildren.OrderBy(f =>
+ {
+ return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
+
+ }).ToArray();
+ }
+
+ /// <summary>
+ /// Transforms shortcuts into their actual paths
+ /// </summary>
+ private List<KeyValuePair<string, FileAttributes>> FilterChildFileSystemEntries(IEnumerable<KeyValuePair<string, FileAttributes>> fileSystemChildren, bool flattenShortcuts)
+ {
+ List<KeyValuePair<string, FileAttributes>> returnFiles = new List<KeyValuePair<string, FileAttributes>>();
+
+ // Loop through each file
+ foreach (KeyValuePair<string, FileAttributes> file in fileSystemChildren)
+ {
+ // Folders
+ if (file.Value.HasFlag(FileAttributes.Directory))
+ {
+ returnFiles.Add(file);
+ }
+
+ // If it's a shortcut, resolve it
+ else if (Shortcut.IsShortcut(file.Key))
+ {
+ string newPath = Shortcut.ResolveShortcut(file.Key);
+ FileAttributes newPathAttributes = File.GetAttributes(newPath);
+
+ // Find out if the shortcut is pointing to a directory or file
+
+ if (newPathAttributes.HasFlag(FileAttributes.Directory))
+ {
+ // If we're flattening then get the shortcut's children
+
+ if (flattenShortcuts)
+ {
+ IEnumerable<KeyValuePair<string, FileAttributes>> newChildren = Directory.GetFileSystemEntries(newPath, "*", SearchOption.TopDirectoryOnly).Select(f => new KeyValuePair<string, FileAttributes>(f, File.GetAttributes(f)));
+
+ returnFiles.AddRange(FilterChildFileSystemEntries(newChildren, false));
+ }
+ else
+ {
+ returnFiles.Add(new KeyValuePair<string, FileAttributes>(newPath, newPathAttributes));
+ }
+ }
+ else
+ {
+ returnFiles.Add(new KeyValuePair<string, FileAttributes>(newPath, newPathAttributes));
+ }
+ }
+ else
+ {
+ returnFiles.Add(file);
+ }
+ }
+
+ return returnFiles;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Library/ItemDataCache.cs b/MediaBrowser.Controller/Library/ItemDataCache.cs new file mode 100644 index 000000000..35b3551a9 --- /dev/null +++ b/MediaBrowser.Controller/Library/ItemDataCache.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Library
+{
+ public class ItemDataCache
+ {
+ private Dictionary<string, object> Data = new Dictionary<string, object>();
+
+ public void SetValue<T>(BaseItem item, string propertyName, T value)
+ {
+ Data[GetKey(item, propertyName)] = value;
+ }
+
+ public T GetValue<T>(BaseItem item, string propertyName)
+ {
+ string key = GetKey(item, propertyName);
+
+ if (Data.ContainsKey(key))
+ {
+ return (T)Data[key];
+ }
+
+ return default(T);
+ }
+
+ private string GetKey(BaseItem item, string propertyName)
+ {
+ return item.Id.ToString() + "-" + propertyName;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj new file mode 100644 index 000000000..a84fc8091 --- /dev/null +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.Controller</RootNamespace>
+ <AssemblyName>MediaBrowser.Controller</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Reactive">
+ <HintPath>..\packages\Rx-Main.1.0.11226\lib\Net4\System.Reactive.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Events\ItemResolveEventArgs.cs" />
+ <Compile Include="IO\DirectoryWatchers.cs" />
+ <Compile Include="IO\Shortcut.cs" />
+ <Compile Include="Library\ItemController.cs" />
+ <Compile Include="Kernel.cs" />
+ <Compile Include="Library\ItemDataCache.cs" />
+ <Compile Include="Net\CollectionExtensions.cs" />
+ <Compile Include="Net\HttpServer.cs" />
+ <Compile Include="Net\Request.cs" />
+ <Compile Include="Net\RequestContext.cs" />
+ <Compile Include="Net\Response.cs" />
+ <Compile Include="Net\StreamExtensions.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Resolvers\AudioResolver.cs" />
+ <Compile Include="Resolvers\BaseItemResolver.cs" />
+ <Compile Include="Resolvers\FolderResolver.cs" />
+ <Compile Include="Resolvers\VideoResolver.cs" />
+ <Compile Include="UserController.cs" />
+ <Compile Include="Xml\BaseItemXmlParser.cs" />
+ <Compile Include="Xml\FolderXmlParser.cs" />
+ <Compile Include="Xml\XmlExtensions.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{9b1ddd79-5134-4df3-ace3-d1957a7350d8}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.Controller/Net/CollectionExtensions.cs b/MediaBrowser.Controller/Net/CollectionExtensions.cs new file mode 100644 index 000000000..137fbe50b --- /dev/null +++ b/MediaBrowser.Controller/Net/CollectionExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+
+namespace MediaBrowser.Controller.Net
+{
+ public static class CollectionExtensions
+ {
+ public static IDictionary<string, IEnumerable<string>> ToDictionary(this NameValueCollection source)
+ {
+ return source.AllKeys.ToDictionary<string, string, IEnumerable<string>>(key => key, source.GetValues);
+ }
+ }
+}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Net/HttpServer.cs b/MediaBrowser.Controller/Net/HttpServer.cs new file mode 100644 index 000000000..bb014ca5a --- /dev/null +++ b/MediaBrowser.Controller/Net/HttpServer.cs @@ -0,0 +1,47 @@ +using System;
+using System.Net;
+using System.Reactive.Linq;
+
+namespace MediaBrowser.Controller.Net
+{
+ public class HttpServer : IObservable<RequestContext>, IDisposable
+ {
+ private readonly HttpListener listener;
+ private readonly IObservable<RequestContext> stream;
+
+ public HttpServer(int port)
+ : this("http://+:" + port + "/")
+ {
+ }
+
+ public HttpServer(string url)
+ {
+ listener = new HttpListener();
+ listener.Prefixes.Add(url);
+ listener.Start();
+ stream = ObservableHttpContext();
+ }
+
+ private IObservable<RequestContext> ObservableHttpContext()
+ {
+ return Observable.Create<RequestContext>(obs =>
+ Observable.FromAsyncPattern<HttpListenerContext>(listener.BeginGetContext,
+ listener.EndGetContext)()
+ .Select(c => new RequestContext(c))
+ .Subscribe(obs))
+ .Repeat()
+ .Retry()
+ .Publish()
+ .RefCount();
+ }
+ public void Dispose()
+ {
+ listener.Stop();
+ }
+
+ public IDisposable Subscribe(IObserver<RequestContext> observer)
+ {
+ return stream.Subscribe(observer);
+ }
+ }
+}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Net/Request.cs b/MediaBrowser.Controller/Net/Request.cs new file mode 100644 index 000000000..751c1e384 --- /dev/null +++ b/MediaBrowser.Controller/Net/Request.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Controller.Net
+{
+ public class Request
+ {
+ public string HttpMethod { get; set; }
+ public IDictionary<string, IEnumerable<string>> Headers { get; set; }
+ public Stream InputStream { get; set; }
+ public string RawUrl { get; set; }
+ public int ContentLength
+ {
+ get { return int.Parse(Headers["Content-Length"].First()); }
+ }
+ }
+}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Net/RequestContext.cs b/MediaBrowser.Controller/Net/RequestContext.cs new file mode 100644 index 000000000..531faab84 --- /dev/null +++ b/MediaBrowser.Controller/Net/RequestContext.cs @@ -0,0 +1,37 @@ +using System.Linq;
+using System.Net;
+using System.IO.Compression;
+
+namespace MediaBrowser.Controller.Net
+{
+ public class RequestContext
+ {
+ public HttpListenerRequest Request { get; private set; }
+ public HttpListenerResponse Response { get; private set; }
+
+ public RequestContext(HttpListenerContext context)
+ {
+ Response = context.Response;
+ Request = context.Request;
+ }
+
+ public void Respond(Response response)
+ {
+ Response.AddHeader("Access-Control-Allow-Origin", "*");
+
+ foreach (var header in response.Headers)
+ {
+ Response.AddHeader(header.Key, header.Value);
+ }
+
+ Response.ContentType = response.ContentType;
+ Response.StatusCode = response.StatusCode;
+
+ Response.SendChunked = true;
+
+ GZipStream gzipStream = new GZipStream(Response.OutputStream, CompressionMode.Compress, false);
+
+ response.WriteStream(Response.OutputStream);
+ }
+ }
+}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Net/Response.cs b/MediaBrowser.Controller/Net/Response.cs new file mode 100644 index 000000000..a119198cb --- /dev/null +++ b/MediaBrowser.Controller/Net/Response.cs @@ -0,0 +1,49 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace MediaBrowser.Controller.Net
+{
+ public class Response
+ {
+ protected RequestContext RequestContext { get; private set; }
+
+ public Response(RequestContext ctx)
+ {
+ RequestContext = ctx;
+
+ WriteStream = s => { };
+ StatusCode = 200;
+ Headers = new Dictionary<string, string>();
+ CacheDuration = TimeSpan.FromTicks(0);
+ ContentType = "text/html";
+ }
+
+ public int StatusCode { get; set; }
+ public string ContentType { get; set; }
+ public IDictionary<string, string> Headers { get; set; }
+ public TimeSpan CacheDuration { get; set; }
+ public Action<Stream> WriteStream { get; set; }
+ }
+
+ /*public class ByteResponse : Response
+ {
+ public ByteResponse(byte[] bytes)
+ {
+ WriteStream = async s =>
+ {
+ await s.WriteAsync(bytes, 0, bytes.Length);
+ s.Close();
+ };
+ }
+ }
+
+ public class StringResponse : ByteResponse
+ {
+ public StringResponse(string message)
+ : base(Encoding.UTF8.GetBytes(message))
+ {
+ }
+ }*/
+}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Net/StreamExtensions.cs b/MediaBrowser.Controller/Net/StreamExtensions.cs new file mode 100644 index 000000000..451a43acb --- /dev/null +++ b/MediaBrowser.Controller/Net/StreamExtensions.cs @@ -0,0 +1,20 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reactive.Linq;
+
+namespace MediaBrowser.Controller.Net
+{
+ public static class StreamExtensions
+ {
+ public static IObservable<byte[]> ReadBytes(this Stream stream, int count)
+ {
+ var buffer = new byte[count];
+ return Observable.FromAsyncPattern((cb, state) => stream.BeginRead(buffer, 0, count, cb, state), ar =>
+ {
+ stream.EndRead(ar);
+ return buffer;
+ })();
+ }
+ }
+}
\ No newline at end of file diff --git a/MediaBrowser.Controller/Properties/AssemblyInfo.cs b/MediaBrowser.Controller/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..350165b1c --- /dev/null +++ b/MediaBrowser.Controller/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Controller")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Controller")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("bc09905a-04ed-497d-b39b-27593401e715")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.Controller/Resolvers/AudioResolver.cs b/MediaBrowser.Controller/Resolvers/AudioResolver.cs new file mode 100644 index 000000000..f9ce5ecd7 --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/AudioResolver.cs @@ -0,0 +1,44 @@ +using System.IO;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Resolvers
+{
+ public class AudioResolver : BaseItemResolver<Audio>
+ {
+ protected override Audio Resolve(ItemResolveEventArgs args)
+ {
+ if (!args.IsFolder)
+ {
+ if (IsAudioFile(args.Path))
+ {
+ return new Audio();
+ }
+ }
+
+ return null;
+ }
+
+ private static bool IsAudioFile(string path)
+ {
+ string extension = Path.GetExtension(path).ToLower();
+
+ switch (extension)
+ {
+ case ".mp3":
+ case ".wma":
+ case ".acc":
+ case ".flac":
+ case ".m4a":
+ case ".m4b":
+ case ".wav":
+ case ".ape":
+ return true;
+
+ default:
+ return false;
+ }
+
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs new file mode 100644 index 000000000..ed815037e --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/BaseItemResolver.cs @@ -0,0 +1,146 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Resolvers
+{
+ public abstract class BaseItemResolver<T> : IBaseItemResolver
+ where T : BaseItem, new ()
+ {
+ protected virtual T Resolve(ItemResolveEventArgs args)
+ {
+ return null;
+ }
+
+ protected virtual void SetItemValues(T item, ItemResolveEventArgs args)
+ {
+ // If the subclass didn't specify this
+ if (string.IsNullOrEmpty(item.Path))
+ {
+ item.Path = args.Path;
+ }
+
+ Folder parentFolder = args.Parent as Folder;
+
+ if (parentFolder != null)
+ {
+ item.Parent = parentFolder;
+ }
+
+ item.Id = Kernel.GetMD5(item.Path);
+
+ PopulateImages(item, args);
+ PopulateLocalTrailers(item, args);
+ }
+
+ public BaseItem ResolvePath(ItemResolveEventArgs args)
+ {
+ T item = Resolve(args);
+
+ if (item != null)
+ {
+ SetItemValues(item, args);
+
+ EnsureName(item);
+ EnsureDates(item);
+ }
+
+ return item;
+ }
+
+ private void EnsureName(T item)
+ {
+ // If the subclass didn't supply a name, add it here
+ if (string.IsNullOrEmpty(item.Name))
+ {
+ item.Name = Path.GetFileNameWithoutExtension(item.Path);
+ }
+
+ }
+
+ private void EnsureDates(T item)
+ {
+ // If the subclass didn't supply dates, add them here
+ if (item.DateCreated == DateTime.MinValue)
+ {
+ item.DateCreated = Path.IsPathRooted(item.Path) ? File.GetCreationTime(item.Path) : DateTime.Now;
+ }
+
+ if (item.DateModified == DateTime.MinValue)
+ {
+ item.DateModified = Path.IsPathRooted(item.Path) ? File.GetLastWriteTime(item.Path) : DateTime.Now;
+ }
+ }
+
+ protected virtual void PopulateImages(T item, ItemResolveEventArgs args)
+ {
+ List<string> backdropFiles = new List<string>();
+
+ foreach (KeyValuePair<string,FileAttributes> file in args.FileSystemChildren)
+ {
+ if (file.Value.HasFlag(FileAttributes.Directory))
+ {
+ continue;
+ }
+
+ string filePath = file.Key;
+
+ string ext = Path.GetExtension(filePath);
+
+ if (!ext.EndsWith("png", StringComparison.OrdinalIgnoreCase) && !ext.EndsWith("jpg", StringComparison.OrdinalIgnoreCase))
+ {
+ continue;
+ }
+
+ string name = Path.GetFileNameWithoutExtension(filePath);
+
+ if (name.Equals("folder", StringComparison.OrdinalIgnoreCase))
+ {
+ item.PrimaryImagePath = filePath;
+ }
+ else if (name.StartsWith("backdrop", StringComparison.OrdinalIgnoreCase))
+ {
+ backdropFiles.Add(filePath);
+ }
+ if (name.Equals("logo", StringComparison.OrdinalIgnoreCase))
+ {
+ item.LogoImagePath = filePath;
+ }
+ if (name.Equals("banner", StringComparison.OrdinalIgnoreCase))
+ {
+ item.BannerImagePath = filePath;
+ }
+ if (name.Equals("art", StringComparison.OrdinalIgnoreCase))
+ {
+ item.ArtImagePath = filePath;
+ }
+ if (name.Equals("thumb", StringComparison.OrdinalIgnoreCase))
+ {
+ item.ThumbnailImagePath = filePath;
+ }
+ }
+
+ item.BackdropImagePaths = backdropFiles;
+ }
+
+ protected virtual void PopulateLocalTrailers(T item, ItemResolveEventArgs args)
+ {
+ var trailerPath = args.GetFolderByName("trailers");
+
+ if (trailerPath.HasValue)
+ {
+ string[] allFiles = Directory.GetFileSystemEntries(trailerPath.Value.Key, "*", SearchOption.TopDirectoryOnly);
+
+ item.LocalTrailers = allFiles.Select(f => Kernel.Instance.ItemController.GetItem(f)).OfType<Video>();
+ }
+ }
+ }
+
+ public interface IBaseItemResolver
+ {
+ BaseItem ResolvePath(ItemResolveEventArgs args);
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/FolderResolver.cs b/MediaBrowser.Controller/Resolvers/FolderResolver.cs new file mode 100644 index 000000000..5c57c6bb3 --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/FolderResolver.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.Xml;
+
+namespace MediaBrowser.Controller.Resolvers
+{
+ public class FolderResolver : BaseFolderResolver<Folder>
+ {
+ protected override Folder Resolve(ItemResolveEventArgs args)
+ {
+ if (args.IsFolder)
+ {
+ return new Folder();
+ }
+
+ return null;
+ }
+ }
+
+ public abstract class BaseFolderResolver<T> : BaseItemResolver<T>
+ where T : Folder, new ()
+ {
+ protected override void SetItemValues(T item, ItemResolveEventArgs args)
+ {
+ base.SetItemValues(item, args);
+
+ item.IsRoot = args.Parent == null;
+
+ PopulateFolderMetadata(item, args);
+ }
+
+ private void PopulateFolderMetadata(Folder folder, ItemResolveEventArgs args)
+ {
+ var metadataFile = args.GetFileByName("folder.xml");
+
+ if (metadataFile.HasValue)
+ {
+ new FolderXmlParser().Fetch(folder, metadataFile.Value.Key);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Resolvers/VideoResolver.cs b/MediaBrowser.Controller/Resolvers/VideoResolver.cs new file mode 100644 index 000000000..ba51dab87 --- /dev/null +++ b/MediaBrowser.Controller/Resolvers/VideoResolver.cs @@ -0,0 +1,114 @@ +using System.IO;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Model.Entities;
+using System.Linq;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Resolvers
+{
+ public class VideoResolver : BaseVideoResolver<Video>
+ {
+ }
+
+ public abstract class BaseVideoResolver<T> : BaseItemResolver<T>
+ where T : Video, new()
+ {
+ protected override T Resolve(ItemResolveEventArgs args)
+ {
+ if (!args.IsFolder)
+ {
+ if (IsVideoFile(args.Path))
+ {
+ return new T()
+ {
+ VideoType = VideoType.VideoFile,
+ Path = args.Path
+ };
+ }
+ }
+
+ else
+ {
+ T item = ResolveFromFolderName(args.Path);
+
+ if (item != null)
+ {
+ return item;
+ }
+
+ foreach (KeyValuePair<string, FileAttributes> folder in args.FileSystemChildren)
+ {
+ if (!folder.Value.HasFlag(FileAttributes.Directory))
+ {
+ continue;
+ }
+
+ item = ResolveFromFolderName(folder.Key);
+
+ if (item != null)
+ {
+ return item;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private T ResolveFromFolderName(string folder)
+ {
+ if (folder.IndexOf("video_ts", System.StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return new T()
+ {
+ VideoType = VideoType.DVD,
+ Path = Path.GetDirectoryName(folder)
+ };
+ }
+ if (folder.IndexOf("bdmv", System.StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return new T()
+ {
+ VideoType = VideoType.BluRay,
+ Path = Path.GetDirectoryName(folder)
+ };
+ }
+
+ return null;
+ }
+
+ private static bool IsVideoFile(string path)
+ {
+ string extension = Path.GetExtension(path).ToLower();
+
+ switch (extension)
+ {
+ case ".mkv":
+ case ".m2ts":
+ case ".iso":
+ case ".ts":
+ case ".rmvb":
+ case ".mov":
+ case ".avi":
+ case ".mpg":
+ case ".mpeg":
+ case ".wmv":
+ case ".mp4":
+ case ".divx":
+ case ".dvr-ms":
+ case ".wtv":
+ case ".ogm":
+ case ".ogv":
+ case ".asf":
+ case ".m4v":
+ case ".flv":
+ case ".f4v":
+ case ".3gp":
+ return true;
+
+ default:
+ return false;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/UserController.cs b/MediaBrowser.Controller/UserController.cs new file mode 100644 index 000000000..cce20329f --- /dev/null +++ b/MediaBrowser.Controller/UserController.cs @@ -0,0 +1,60 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using MediaBrowser.Common.Json;
+using MediaBrowser.Model.Users;
+
+namespace MediaBrowser.Controller
+{
+ public class UserController
+ {
+ public string UsersPath { get; set; }
+
+ public UserController(string usersPath)
+ {
+ UsersPath = usersPath;
+ }
+
+ public IEnumerable<User> GetAllUsers()
+ {
+ if (!Directory.Exists(UsersPath))
+ {
+ Directory.CreateDirectory(UsersPath);
+ }
+
+ List<User> list = new List<User>();
+
+ foreach (string folder in Directory.GetDirectories(UsersPath, "*", SearchOption.TopDirectoryOnly))
+ {
+ User item = GetFromDirectory(folder);
+
+ if (item != null)
+ {
+ list.Add(item);
+ }
+ }
+
+ return list;
+ }
+
+ private User GetFromDirectory(string path)
+ {
+ string file = Path.Combine(path, "user.js");
+
+ return JsonSerializer.Deserialize<User>(file);
+ }
+
+ public void CreateUser(User user)
+ {
+ user.Id = Guid.NewGuid();
+
+ user.DateCreated = user.DateModified = DateTime.Now;
+
+ string userFolder = Path.Combine(UsersPath, user.Id.ToString());
+
+ Directory.CreateDirectory(userFolder);
+
+ JsonSerializer.Serialize(user, Path.Combine(userFolder, "user.js"));
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs b/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs new file mode 100644 index 000000000..2addf6a1d --- /dev/null +++ b/MediaBrowser.Controller/Xml/BaseItemXmlParser.cs @@ -0,0 +1,591 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Xml;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Xml
+{
+ public class BaseItemXmlParser<T>
+ where T : BaseItem, new()
+ {
+ public virtual void Fetch(T item, string metadataFile)
+ {
+ XmlDocument doc = new XmlDocument();
+
+ doc.Load(metadataFile);
+
+ XmlElement titleElement = doc.DocumentElement;
+
+ foreach (XmlNode node in titleElement.ChildNodes)
+ {
+ FetchDataFromXmlNode(node, item);
+ }
+
+ // If dates weren't supplied in metadata, use values from the file
+ if (item.DateCreated == DateTime.MinValue)
+ {
+ item.DateCreated = File.GetCreationTime(metadataFile);
+ }
+
+ if (item.DateModified == DateTime.MinValue)
+ {
+ item.DateModified = File.GetLastWriteTime(metadataFile);
+ }
+ }
+
+ protected virtual void FetchDataFromXmlNode(XmlNode node, T item)
+ {
+ switch (node.Name)
+ {
+ case "Added":
+ DateTime added;
+ if (DateTime.TryParse(node.InnerText ?? string.Empty, out added))
+ {
+ item.DateCreated = added;
+ }
+ break;
+
+ case "Type":
+ {
+ item.DisplayMediaType = node.InnerText ?? string.Empty;
+
+ switch (item.DisplayMediaType.ToLower())
+ {
+ case "blu-ray":
+ item.DisplayMediaType = VideoType.BluRay.ToString();
+ break;
+ case "dvd":
+ item.DisplayMediaType = VideoType.DVD.ToString();
+ break;
+ case "":
+ item.DisplayMediaType = null;
+ break;
+ }
+
+ break;
+ }
+
+ case "banner":
+ item.BannerImagePath = node.InnerText ?? string.Empty;
+ break;
+
+ case "LocalTitle":
+ item.Name = node.InnerText ?? string.Empty;
+ break;
+
+ case "SortTitle":
+ item.SortName = node.InnerText ?? string.Empty;
+ break;
+
+ case "Overview":
+ case "Description":
+ item.Overview = node.InnerText ?? string.Empty;
+ break;
+
+ case "TagLine":
+ item.Tagline = node.InnerText ?? string.Empty;
+ break;
+
+ case "ContentRating":
+ case "MPAARating":
+ item.OfficialRating = node.InnerText ?? string.Empty;
+ break;
+
+ case "CustomRating":
+ item.CustomRating = node.InnerText ?? string.Empty;
+ break;
+
+ case "CustomPin":
+ item.CustomPin = node.InnerText ?? string.Empty;
+ break;
+
+ case "Covers":
+ FetchFromCoversNode(node, item);
+ break;
+
+ case "Genres":
+ FetchFromGenresNode(node, item);
+ break;
+
+ case "Genre":
+ {
+ var genres = (item.Genres ?? new string[] { }).ToList();
+ genres.AddRange(GetSplitValues(node.InnerText, '|'));
+
+ item.Genres = genres;
+ break;
+ }
+
+ case "AspectRatio":
+ item.AspectRatio = node.InnerText ?? string.Empty;
+ break;
+
+ case "Rating":
+ case "IMDBrating":
+ float IMDBrating = node.SafeGetSingle((float)-1, (float)10);
+
+ if (IMDBrating >= 0)
+ {
+ item.UserRating = IMDBrating;
+ }
+ break;
+
+ case "Network":
+ {
+ var studios = (item.Studios ?? new string[] { }).ToList();
+ studios.AddRange(GetSplitValues(node.InnerText, '|'));
+
+ item.Studios = studios;
+ break;
+ }
+ case "Studios":
+ FetchFromStudiosNode(node, item);
+ break;
+
+ case "Director":
+ {
+ var list = (item.People ?? new Person[]{}).ToList();
+ list.AddRange(GetSplitValues(node.InnerText, '|').Select(v => new Person() { Name = v, PersonType = PersonType.Director }));
+
+ item.People = list;
+ break;
+ }
+ case "Writer":
+ {
+ var list = (item.People ?? new Person[] { }).ToList();
+ list.AddRange(GetSplitValues(node.InnerText, '|').Select(v => new Person() { Name = v, PersonType = PersonType.Writer }));
+
+ item.People = list;
+ break;
+ }
+
+ case "Actors":
+ case "GuestStars":
+ {
+ var list = (item.People ?? new Person[] { }).ToList();
+ list.AddRange(GetSplitValues(node.InnerText, '|').Select(v => new Person() { Name = v, PersonType = PersonType.Actor }));
+
+ item.People = list;
+ break;
+ }
+
+ case "Persons":
+ FetchDataFromPersonsNode(node, item);
+ break;
+
+ case "Trailer":
+ item.TrailerUrl = node.InnerText ?? string.Empty;
+ break;
+
+ case "ParentalRating":
+ FetchFromParentalRatingNode(node, item);
+ break;
+
+ case "ProductionYear":
+ {
+ int ProductionYear;
+ if (int.TryParse(node.InnerText, out ProductionYear) && ProductionYear > 1850)
+ {
+ item.ProductionYear = ProductionYear;
+ }
+
+ break;
+ }
+
+ case "MediaInfo":
+ FetchMediaInfo(node, item);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ protected virtual void FetchFromCoversNode(XmlNode node, T item)
+ {
+ string cover = node.SafeGetString("Front");
+
+ if (!string.IsNullOrEmpty(cover))
+ {
+ item.PrimaryImagePath = cover;
+ }
+ }
+
+ protected virtual void FetchMediaInfo(XmlNode node, T item)
+ {
+ var iMediaInfo = item as Video;
+
+ if (iMediaInfo != null)
+ {
+ FetchMediaInfo(node, iMediaInfo);
+ }
+ }
+
+ protected virtual void FetchMediaInfo(XmlNode node, Video item)
+ {
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Audio":
+ {
+ AudioStream stream = FetchMediaInfoAudio(childNode);
+
+ List<AudioStream> streams = item.AudioStreams.ToList();
+ streams.Add(stream);
+ item.AudioStreams = streams;
+
+ break;
+ }
+
+ case "Video":
+ FetchMediaInfoVideo(childNode, item);
+ break;
+
+ case "Subtitle":
+ FetchMediaInfoSubtitles(childNode, item);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ protected virtual AudioStream FetchMediaInfoAudio(XmlNode node)
+ {
+ AudioStream stream = new AudioStream();
+
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "BitRate":
+ stream.BitRate = childNode.SafeGetInt32();
+ break;
+
+ case "Channels":
+ stream.Channels = childNode.SafeGetInt32();
+ break;
+
+ case "Language":
+ stream.Language = childNode.InnerText ?? string.Empty;
+ break;
+
+ case "Codec":
+ {
+ string codec = childNode.InnerText ?? string.Empty;
+
+ switch (codec.ToLower())
+ {
+ case "dts-es":
+ case "dts-es matrix":
+ case "dts-es discrete":
+ stream.AudioFormat = "DTS";
+ stream.AudioProfile = "ES";
+ break;
+ case "dts-hd hra":
+ case "dts-hd high resolution":
+ stream.AudioFormat = "DTS";
+ stream.AudioProfile = "HRA";
+ break;
+ case "dts ma":
+ case "dts-hd ma":
+ case "dts-hd master":
+ stream.AudioFormat = "DTS";
+ stream.AudioProfile = "MA";
+ break;
+ case "dolby digital":
+ case "dolby digital surround ex":
+ case "dolby surround":
+ stream.AudioFormat = "AC-3";
+ break;
+ case "dolby digital plus":
+ stream.AudioFormat = "E-AC-3";
+ break;
+ case "dolby truehd":
+ stream.AudioFormat = "AC-3";
+ stream.AudioProfile = "TrueHD";
+ break;
+ case "mp2":
+ stream.AudioFormat = "MPEG Audio";
+ stream.AudioProfile = "Layer 2";
+ break;
+ case "other":
+ break;
+ default:
+ stream.AudioFormat = codec;
+ break;
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ return stream;
+ }
+
+ protected virtual void FetchMediaInfoVideo(XmlNode node, Video item)
+ {
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Width":
+ item.Width = childNode.SafeGetInt32();
+ break;
+
+ case "Height":
+ item.Height = childNode.SafeGetInt32();
+ break;
+
+ case "BitRate":
+ item.VideoBitRate = childNode.SafeGetInt32();
+ break;
+
+ case "FrameRate":
+ item.FrameRate = childNode.InnerText ?? string.Empty;
+ break;
+
+ case "ScanType":
+ item.ScanType = childNode.InnerText ?? string.Empty;
+ break;
+
+ case "Duration":
+ item.RunTime = TimeSpan.FromMinutes(childNode.SafeGetInt32());
+ break;
+
+ case "DurationSeconds":
+ int seconds = childNode.SafeGetInt32();
+ if (seconds > 0)
+ {
+ item.RunTime = TimeSpan.FromSeconds(seconds);
+ }
+ break;
+
+ case "Codec":
+ {
+ string videoCodec = childNode.InnerText ?? string.Empty;
+
+ switch (videoCodec.ToLower())
+ {
+ case "sorenson h.263":
+ item.VideoCodec = "Sorenson H263";
+ break;
+ case "h.262":
+ item.VideoCodec = "MPEG-2 Video";
+ break;
+ case "h.264":
+ item.VideoCodec = "AVC";
+ break;
+ default:
+ item.VideoCodec = videoCodec;
+ break;
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ protected virtual void FetchMediaInfoSubtitles(XmlNode node, Video item)
+ {
+ List<string> subtitles = item.Subtitles.ToList();
+
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Language":
+ string lang = childNode.InnerText;
+
+ if (!string.IsNullOrEmpty(lang))
+ {
+ subtitles.Add(lang);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ item.Subtitles = subtitles;
+ }
+
+ protected virtual void FetchFromGenresNode(XmlNode node, T item)
+ {
+ List<string> list = (item.Genres ?? new string[] { }).ToList();
+
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Genre":
+ string text = childNode.InnerText ?? string.Empty;
+
+ if (!string.IsNullOrEmpty(text))
+ {
+ list.Add(text);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ }
+ item.Genres = list;
+ }
+
+ protected virtual void FetchDataFromPersonsNode(XmlNode node, T item)
+ {
+ List<Person> list = (item.People ?? new Person[] { }).ToList();
+
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Person":
+ {
+ list.Add(GetPersonFromXmlNode(childNode));
+
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ }
+
+ item.People = list;
+ }
+
+ protected virtual void FetchFromStudiosNode(XmlNode node, T item)
+ {
+ List<string> list = (item.Studios ?? new string[] { }).ToList();
+
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Studio":
+ string text = childNode.InnerText ?? string.Empty;
+
+ if (!string.IsNullOrEmpty(text))
+ {
+ list.Add(text);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ item.Studios = list;
+ }
+
+ protected virtual void FetchFromParentalRatingNode(XmlNode node, T item)
+ {
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Value":
+ {
+ int ParentalRating = childNode.SafeGetInt32((int)7);
+
+ switch (ParentalRating)
+ {
+ case -1:
+ item.OfficialRating = "NR";
+ break;
+ case 0:
+ item.OfficialRating = "UR";
+ break;
+ case 1:
+ item.OfficialRating = "G";
+ break;
+ case 3:
+ item.OfficialRating = "PG";
+ break;
+ case 4:
+ item.OfficialRating = "PG-13";
+ break;
+ case 5:
+ item.OfficialRating = "NC-17";
+ break;
+ case 6:
+ item.OfficialRating = "R";
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ private Person GetPersonFromXmlNode(XmlNode node)
+ {
+ Person person = new Person();
+
+ foreach (XmlNode childNode in node.ChildNodes)
+ {
+ switch (childNode.Name)
+ {
+ case "Name":
+ person.Name = childNode.InnerText ?? string.Empty;
+ break;
+
+ case "Type":
+ {
+ string type = childNode.InnerText ?? string.Empty;
+
+ if (type == "Director")
+ {
+ person.PersonType = PersonType.Director;
+ }
+ else if (type == "Actor")
+ {
+ person.PersonType = PersonType.Actor;
+ }
+ break;
+ }
+
+ case "Role":
+ person.Description = childNode.InnerText ?? string.Empty;
+ break;
+
+ default:
+ break;
+ }
+
+ }
+ return person;
+ }
+
+ protected IEnumerable<string> GetSplitValues(string value, char deliminator)
+ {
+ value = (value ?? string.Empty).Trim(deliminator);
+
+ return string.IsNullOrEmpty(value) ? new string[] { } : value.Split(deliminator);
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/Xml/FolderXmlParser.cs b/MediaBrowser.Controller/Xml/FolderXmlParser.cs new file mode 100644 index 000000000..d0dc7d4ec --- /dev/null +++ b/MediaBrowser.Controller/Xml/FolderXmlParser.cs @@ -0,0 +1,8 @@ +using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Xml
+{
+ public class FolderXmlParser : BaseItemXmlParser<Folder>
+ {
+ }
+}
diff --git a/MediaBrowser.Controller/Xml/XmlExtensions.cs b/MediaBrowser.Controller/Xml/XmlExtensions.cs new file mode 100644 index 000000000..4f753a3f8 --- /dev/null +++ b/MediaBrowser.Controller/Xml/XmlExtensions.cs @@ -0,0 +1,74 @@ +using System;
+using System.Globalization;
+using System.Xml;
+
+namespace MediaBrowser.Controller.Xml
+{
+ public static class XmlExtensions
+ {
+ public static int SafeGetInt32(this XmlNode node)
+ {
+ return SafeGetInt32(node, 0);
+ }
+
+ public static int SafeGetInt32(this XmlNode node, int defaultInt)
+ {
+ if (node != null && node.InnerText.Length > 0)
+ {
+ int rval;
+ if (Int32.TryParse(node.InnerText, out rval))
+ {
+ return rval;
+ }
+
+ }
+ return defaultInt;
+ }
+
+ private static CultureInfo _usCulture = new CultureInfo("en-US");
+
+ public static float SafeGetSingle(this XmlNode rvalNode, float minValue, float maxValue)
+ {
+ if (rvalNode.InnerText.Length > 0)
+ {
+ float rval;
+ // float.TryParse is local aware, so it can be probamatic, force us culture
+ if (float.TryParse(rvalNode.InnerText, NumberStyles.AllowDecimalPoint, _usCulture, out rval))
+ {
+ if (rval >= minValue && rval <= maxValue)
+ {
+ return rval;
+ }
+ }
+
+ }
+ return minValue;
+ }
+
+ public static float SafeGetSingle(this XmlNode doc, string path, float minValue, float maxValue)
+ {
+ XmlNode rvalNode = doc.SelectSingleNode(path);
+ if (rvalNode != null)
+ {
+ rvalNode.SafeGetSingle(minValue, maxValue);
+
+ }
+ return minValue;
+ }
+
+
+ public static string SafeGetString(this XmlNode node)
+ {
+ return SafeGetString(node, null);
+ }
+
+ public static string SafeGetString(this XmlNode node, string defaultValue)
+ {
+ if (node != null && node.InnerText.Length > 0)
+ {
+ return node.InnerText;
+ }
+ return defaultValue;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/packages.config b/MediaBrowser.Controller/packages.config new file mode 100644 index 000000000..4f6bcdcff --- /dev/null +++ b/MediaBrowser.Controller/packages.config @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Newtonsoft.Json" version="4.5.7" targetFramework="net45" />
+ <package id="Rx-Main" version="1.0.11226" targetFramework="net45" />
+</packages>
\ No newline at end of file diff --git a/MediaBrowser.HtmlBrowser/MediaBrowser.HtmlBrowser.csproj b/MediaBrowser.HtmlBrowser/MediaBrowser.HtmlBrowser.csproj new file mode 100644 index 000000000..b496a2741 --- /dev/null +++ b/MediaBrowser.HtmlBrowser/MediaBrowser.HtmlBrowser.csproj @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{99B4CFE8-1441-4F0D-8C40-A70D0DD372ED}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.HtmlBrowser</RootNamespace>
+ <AssemblyName>MediaBrowser.HtmlBrowser</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Reactive">
+ <HintPath>..\packages\Rx-Main.1.0.11226\lib\Net4\System.Reactive.dll</HintPath>
+ </Reference>
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Plugin.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <PropertyGroup>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>
+ </PropertyGroup>
+ <!-- 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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.HtmlBrowser/Plugin.cs b/MediaBrowser.HtmlBrowser/Plugin.cs new file mode 100644 index 000000000..ea3bd4ccc --- /dev/null +++ b/MediaBrowser.HtmlBrowser/Plugin.cs @@ -0,0 +1,16 @@ +using System;
+using System.Collections.Generic;
+using System.Reactive.Linq;
+using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Net;
+
+namespace MediaBrowser.HtmlBrowser
+{
+ public class Plugin : BasePlugin<BasePluginConfiguration>
+ {
+ protected override void InitInternal()
+ {
+ }
+ }
+}
diff --git a/MediaBrowser.HtmlBrowser/Properties/AssemblyInfo.cs b/MediaBrowser.HtmlBrowser/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b65ea6f77 --- /dev/null +++ b/MediaBrowser.HtmlBrowser/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.HtmlBrowser")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.HtmlBrowser")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("2b84a02d-40ff-4187-818f-170abc3c31cf")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.HtmlBrowser/packages.config b/MediaBrowser.HtmlBrowser/packages.config new file mode 100644 index 000000000..47102a263 --- /dev/null +++ b/MediaBrowser.HtmlBrowser/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Rx-Main" version="1.0.11226" targetFramework="net45" />
+</packages>
\ No newline at end of file diff --git a/MediaBrowser.InternetProviders/MediaBrowser.InternetProviders.csproj b/MediaBrowser.InternetProviders/MediaBrowser.InternetProviders.csproj new file mode 100644 index 000000000..750344331 --- /dev/null +++ b/MediaBrowser.InternetProviders/MediaBrowser.InternetProviders.csproj @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{5758B2C7-949A-421D-B268-70A950CF8741}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.InternetProviders</RootNamespace>
+ <AssemblyName>MediaBrowser.InternetProviders</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Plugin.cs" />
+ <Compile Include="PluginConfiguration.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{9b1ddd79-5134-4df3-ace3-d1957a7350d8}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Movies\MediaBrowser.Movies.csproj">
+ <Project>{92b9f802-4415-438f-90e1-44602135ea41}</Project>
+ <Name>MediaBrowser.Movies</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.TV\MediaBrowser.TV.csproj">
+ <Project>{32dfc600-cd2f-4b2d-b39a-3b4c6c32f9b4}</Project>
+ <Name>MediaBrowser.TV</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <PropertyGroup>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>
+ </PropertyGroup>
+ <!-- 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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.InternetProviders/Plugin.cs b/MediaBrowser.InternetProviders/Plugin.cs new file mode 100644 index 000000000..c7b981aed --- /dev/null +++ b/MediaBrowser.InternetProviders/Plugin.cs @@ -0,0 +1,11 @@ +using MediaBrowser.Common.Plugins;
+
+namespace MediaBrowser.InternetProviders
+{
+ public class Plugin : BasePlugin<PluginConfiguration>
+ {
+ protected override void InitInternal()
+ {
+ }
+ }
+}
diff --git a/MediaBrowser.InternetProviders/PluginConfiguration.cs b/MediaBrowser.InternetProviders/PluginConfiguration.cs new file mode 100644 index 000000000..abd189f2a --- /dev/null +++ b/MediaBrowser.InternetProviders/PluginConfiguration.cs @@ -0,0 +1,8 @@ +using MediaBrowser.Common.Plugins;
+
+namespace MediaBrowser.InternetProviders
+{
+ public class PluginConfiguration : BasePluginConfiguration
+ {
+ }
+}
diff --git a/MediaBrowser.InternetProviders/Properties/AssemblyInfo.cs b/MediaBrowser.InternetProviders/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..859fc6ef9 --- /dev/null +++ b/MediaBrowser.InternetProviders/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.InternetProviders")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.InternetProviders")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("520717d0-3257-41b2-8da2-6aa8b8248250")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.Model/Configuration/Configuration.cs b/MediaBrowser.Model/Configuration/Configuration.cs new file mode 100644 index 000000000..c2aa73728 --- /dev/null +++ b/MediaBrowser.Model/Configuration/Configuration.cs @@ -0,0 +1,17 @@ +using MediaBrowser.Common.Logging;
+
+namespace MediaBrowser.Model.Configuration
+{
+ public class Configuration
+ {
+ public string ImagesByNamePath { get; set; }
+ public int HttpServerPortNumber { get; set; }
+ public LogSeverity LogSeverity { get; set; }
+
+ public Configuration()
+ {
+ HttpServerPortNumber = 8096;
+ LogSeverity = Common.Logging.LogSeverity.Info;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Entities/Audio.cs b/MediaBrowser.Model/Entities/Audio.cs new file mode 100644 index 000000000..b243411ad --- /dev/null +++ b/MediaBrowser.Model/Entities/Audio.cs @@ -0,0 +1,12 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Entities
+{
+ public class Audio : BaseItem
+ {
+ }
+}
diff --git a/MediaBrowser.Model/Entities/BaseItem.cs b/MediaBrowser.Model/Entities/BaseItem.cs new file mode 100644 index 000000000..2eaf375d9 --- /dev/null +++ b/MediaBrowser.Model/Entities/BaseItem.cs @@ -0,0 +1,64 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
+
+namespace MediaBrowser.Model.Entities
+{
+ public abstract class BaseItem
+ {
+ public string Name { get; set; }
+ public string SortName { get; set; }
+
+ public Guid Id { get; set; }
+
+ public DateTime DateCreated { get; set; }
+ public DateTime DateModified { get; set; }
+
+ public string Path { get; set; }
+
+ [JsonIgnore]
+ public Folder Parent { get; set; }
+
+ public string PrimaryImagePath { get; set; }
+ public string LogoImagePath { get; set; }
+ public string ArtImagePath { get; set; }
+ public string ThumbnailImagePath { get; set; }
+ public string BannerImagePath { get; set; }
+
+ public IEnumerable<string> BackdropImagePaths { get; set; }
+
+ public string OfficialRating { get; set; }
+
+ public string CustomRating { get; set; }
+ public string CustomPin { get; set; }
+
+ public string Overview { get; set; }
+ public string Tagline { get; set; }
+
+ public IEnumerable<Person> People { get; set; }
+
+ public IEnumerable<string> Studios { get; set; }
+
+ public IEnumerable<string> Genres { get; set; }
+
+ public string DisplayMediaType { get; set; }
+
+ public float? UserRating { get; set; }
+ public TimeSpan? RunTime { get; set; }
+
+ public string AspectRatio { get; set; }
+ public int? ProductionYear { get; set; }
+
+ public IEnumerable<Video> LocalTrailers { get; set; }
+
+ public string TrailerUrl { get; set; }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Entities/Folder.cs b/MediaBrowser.Model/Entities/Folder.cs new file mode 100644 index 000000000..6af9bf259 --- /dev/null +++ b/MediaBrowser.Model/Entities/Folder.cs @@ -0,0 +1,96 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.IO;
+using Newtonsoft.Json;
+
+namespace MediaBrowser.Model.Entities
+{
+ public class Folder : BaseItem
+ {
+ public bool IsRoot { get; set; }
+
+ public bool IsVirtualFolder
+ {
+ get
+ {
+ return Parent != null && Parent.IsRoot;
+ }
+ }
+
+ [JsonIgnore]
+ public BaseItem[] Children { get; set; }
+
+ [JsonIgnore]
+ public IEnumerable<Folder> FolderChildren { get { return Children.OfType<Folder>(); } }
+
+ public Folder GetFolderByName(string name)
+ {
+ return FolderChildren.FirstOrDefault(f => System.IO.Path.GetFileName(f.Path).Equals(name, StringComparison.OrdinalIgnoreCase));
+ }
+
+ /// <summary>
+ /// Finds an item by ID, recursively
+ /// </summary>
+ public BaseItem FindById(Guid id)
+ {
+ if (Id == id)
+ {
+ return this;
+ }
+
+ foreach (BaseItem item in Children)
+ {
+ if (item.Id == id)
+ {
+ return item;
+ }
+ }
+
+ foreach (Folder folder in FolderChildren)
+ {
+ BaseItem item = folder.FindById(id);
+
+ if (item != null)
+ {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Finds an item by path, recursively
+ /// </summary>
+ public BaseItem FindByPath(string path)
+ {
+ if (Path.Equals(path, StringComparison.OrdinalIgnoreCase))
+ {
+ return this;
+ }
+
+ foreach (BaseItem item in Children)
+ {
+ if (item.Path.Equals(path, StringComparison.OrdinalIgnoreCase))
+ {
+ return item;
+ }
+ }
+
+ foreach (Folder folder in FolderChildren)
+ {
+ BaseItem item = folder.FindByPath(path);
+
+ if (item != null)
+ {
+ return item;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.Model/Entities/Person.cs b/MediaBrowser.Model/Entities/Person.cs new file mode 100644 index 000000000..320491d02 --- /dev/null +++ b/MediaBrowser.Model/Entities/Person.cs @@ -0,0 +1,22 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Entities
+{
+ public class Person
+ {
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public PersonType PersonType { get; set; }
+ }
+
+ public enum PersonType
+ {
+ Actor = 1,
+ Director = 2,
+ Writer = 3
+ }
+}
diff --git a/MediaBrowser.Model/Entities/PlaybackStatus.cs b/MediaBrowser.Model/Entities/PlaybackStatus.cs new file mode 100644 index 000000000..042cfe098 --- /dev/null +++ b/MediaBrowser.Model/Entities/PlaybackStatus.cs @@ -0,0 +1,12 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Entities
+{
+ public class PlaybackStatus
+ {
+ }
+}
diff --git a/MediaBrowser.Model/Entities/Video.cs b/MediaBrowser.Model/Entities/Video.cs new file mode 100644 index 000000000..8b27f47c8 --- /dev/null +++ b/MediaBrowser.Model/Entities/Video.cs @@ -0,0 +1,42 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Model.Entities
+{
+ public class Video : BaseItem
+ {
+ public VideoType VideoType { get; set; }
+
+ private IEnumerable<string> _Subtitles = new string[] { };
+ public IEnumerable<string> Subtitles { get { return _Subtitles; } set { _Subtitles = value; } }
+
+ private IEnumerable<AudioStream> _AudioStreams = new AudioStream[] { };
+ public IEnumerable<AudioStream> AudioStreams { get { return _AudioStreams; } set { _AudioStreams = value; } }
+
+ public int Height { get; set; }
+ public int Width { get; set; }
+ public string ScanType { get; set; }
+ public string FrameRate { get; set; }
+ public int VideoBitRate { get; set; }
+ public string VideoCodec { get; set; }
+ }
+
+ public class AudioStream
+ {
+ public string AudioFormat { get; set; }
+ public string AudioProfile { get; set; }
+ public string Language { get; set; }
+ public int BitRate { get; set; }
+ public int Channels { get; set; }
+ }
+
+ public enum VideoType
+ {
+ VideoFile = 1,
+ DVD = 2,
+ BluRay = 3
+ }
+}
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj new file mode 100644 index 000000000..2f3548661 --- /dev/null +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{9B1DDD79-5134-4DF3-ACE3-D1957A7350D8}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.Model</RootNamespace>
+ <AssemblyName>MediaBrowser.Model</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json">
+ <HintPath>..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Configuration\Configuration.cs" />
+ <Compile Include="Entities\Person.cs" />
+ <Compile Include="Entities\Audio.cs" />
+ <Compile Include="Entities\BaseItem.cs" />
+ <Compile Include="Entities\Folder.cs" />
+ <Compile Include="Entities\PlaybackStatus.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Users\User.cs" />
+ <Compile Include="Users\UserItemData.cs" />
+ <Compile Include="Entities\Video.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ </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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.Model/Properties/AssemblyInfo.cs b/MediaBrowser.Model/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c5af59e20 --- /dev/null +++ b/MediaBrowser.Model/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Model")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Model")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("4478b410-9582-4c22-b890-2a309708b9f1")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.Model/Users/User.cs b/MediaBrowser.Model/Users/User.cs new file mode 100644 index 000000000..19b65fce4 --- /dev/null +++ b/MediaBrowser.Model/Users/User.cs @@ -0,0 +1,19 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Users
+{
+ public class User : BaseItem
+ {
+ public string Password { get; set; }
+ public string MaxParentalRating { get; set; }
+ public bool HideBlockedContent { get; set; }
+
+ private Dictionary<Guid, UserItemData> _ItemData = new Dictionary<Guid, UserItemData>();
+ public Dictionary<Guid, UserItemData> ItemData { get { return _ItemData; } set { _ItemData = value; } }
+ }
+}
diff --git a/MediaBrowser.Model/Users/UserItemData.cs b/MediaBrowser.Model/Users/UserItemData.cs new file mode 100644 index 000000000..ddb78555f --- /dev/null +++ b/MediaBrowser.Model/Users/UserItemData.cs @@ -0,0 +1,23 @@ +using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Users
+{
+ public class UserItemData
+ {
+ public UserItemRating Rating { get; set; }
+
+ public PlaybackStatus PlaybackStatus { get; set; }
+ }
+
+ public enum UserItemRating
+ {
+ Likes,
+ Dislikes,
+ Favorite
+ }
+}
diff --git a/MediaBrowser.Model/packages.config b/MediaBrowser.Model/packages.config new file mode 100644 index 000000000..9bfda3802 --- /dev/null +++ b/MediaBrowser.Model/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Newtonsoft.Json" version="4.5.7" targetFramework="net45" />
+</packages>
\ No newline at end of file diff --git a/MediaBrowser.Movies/Entities/BoxSet.cs b/MediaBrowser.Movies/Entities/BoxSet.cs new file mode 100644 index 000000000..520722607 --- /dev/null +++ b/MediaBrowser.Movies/Entities/BoxSet.cs @@ -0,0 +1,10 @@ +using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Movies.Entities
+{
+ public class BoxSet : Folder
+ {
+ }
+}
diff --git a/MediaBrowser.Movies/Entities/Movie.cs b/MediaBrowser.Movies/Entities/Movie.cs new file mode 100644 index 000000000..95c86ccd6 --- /dev/null +++ b/MediaBrowser.Movies/Entities/Movie.cs @@ -0,0 +1,14 @@ +using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Movies.Entities
+{
+ public class Movie : Video
+ {
+ public string TmdbId { get; set; }
+ public string ImdbId { get; set; }
+
+ public IEnumerable<Video> SpecialFeatures { get; set; }
+ }
+}
diff --git a/MediaBrowser.Movies/MediaBrowser.Movies.csproj b/MediaBrowser.Movies/MediaBrowser.Movies.csproj new file mode 100644 index 000000000..55816f78e --- /dev/null +++ b/MediaBrowser.Movies/MediaBrowser.Movies.csproj @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{92B9F802-4415-438F-90E1-44602135EA41}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.Movies</RootNamespace>
+ <AssemblyName>MediaBrowser.Movies</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Entities\BoxSet.cs" />
+ <Compile Include="Resolvers\BoxSetResolver.cs" />
+ <Compile Include="Entities\Movie.cs" />
+ <Compile Include="Resolvers\MovieResolver.cs" />
+ <Compile Include="Metadata\MovieXmlParser.cs" />
+ <Compile Include="Plugin.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{9b1ddd79-5134-4df3-ace3-d1957a7350d8}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <PropertyGroup>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>
+ </PropertyGroup>
+ <!-- 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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.Movies/Metadata/MovieXmlParser.cs b/MediaBrowser.Movies/Metadata/MovieXmlParser.cs new file mode 100644 index 000000000..09d5c319a --- /dev/null +++ b/MediaBrowser.Movies/Metadata/MovieXmlParser.cs @@ -0,0 +1,35 @@ +using System.Linq;
+using System.Xml;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Xml;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Movies.Entities;
+
+namespace MediaBrowser.Movies.Metadata
+{
+ public class MovieXmlParser : BaseItemXmlParser<Movie>
+ {
+ protected override void FetchDataFromXmlNode(XmlNode node, Movie item)
+ {
+ switch (node.Name)
+ {
+ case "TMDbId":
+ item.TmdbId = node.InnerText ?? string.Empty;
+ break;
+
+ case "IMDB":
+ case "IMDbId":
+ string IMDbId = node.InnerText ?? string.Empty;
+ if (!string.IsNullOrEmpty(IMDbId))
+ {
+ item.ImdbId = IMDbId;
+ }
+ break;
+
+ default:
+ base.FetchDataFromXmlNode(node, item);
+ break;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.Movies/Plugin.cs b/MediaBrowser.Movies/Plugin.cs new file mode 100644 index 000000000..414950466 --- /dev/null +++ b/MediaBrowser.Movies/Plugin.cs @@ -0,0 +1,24 @@ +using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller;
+using MediaBrowser.Movies.Entities;
+using MediaBrowser.Movies.Resolvers;
+
+namespace MediaBrowser.Movies
+{
+ public class Plugin : BasePlugin<BasePluginConfiguration>
+ {
+ protected override void InitInternal()
+ {
+ Kernel.Instance.AddBaseItemType<BoxSet, BoxSetResolver>();
+ Kernel.Instance.AddBaseItemType<Movie, MovieResolver>();
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+
+ Kernel.Instance.RemoveBaseItemType<Movie, MovieResolver>();
+ Kernel.Instance.RemoveBaseItemType<BoxSet, BoxSetResolver>();
+ }
+ }
+}
diff --git a/MediaBrowser.Movies/Properties/AssemblyInfo.cs b/MediaBrowser.Movies/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..bc9c6b006 --- /dev/null +++ b/MediaBrowser.Movies/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Movies")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Movies")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("e7616f1d-840f-4ada-bc58-e885035fbc1b")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.Movies/Resolvers/BoxSetResolver.cs b/MediaBrowser.Movies/Resolvers/BoxSetResolver.cs new file mode 100644 index 000000000..0d53429bb --- /dev/null +++ b/MediaBrowser.Movies/Resolvers/BoxSetResolver.cs @@ -0,0 +1,24 @@ +using System;
+using System.IO;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Movies.Entities;
+
+namespace MediaBrowser.Movies.Resolvers
+{
+ public class BoxSetResolver : BaseFolderResolver<BoxSet>
+ {
+ protected override BoxSet Resolve(ItemResolveEventArgs args)
+ {
+ if (args.IsFolder)
+ {
+ if (Path.GetFileName(args.Path).IndexOf("[boxset]", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return new BoxSet();
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.Movies/Resolvers/MovieResolver.cs b/MediaBrowser.Movies/Resolvers/MovieResolver.cs new file mode 100644 index 000000000..d2076851a --- /dev/null +++ b/MediaBrowser.Movies/Resolvers/MovieResolver.cs @@ -0,0 +1,83 @@ +using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Movies.Entities;
+using MediaBrowser.Movies.Metadata;
+
+namespace MediaBrowser.Movies.Resolvers
+{
+ public class MovieResolver : BaseVideoResolver<Movie>
+ {
+ protected override Movie Resolve(ItemResolveEventArgs args)
+ {
+ if (args.IsFolder)
+ {
+ var metadataFile = args.GetFileByName("movie.xml");
+
+ if (metadataFile.HasValue || Path.GetFileName(args.Path).IndexOf("[tmdbid=", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return GetMovie(args);
+ }
+ }
+
+ return null;
+ }
+
+ private Movie GetMovie(ItemResolveEventArgs args)
+ {
+ foreach (var child in args.FileSystemChildren)
+ {
+ ItemResolveEventArgs childArgs = new ItemResolveEventArgs()
+ {
+ Path = child.Key,
+ FileAttributes = child.Value,
+ FileSystemChildren = new KeyValuePair<string, FileAttributes>[] { }
+ };
+
+ var item = base.Resolve(childArgs);
+
+ if (item != null)
+ {
+ return new Movie()
+ {
+ Path = item.Path,
+ VideoType = item.VideoType
+ };
+ }
+ }
+
+ return new Movie();
+ }
+
+ private void PopulateBonusFeatures(Movie item, ItemResolveEventArgs args)
+ {
+ var trailerPath = args.GetFolderByName("specials");
+
+ if (trailerPath.HasValue)
+ {
+ string[] allFiles = Directory.GetFileSystemEntries(trailerPath.Value.Key, "*", SearchOption.TopDirectoryOnly);
+
+ item.SpecialFeatures = allFiles.Select(f => Kernel.Instance.ItemController.GetItem(f)).OfType<Video>();
+ }
+ }
+
+ protected override void SetItemValues(Movie item, ItemResolveEventArgs args)
+ {
+ base.SetItemValues(item, args);
+
+ var metadataFile = args.GetFileByName("movie.xml");
+
+ if (metadataFile.HasValue)
+ {
+ new MovieXmlParser().Fetch(item, metadataFile.Value.Key);
+ }
+
+ PopulateBonusFeatures(item, args);
+ }
+ }
+}
diff --git a/MediaBrowser.Program/App.config b/MediaBrowser.Program/App.config new file mode 100644 index 000000000..04df0f820 --- /dev/null +++ b/MediaBrowser.Program/App.config @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+ <appSettings>
+ <add key="DataPath" value="..\..\..\ProgramData" />
+ </appSettings>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
+ </startup>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration>
\ No newline at end of file diff --git a/MediaBrowser.Program/MediaBrowser.Program.csproj b/MediaBrowser.Program/MediaBrowser.Program.csproj new file mode 100644 index 000000000..4031aaf0b --- /dev/null +++ b/MediaBrowser.Program/MediaBrowser.Program.csproj @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{78AEA637-AF42-4F43-8E2B-0F2F0E2931F3}</ProjectGuid>
+ <OutputType>Exe</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.Program</RootNamespace>
+ <AssemblyName>MediaBrowser.Program</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Configuration" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Program.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{9b1ddd79-5134-4df3-ace3-d1957a7350d8}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.Program/Program.cs b/MediaBrowser.Program/Program.cs new file mode 100644 index 000000000..668869685 --- /dev/null +++ b/MediaBrowser.Program/Program.cs @@ -0,0 +1,48 @@ +using System;
+using System.Configuration;
+using System.IO;
+using MediaBrowser.Controller;
+
+namespace MediaBrowser.Program
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ LoadKernel();
+ }
+
+ private static void LoadKernel()
+ {
+ DateTime now = DateTime.Now;
+
+ Console.WriteLine("Loading");
+
+ string installDir = ConfigurationManager.AppSettings["DataPath"];
+
+ if (!Path.IsPathRooted(installDir))
+ {
+ string path = System.Reflection.Assembly.GetExecutingAssembly().Location;
+ path = Path.GetDirectoryName(path);
+
+ installDir = Path.Combine(path, installDir);
+
+ installDir = Path.GetFullPath(installDir);
+ }
+
+ if (!Directory.Exists(installDir))
+ {
+ Directory.CreateDirectory(installDir);
+ }
+
+ Kernel kernel = new Kernel(installDir);
+
+ kernel.Init();
+
+ var time = DateTime.Now - now;
+ Console.WriteLine("Done in " + time.TotalSeconds + " seconds");
+ Console.WriteLine("Press Enter to quit.");
+ Console.ReadLine();
+ }
+ }
+}
diff --git a/MediaBrowser.Program/Properties/AssemblyInfo.cs b/MediaBrowser.Program/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..f3ba9f028 --- /dev/null +++ b/MediaBrowser.Program/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.Program")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.Program")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("4be8a93f-7491-48e4-9400-f3a95a7bbdb2")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.TV/Entities/Episode.cs b/MediaBrowser.TV/Entities/Episode.cs new file mode 100644 index 000000000..d2966a3e3 --- /dev/null +++ b/MediaBrowser.TV/Entities/Episode.cs @@ -0,0 +1,13 @@ +using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.TV.Entities
+{
+ public class Episode : Video
+ {
+ public string SeasonNumber { get; set; }
+ public string EpisodeNumber { get; set; }
+ public string FirstAired { get; set; }
+ }
+}
diff --git a/MediaBrowser.TV/Entities/Season.cs b/MediaBrowser.TV/Entities/Season.cs new file mode 100644 index 000000000..ccf08a63a --- /dev/null +++ b/MediaBrowser.TV/Entities/Season.cs @@ -0,0 +1,16 @@ +using MediaBrowser.Model.Entities;
+using System.Linq;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+
+namespace MediaBrowser.TV.Entities
+{
+ public class Season : Folder
+ {
+ /// <summary>
+ /// Store these to reduce disk access in Episode Resolver
+ /// </summary>
+ [JsonIgnore]
+ public IEnumerable<string> MetadataFiles { get; set; }
+ }
+}
diff --git a/MediaBrowser.TV/Entities/Series.cs b/MediaBrowser.TV/Entities/Series.cs new file mode 100644 index 000000000..0034c9fe5 --- /dev/null +++ b/MediaBrowser.TV/Entities/Series.cs @@ -0,0 +1,12 @@ +using System;
+using System.Collections.Generic;
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.TV.Entities
+{
+ public class Series : Folder
+ {
+ public string TVDBSeriesId { get; set; }
+ public string Status { get; set; }
+ }
+}
diff --git a/MediaBrowser.TV/MediaBrowser.TV.csproj b/MediaBrowser.TV/MediaBrowser.TV.csproj new file mode 100644 index 000000000..7b0468071 --- /dev/null +++ b/MediaBrowser.TV/MediaBrowser.TV.csproj @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{32DFC600-CD2F-4B2D-B39A-3B4C6C32F9B4}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>MediaBrowser.TV</RootNamespace>
+ <AssemblyName>MediaBrowser.TV</AssemblyName>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Entities\Episode.cs" />
+ <Compile Include="Resolvers\EpisodeResolver.cs" />
+ <Compile Include="Metadata\EpisodeXmlParser.cs" />
+ <Compile Include="Plugin.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Entities\Season.cs" />
+ <Compile Include="Resolvers\SeasonResolver.cs" />
+ <Compile Include="Entities\Series.cs" />
+ <Compile Include="Resolvers\SeriesResolver.cs" />
+ <Compile Include="Metadata\SeriesXmlParser.cs" />
+ <Compile Include="TVUtils.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
+ <Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
+ <Name>MediaBrowser.Common</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
+ <Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
+ <Name>MediaBrowser.Controller</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
+ <Project>{9b1ddd79-5134-4df3-ace3-d1957a7350d8}</Project>
+ <Name>MediaBrowser.Model</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <PropertyGroup>
+ <PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData\Plugins\$(ProjectName)\" /y</PostBuildEvent>
+ </PropertyGroup>
+ <!-- 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.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
\ No newline at end of file diff --git a/MediaBrowser.TV/Metadata/EpisodeXmlParser.cs b/MediaBrowser.TV/Metadata/EpisodeXmlParser.cs new file mode 100644 index 000000000..3f23f2a4f --- /dev/null +++ b/MediaBrowser.TV/Metadata/EpisodeXmlParser.cs @@ -0,0 +1,62 @@ +using System;
+using System.IO;
+using System.Xml;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Xml;
+using MediaBrowser.TV.Entities;
+
+namespace MediaBrowser.TV.Metadata
+{
+ public class EpisodeXmlParser : BaseItemXmlParser<Episode>
+ {
+ protected override void FetchDataFromXmlNode(XmlNode node, Episode item)
+ {
+ switch (node.Name)
+ {
+ case "filename":
+ {
+ string filename = node.InnerText;
+
+ if (!string.IsNullOrEmpty(filename))
+ {
+ string metadataFolder = Path.GetDirectoryName(item.Path);
+ item.PrimaryImagePath = Path.Combine(metadataFolder, filename);
+ }
+ break;
+ }
+ case "EpisodeNumber":
+ item.EpisodeNumber = node.InnerText ?? string.Empty;
+ break;
+
+ case "SeasonNumber":
+ item.SeasonNumber = node.InnerText ?? string.Empty;
+ break;
+
+ case "EpisodeName":
+ item.Name = node.InnerText ?? string.Empty;
+ break;
+
+ case "FirstAired":
+ {
+ item.FirstAired = node.InnerText ?? string.Empty;
+
+ if (!string.IsNullOrEmpty(item.FirstAired))
+ {
+ DateTime airDate;
+ int y = DateTime.TryParse(item.FirstAired, out airDate) ? airDate.Year : -1;
+ if (y > 1850)
+ {
+ item.ProductionYear = y;
+ }
+ }
+
+ break;
+ }
+
+ default:
+ base.FetchDataFromXmlNode(node, item);
+ break;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.TV/Metadata/SeriesXmlParser.cs b/MediaBrowser.TV/Metadata/SeriesXmlParser.cs new file mode 100644 index 000000000..4ce081f40 --- /dev/null +++ b/MediaBrowser.TV/Metadata/SeriesXmlParser.cs @@ -0,0 +1,47 @@ +using System;
+using System.Xml;
+using MediaBrowser.Controller.Xml;
+using MediaBrowser.TV.Entities;
+
+namespace MediaBrowser.TV.Metadata
+{
+ public class SeriesXmlParser : BaseItemXmlParser<Series>
+ {
+ protected override void FetchDataFromXmlNode(XmlNode node, Series item)
+ {
+ switch (node.Name)
+ {
+ case "id":
+ item.TVDBSeriesId = node.InnerText ?? string.Empty;
+ break;
+
+ case "SeriesName":
+ item.Name = node.InnerText ?? string.Empty;
+ break;
+
+ case "Status":
+ item.Status = node.InnerText ?? string.Empty;
+ break;
+
+ case "Runtime":
+ {
+ string text = node.InnerText ?? string.Empty;
+ if (!string.IsNullOrEmpty(text))
+ {
+
+ int runtime;
+ if (int.TryParse(text.Split(' ')[0], out runtime))
+ {
+ item.RunTime = TimeSpan.FromMinutes(runtime);
+ }
+ }
+ break;
+ }
+
+ default:
+ base.FetchDataFromXmlNode(node, item);
+ break;
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.TV/Plugin.cs b/MediaBrowser.TV/Plugin.cs new file mode 100644 index 000000000..ade13d848 --- /dev/null +++ b/MediaBrowser.TV/Plugin.cs @@ -0,0 +1,44 @@ +using MediaBrowser.Common.Plugins;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.TV.Entities;
+using MediaBrowser.TV.Resolvers;
+using System;
+
+namespace MediaBrowser.TV
+{
+ public class Plugin : BasePlugin<BasePluginConfiguration>
+ {
+ protected override void InitInternal()
+ {
+ Kernel.Instance.AddBaseItemType<Series, SeriesResolver>();
+ Kernel.Instance.AddBaseItemType<Season, SeasonResolver>();
+ Kernel.Instance.AddBaseItemType<Episode, EpisodeResolver>();
+
+ Kernel.Instance.ItemController.PreBeginResolvePath += ItemController_PreBeginResolvePath;
+ }
+
+ void ItemController_PreBeginResolvePath(object sender, PreBeginResolveEventArgs e)
+ {
+ if (e.IsFolder && System.IO.Path.GetFileName(e.Path).Equals("metadata", StringComparison.OrdinalIgnoreCase))
+ {
+ if (e.Parent is Season || e.Parent is Series)
+ {
+ e.Cancel = true;
+ }
+ }
+ }
+
+ public override void Dispose()
+ {
+ base.Dispose();
+
+ Kernel.Instance.RemoveBaseItemType<Series, SeriesResolver>();
+ Kernel.Instance.RemoveBaseItemType<Season, SeasonResolver>();
+ Kernel.Instance.RemoveBaseItemType<Episode, EpisodeResolver>();
+
+ Kernel.Instance.ItemController.PreBeginResolvePath -= ItemController_PreBeginResolvePath;
+ }
+ }
+}
diff --git a/MediaBrowser.TV/Properties/AssemblyInfo.cs b/MediaBrowser.TV/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d92a7f576 --- /dev/null +++ b/MediaBrowser.TV/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +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.TV")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("MediaBrowser.TV")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[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("1646eb9e-3f4f-46ea-b1e9-09bc85c1143a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/MediaBrowser.TV/Resolvers/EpisodeResolver.cs b/MediaBrowser.TV/Resolvers/EpisodeResolver.cs new file mode 100644 index 000000000..ad6acd5fc --- /dev/null +++ b/MediaBrowser.TV/Resolvers/EpisodeResolver.cs @@ -0,0 +1,83 @@ +using System;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.TV.Entities;
+using MediaBrowser.TV.Metadata;
+
+namespace MediaBrowser.TV.Resolvers
+{
+ class EpisodeResolver : BaseVideoResolver<Episode>
+ {
+ protected override Episode Resolve(ItemResolveEventArgs args)
+ {
+ if (args.Parent is Season || args.Parent is Series)
+ {
+ return base.Resolve(args);
+ }
+
+ return null;
+ }
+
+ protected override void SetItemValues(Episode item, ItemResolveEventArgs args)
+ {
+ base.SetItemValues(item, args);
+
+ string metadataFolder = Path.Combine(args.Parent.Path, "metadata");
+
+ string episodeFileName = Path.GetFileName(item.Path);
+
+ string metadataFile = Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".xml"));
+
+ Season season = args.Parent as Season;
+
+ FetchMetadata(item, season, metadataFile);
+
+ if (string.IsNullOrEmpty(item.PrimaryImagePath))
+ {
+ SetPrimaryImagePath(item, season, metadataFolder, episodeFileName);
+ }
+ }
+
+ private void FetchMetadata(Episode item, Season season, string metadataFile)
+ {
+ if (season == null)
+ {
+ // Episode directly in Series folder
+ // Need to validate it the slow way
+ if (!File.Exists(metadataFile))
+ {
+ return;
+ }
+ }
+ else
+ {
+ if (!season.MetadataFiles.Any(s => s.Equals(metadataFile, StringComparison.OrdinalIgnoreCase)))
+ {
+ return;
+ }
+ }
+
+ new EpisodeXmlParser().Fetch(item, metadataFile);
+ }
+
+ private void SetPrimaryImagePath(Episode item, Season season, string metadataFolder, string episodeFileName)
+ {
+ string[] imageFiles = new string[] {
+ Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".jpg")),
+ Path.Combine(metadataFolder, Path.ChangeExtension(episodeFileName, ".png"))
+ };
+
+ if (season == null)
+ {
+ // Gotta do this the slow way
+ item.PrimaryImagePath = imageFiles.FirstOrDefault(f => File.Exists(f));
+ }
+ else
+ {
+ item.PrimaryImagePath = imageFiles.FirstOrDefault(f => season.MetadataFiles.Any(s => s.Equals(f, StringComparison.OrdinalIgnoreCase)));
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.TV/Resolvers/SeasonResolver.cs b/MediaBrowser.TV/Resolvers/SeasonResolver.cs new file mode 100644 index 000000000..3d4f03828 --- /dev/null +++ b/MediaBrowser.TV/Resolvers/SeasonResolver.cs @@ -0,0 +1,33 @@ +using System;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.TV.Entities;
+
+namespace MediaBrowser.TV.Resolvers
+{
+ class SeasonResolver : BaseFolderResolver<Season>
+ {
+ protected override Season Resolve(ItemResolveEventArgs args)
+ {
+ if (args.IsFolder && args.Parent is Series)
+ {
+ Season season = new Season();
+
+ if (args.ContainsFolder("metadata"))
+ {
+ season.MetadataFiles = Directory.GetFiles(Path.Combine(args.Path, "metadata"), "*", SearchOption.TopDirectoryOnly);
+ }
+ else
+ {
+ season.MetadataFiles = new string[] { };
+ }
+
+ return season;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.TV/Resolvers/SeriesResolver.cs b/MediaBrowser.TV/Resolvers/SeriesResolver.cs new file mode 100644 index 000000000..68581eb0b --- /dev/null +++ b/MediaBrowser.TV/Resolvers/SeriesResolver.cs @@ -0,0 +1,40 @@ +using System;
+using System.IO;
+using System.Linq;
+using MediaBrowser.Controller.Events;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.TV.Entities;
+using MediaBrowser.TV.Metadata;
+
+namespace MediaBrowser.TV.Resolvers
+{
+ class SeriesResolver : BaseFolderResolver<Series>
+ {
+ protected override Series Resolve(ItemResolveEventArgs args)
+ {
+ if (args.IsFolder)
+ {
+ var metadataFile = args.GetFileByName("series.xml");
+
+ if (metadataFile.HasValue || Path.GetFileName(args.Path).IndexOf("[tvdbid=", StringComparison.OrdinalIgnoreCase) != -1)
+ {
+ return new Series();
+ }
+ }
+
+ return null;
+ }
+
+ protected override void SetItemValues(Series item, ItemResolveEventArgs args)
+ {
+ base.SetItemValues(item, args);
+
+ var metadataFile = args.GetFileByName("series.xml");
+
+ if (metadataFile.HasValue)
+ {
+ new SeriesXmlParser().Fetch(item, metadataFile.Value.Key);
+ }
+ }
+ }
+}
diff --git a/MediaBrowser.TV/TVUtils.cs b/MediaBrowser.TV/TVUtils.cs new file mode 100644 index 000000000..8421ebc69 --- /dev/null +++ b/MediaBrowser.TV/TVUtils.cs @@ -0,0 +1,104 @@ +using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.TV
+{
+ public static class TVUtils
+ {
+ private static readonly Regex[] seasonPathExpressions = new Regex[] {
+ new Regex(@".+\\[s|S]eason\s?(?<seasonnumber>\d{1,2})$"),
+ new Regex(@".+\\[s|S]æson\s?(?<seasonnumber>\d{1,2})$"),
+ new Regex(@".+\\[t|T]emporada\s?(?<seasonnumber>\d{1,2})$"),
+ new Regex(@".+\\[s|S]aison\s?(?<seasonnumber>\d{1,2})$"),
+ new Regex(@".+\\[s|S]taffel\s?(?<seasonnumber>\d{1,2})$"),
+ new Regex(@".+\\[s|S](?<seasonnumber>\d{1,2})$"),
+ new Regex(@".+\\[s|S]eason\s?(?<seasonnumber>\d{1,2})[^\\]*$")
+
+ };
+
+ /// <summary>
+ /// Used to detect paths that represent episodes, need to make sure they don't also
+ /// match movie titles like "2001 A Space..."
+ /// Currently we limit the numbers here to 2 digits to try and avoid this
+ /// </summary>
+ /// <remarks>
+ /// The order here is important, if the order is changed some of the later
+ /// ones might incorrectly match things that higher ones would have caught.
+ /// The most restrictive expressions should appear first
+ /// </remarks>
+ private static readonly Regex[] episodeExpressions = new Regex[] {
+ new Regex(@".*\\[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})[^\\]*$"), // 01x02 blah.avi S01x01 balh.avi
+ new Regex(@".*\\[s|S](?<seasonnumber>\d{1,2})x?[e|E](?<epnumber>\d{1,3})[^\\]*$"), // S01E02 blah.avi, S01xE01 blah.avi
+ new Regex(@".*\\(?<seriesname>[^\\]*)[s|S]?(?<seasonnumber>\d{1,2})[x|X](?<epnumber>\d{1,3})[^\\]*$"), // 01x02 blah.avi S01x01 balh.avi
+ new Regex(@".*\\(?<seriesname>[^\\]*)[s|S](?<seasonnumber>\d{1,2})[x|X|\.]?[e|E](?<epnumber>\d{1,3})[^\\]*$") // S01E02 blah.avi, S01xE01 blah.avi
+ };
+ /// <summary>
+ /// To avoid the following matching movies they are only valid when contained in a folder which has been matched as a being season
+ /// </summary>
+ private static readonly Regex[] episodeExpressionsInASeasonFolder = new Regex[] {
+ new Regex(@".*\\(?<epnumber>\d{1,2})\s?-\s?[^\\]*$"), // 01 - blah.avi, 01-blah.avi
+ new Regex(@".*\\(?<epnumber>\d{1,2})[^\d\\]*[^\\]*$"), // 01.avi, 01.blah.avi "01 - 22 blah.avi"
+ new Regex(@".*\\(?<seasonnumber>\d)(?<epnumber>\d{1,2})[^\d\\]+[^\\]*$"), // 01.avi, 01.blah.avi
+ new Regex(@".*\\\D*\d+(?<epnumber>\d{2})") // hell0 - 101 - hello.avi
+
+ };
+
+ public static bool IsSeasonFolder(string path)
+ {
+ path = path.ToLower();
+
+ return seasonPathExpressions.Any(r => r.IsMatch(path));
+ }
+
+ public static bool IsSeriesFolder(string path, IEnumerable<string> files, IEnumerable<string> folders)
+ {
+ if (folders.Any(f => IsSeasonFolder(f)))
+ {
+ return true;
+ }
+
+ return files.Any(f => !string.IsNullOrEmpty(EpisodeNumberFromFile(f, false)));
+ }
+
+ public static bool IsEpisode(string fullPath)
+ {
+ bool isInSeason = IsSeasonFolder(Path.GetDirectoryName(fullPath));
+
+ if (isInSeason)
+ {
+ return true;
+ }
+ else if (EpisodeNumberFromFile(fullPath, isInSeason) != null)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ public static string EpisodeNumberFromFile(string fullPath, bool isInSeason)
+ {
+ string fl = fullPath.ToLower();
+ foreach (Regex r in episodeExpressions)
+ {
+ Match m = r.Match(fl);
+ if (m.Success)
+ return m.Groups["epnumber"].Value;
+ }
+ if (isInSeason)
+ {
+ foreach (Regex r in episodeExpressionsInASeasonFolder)
+ {
+ Match m = r.Match(fl);
+ if (m.Success)
+ return m.Groups["epnumber"].Value;
+ }
+
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/MediaBrowser.TV/packages.config b/MediaBrowser.TV/packages.config new file mode 100644 index 000000000..9bfda3802 --- /dev/null +++ b/MediaBrowser.TV/packages.config @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="Newtonsoft.Json" version="4.5.7" targetFramework="net45" />
+</packages>
\ No newline at end of file diff --git a/MediaBrowser.sln b/MediaBrowser.sln new file mode 100644 index 000000000..485214d71 --- /dev/null +++ b/MediaBrowser.sln @@ -0,0 +1,77 @@ +
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{9B1DDD79-5134-4DF3-ACE3-D1957A7350D8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Controller", "MediaBrowser.Controller\MediaBrowser.Controller.csproj", "{17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Movies", "MediaBrowser.Movies\MediaBrowser.Movies.csproj", "{92B9F802-4415-438F-90E1-44602135EA41}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.TV", "MediaBrowser.TV\MediaBrowser.TV.csproj", "{32DFC600-CD2F-4B2D-B39A-3B4C6C32F9B4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Configuration", "MediaBrowser.Configuration\MediaBrowser.Configuration.csproj", "{933CC468-E22B-48D8-8BCA-2E026F411CA2}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Program", "MediaBrowser.Program\MediaBrowser.Program.csproj", "{78AEA637-AF42-4F43-8E2B-0F2F0E2931F3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Api", "MediaBrowser.Api\MediaBrowser.Api.csproj", "{4FD51AC5-2C16-4308-A993-C3A84F3B4582}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Common", "MediaBrowser.Common\MediaBrowser.Common.csproj", "{9142EEFA-7570-41E1-BFCC-468BB571AF2F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.InternetProviders", "MediaBrowser.InternetProviders\MediaBrowser.InternetProviders.csproj", "{5758B2C7-949A-421D-B268-70A950CF8741}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.HtmlBrowser", "MediaBrowser.HtmlBrowser\MediaBrowser.HtmlBrowser.csproj", "{99B4CFE8-1441-4F0D-8C40-A70D0DD372ED}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17E1F4E6-8ABD-4FE5-9ECF-43D4B6087BA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {32DFC600-CD2F-4B2D-B39A-3B4C6C32F9B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {32DFC600-CD2F-4B2D-B39A-3B4C6C32F9B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {32DFC600-CD2F-4B2D-B39A-3B4C6C32F9B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {32DFC600-CD2F-4B2D-B39A-3B4C6C32F9B4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FD51AC5-2C16-4308-A993-C3A84F3B4582}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5758B2C7-949A-421D-B268-70A950CF8741}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5758B2C7-949A-421D-B268-70A950CF8741}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5758B2C7-949A-421D-B268-70A950CF8741}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5758B2C7-949A-421D-B268-70A950CF8741}.Release|Any CPU.Build.0 = Release|Any CPU
+ {78AEA637-AF42-4F43-8E2B-0F2F0E2931F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {78AEA637-AF42-4F43-8E2B-0F2F0E2931F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {78AEA637-AF42-4F43-8E2B-0F2F0E2931F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {78AEA637-AF42-4F43-8E2B-0F2F0E2931F3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9142EEFA-7570-41E1-BFCC-468BB571AF2F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {92B9F802-4415-438F-90E1-44602135EA41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92B9F802-4415-438F-90E1-44602135EA41}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92B9F802-4415-438F-90E1-44602135EA41}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92B9F802-4415-438F-90E1-44602135EA41}.Release|Any CPU.Build.0 = Release|Any CPU
+ {933CC468-E22B-48D8-8BCA-2E026F411CA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {933CC468-E22B-48D8-8BCA-2E026F411CA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {933CC468-E22B-48D8-8BCA-2E026F411CA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {933CC468-E22B-48D8-8BCA-2E026F411CA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {99B4CFE8-1441-4F0D-8C40-A70D0DD372ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {99B4CFE8-1441-4F0D-8C40-A70D0DD372ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {99B4CFE8-1441-4F0D-8C40-A70D0DD372ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {99B4CFE8-1441-4F0D-8C40-A70D0DD372ED}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9B1DDD79-5134-4DF3-ACE3-D1957A7350D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9B1DDD79-5134-4DF3-ACE3-D1957A7350D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9B1DDD79-5134-4DF3-ACE3-D1957A7350D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9B1DDD79-5134-4DF3-ACE3-D1957A7350D8}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(Performance) = preSolution
+ HasPerformanceSessions = true
+ EndGlobalSection
+EndGlobal
|
