Browse Source

Initial check-in

LukePulverenti Luke Pulverenti luke pulverenti 13 years ago
commit
b50f78e5da
93 changed files with 5325 additions and 0 deletions
  1. 37 0
      .hgignore
  2. 14 0
      MediaBrowser.Api/HttpHandlers/ImageHandler.cs
  3. 93 0
      MediaBrowser.Api/HttpHandlers/ItemHandler.cs
  4. 79 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  5. 33 0
      MediaBrowser.Api/Plugin.cs
  6. 36 0
      MediaBrowser.Api/Properties/AssemblyInfo.cs
  7. 4 0
      MediaBrowser.Api/packages.config
  8. 9 0
      MediaBrowser.Common/Events/GenericItemEventArgs.cs
  9. 55 0
      MediaBrowser.Common/Json/JsonSerializer.cs
  10. 80 0
      MediaBrowser.Common/Logging/BaseLogger.cs
  11. 55 0
      MediaBrowser.Common/Logging/FileLogger.cs
  12. 118 0
      MediaBrowser.Common/Logging/LogRow.cs
  13. 18 0
      MediaBrowser.Common/Logging/LogSeverity.cs
  14. 38 0
      MediaBrowser.Common/Logging/Logger.cs
  15. 68 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  16. 58 0
      MediaBrowser.Common/Plugins/BasePlugin.cs
  17. 18 0
      MediaBrowser.Common/Plugins/BasePluginConfiguration.cs
  18. 90 0
      MediaBrowser.Common/Plugins/PluginController.cs
  19. 36 0
      MediaBrowser.Common/Properties/AssemblyInfo.cs
  20. 4 0
      MediaBrowser.Common/packages.config
  21. 77 0
      MediaBrowser.Configuration/MediaBrowser.Configuration.csproj
  22. 11 0
      MediaBrowser.Configuration/Plugin.cs
  23. 36 0
      MediaBrowser.Configuration/Properties/AssemblyInfo.cs
  24. 4 0
      MediaBrowser.Configuration/packages.config
  25. 94 0
      MediaBrowser.Controller/Events/ItemResolveEventArgs.cs
  26. 152 0
      MediaBrowser.Controller/IO/DirectoryWatchers.cs
  27. 182 0
      MediaBrowser.Controller/IO/Shortcut.cs
  28. 258 0
      MediaBrowser.Controller/Kernel.cs
  29. 326 0
      MediaBrowser.Controller/Library/ItemController.cs
  30. 32 0
      MediaBrowser.Controller/Library/ItemDataCache.cs
  31. 92 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  32. 14 0
      MediaBrowser.Controller/Net/CollectionExtensions.cs
  33. 47 0
      MediaBrowser.Controller/Net/HttpServer.cs
  34. 18 0
      MediaBrowser.Controller/Net/Request.cs
  35. 37 0
      MediaBrowser.Controller/Net/RequestContext.cs
  36. 49 0
      MediaBrowser.Controller/Net/Response.cs
  37. 20 0
      MediaBrowser.Controller/Net/StreamExtensions.cs
  38. 36 0
      MediaBrowser.Controller/Properties/AssemblyInfo.cs
  39. 44 0
      MediaBrowser.Controller/Resolvers/AudioResolver.cs
  40. 146 0
      MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
  41. 45 0
      MediaBrowser.Controller/Resolvers/FolderResolver.cs
  42. 114 0
      MediaBrowser.Controller/Resolvers/VideoResolver.cs
  43. 60 0
      MediaBrowser.Controller/UserController.cs
  44. 591 0
      MediaBrowser.Controller/Xml/BaseItemXmlParser.cs
  45. 8 0
      MediaBrowser.Controller/Xml/FolderXmlParser.cs
  46. 74 0
      MediaBrowser.Controller/Xml/XmlExtensions.cs
  47. 5 0
      MediaBrowser.Controller/packages.config
  48. 72 0
      MediaBrowser.HtmlBrowser/MediaBrowser.HtmlBrowser.csproj
  49. 16 0
      MediaBrowser.HtmlBrowser/Plugin.cs
  50. 36 0
      MediaBrowser.HtmlBrowser/Properties/AssemblyInfo.cs
  51. 4 0
      MediaBrowser.HtmlBrowser/packages.config
  52. 79 0
      MediaBrowser.InternetProviders/MediaBrowser.InternetProviders.csproj
  53. 11 0
      MediaBrowser.InternetProviders/Plugin.cs
  54. 8 0
      MediaBrowser.InternetProviders/PluginConfiguration.cs
  55. 36 0
      MediaBrowser.InternetProviders/Properties/AssemblyInfo.cs
  56. 17 0
      MediaBrowser.Model/Configuration/Configuration.cs
  57. 12 0
      MediaBrowser.Model/Entities/Audio.cs
  58. 64 0
      MediaBrowser.Model/Entities/BaseItem.cs
  59. 96 0
      MediaBrowser.Model/Entities/Folder.cs
  60. 22 0
      MediaBrowser.Model/Entities/Person.cs
  61. 12 0
      MediaBrowser.Model/Entities/PlaybackStatus.cs
  62. 42 0
      MediaBrowser.Model/Entities/Video.cs
  63. 73 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  64. 36 0
      MediaBrowser.Model/Properties/AssemblyInfo.cs
  65. 19 0
      MediaBrowser.Model/Users/User.cs
  66. 23 0
      MediaBrowser.Model/Users/UserItemData.cs
  67. 4 0
      MediaBrowser.Model/packages.config
  68. 10 0
      MediaBrowser.Movies/Entities/BoxSet.cs
  69. 14 0
      MediaBrowser.Movies/Entities/Movie.cs
  70. 75 0
      MediaBrowser.Movies/MediaBrowser.Movies.csproj
  71. 35 0
      MediaBrowser.Movies/Metadata/MovieXmlParser.cs
  72. 24 0
      MediaBrowser.Movies/Plugin.cs
  73. 36 0
      MediaBrowser.Movies/Properties/AssemblyInfo.cs
  74. 24 0
      MediaBrowser.Movies/Resolvers/BoxSetResolver.cs
  75. 83 0
      MediaBrowser.Movies/Resolvers/MovieResolver.cs
  76. 17 0
      MediaBrowser.Program/App.config
  77. 69 0
      MediaBrowser.Program/MediaBrowser.Program.csproj
  78. 48 0
      MediaBrowser.Program/Program.cs
  79. 36 0
      MediaBrowser.Program/Properties/AssemblyInfo.cs
  80. 13 0
      MediaBrowser.TV/Entities/Episode.cs
  81. 16 0
      MediaBrowser.TV/Entities/Season.cs
  82. 12 0
      MediaBrowser.TV/Entities/Series.cs
  83. 86 0
      MediaBrowser.TV/MediaBrowser.TV.csproj
  84. 62 0
      MediaBrowser.TV/Metadata/EpisodeXmlParser.cs
  85. 47 0
      MediaBrowser.TV/Metadata/SeriesXmlParser.cs
  86. 44 0
      MediaBrowser.TV/Plugin.cs
  87. 36 0
      MediaBrowser.TV/Properties/AssemblyInfo.cs
  88. 83 0
      MediaBrowser.TV/Resolvers/EpisodeResolver.cs
  89. 33 0
      MediaBrowser.TV/Resolvers/SeasonResolver.cs
  90. 40 0
      MediaBrowser.TV/Resolvers/SeriesResolver.cs
  91. 104 0
      MediaBrowser.TV/TVUtils.cs
  92. 4 0
      MediaBrowser.TV/packages.config
  93. 77 0
      MediaBrowser.sln

+ 37 - 0
.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

+ 14 - 0
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
+    {
+    }
+}

+ 93 - 0
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();
+            }
+        }
+    }
+}

+ 79 - 0
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>

+ 33 - 0
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();
+        }
+    }
+}

+ 36 - 0
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")]

+ 4 - 0
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>

+ 9 - 0
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; }
+    }
+}

+ 55 - 0
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);
+                }
+            }
+        }
+    }
+}

+ 80 - 0
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);
+    }
+}

+ 55 - 0
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();
+            }
+        }
+    }
+}

+ 118 - 0
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();
+        }
+    }
+}

+ 18 - 0
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
+    }
+}

+ 38 - 0
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);
+        }
+    }
+}

+ 68 - 0
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>

+ 58 - 0
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();
+    }
+}

+ 18 - 0
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;
+        }
+    }
+}

+ 90 - 0
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;
+        }
+    }
+}

+ 36 - 0
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")]

+ 4 - 0
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>

+ 77 - 0
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>

+ 11 - 0
MediaBrowser.Configuration/Plugin.cs

@@ -0,0 +1,11 @@
+using MediaBrowser.Common.Plugins;
+
+namespace MediaBrowser.Configuration
+{
+    public class Plugin : BasePlugin<BasePluginConfiguration>
+    {
+        protected override void InitInternal()
+        {
+        }
+    }
+}

+ 36 - 0
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")]

+ 4 - 0
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>

+ 94 - 0
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);
+            }
+        }
+
+    }
+}

+ 152 - 0
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();
+        }
+    }
+}

+ 182 - 0
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);
+        }
+    }
+}

+ 258 - 0
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();
+            }
+        }
+    }
+}

+ 326 - 0
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;
+        }
+    }
+}

+ 32 - 0
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;
+        }
+    }
+}

+ 92 - 0
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>

+ 14 - 0
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);
+        }
+    }
+}

+ 47 - 0
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);
+        }
+    }
+}

+ 18 - 0
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()); }
+        }
+    }
+}

+ 37 - 0
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);
+        }
+    }
+}

+ 49 - 0
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))
+        {
+        }
+    }*/
+}

+ 20 - 0
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;
+            })();
+        }
+    }
+}

+ 36 - 0
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")]

+ 44 - 0
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;
+            }
+
+        }
+    }
+}

+ 146 - 0
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);
+    }
+}

+ 45 - 0
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);
+            }
+        }
+    }
+}

+ 114 - 0
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;
+            }
+        }
+    }
+}

+ 60 - 0
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"));
+        }
+    }
+}

+ 591 - 0
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);
+        }
+    }
+}

+ 8 - 0
MediaBrowser.Controller/Xml/FolderXmlParser.cs

@@ -0,0 +1,8 @@
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Controller.Xml
+{
+    public class FolderXmlParser : BaseItemXmlParser<Folder>
+    {
+    }
+}

+ 74 - 0
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;
+        }
+    }
+}

+ 5 - 0
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>

+ 72 - 0
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>

+ 16 - 0
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()
+        {
+        }
+    }
+}

+ 36 - 0
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")]

+ 4 - 0
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>

+ 79 - 0
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>

+ 11 - 0
MediaBrowser.InternetProviders/Plugin.cs

@@ -0,0 +1,11 @@
+using MediaBrowser.Common.Plugins;
+
+namespace MediaBrowser.InternetProviders
+{
+    public class Plugin : BasePlugin<PluginConfiguration>
+    {
+        protected override void InitInternal()
+        {
+        }
+    }
+}

+ 8 - 0
MediaBrowser.InternetProviders/PluginConfiguration.cs

@@ -0,0 +1,8 @@
+using MediaBrowser.Common.Plugins;
+
+namespace MediaBrowser.InternetProviders
+{
+    public class PluginConfiguration : BasePluginConfiguration
+    {
+    }
+}

+ 36 - 0
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")]

+ 17 - 0
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;
+        }
+    }
+}

+ 12 - 0
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
+    {
+    }
+}

+ 64 - 0
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;
+        }
+    }
+}

+ 96 - 0
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;
+        }
+    }
+}

+ 22 - 0
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
+    }
+}

+ 12 - 0
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
+    {
+    }
+}

+ 42 - 0
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
+    }
+}

+ 73 - 0
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>

+ 36 - 0
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")]

+ 19 - 0
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; } }
+    }
+}

+ 23 - 0
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
+    }
+}

+ 4 - 0
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>

+ 10 - 0
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
+    {
+    }
+}

+ 14 - 0
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; }
+    }
+}

+ 75 - 0
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>

+ 35 - 0
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;
+            }
+        }
+    }
+}

+ 24 - 0
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>();
+        }
+    }
+}

+ 36 - 0
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")]

+ 24 - 0
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;
+        }
+    }
+}

+ 83 - 0
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);
+        }
+    }
+}

+ 17 - 0
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>

+ 69 - 0
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>

+ 48 - 0
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();
+        }
+    }
+}

+ 36 - 0
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")]

+ 13 - 0
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; }
+    }
+}

+ 16 - 0
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; }
+    }
+}

+ 12 - 0
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; }
+    }
+}

+ 86 - 0
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>

+ 62 - 0
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;
+            }
+        }
+    }
+}

+ 47 - 0
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;
+            }
+        }
+    }
+}

+ 44 - 0
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;
+        }
+    }
+}

+ 36 - 0
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")]

+ 83 - 0
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)));
+            }
+        }
+    }
+}

+ 33 - 0
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;
+        }
+    }
+}

+ 40 - 0
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);
+            }
+        }
+    }
+}

+ 104 - 0
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;
+        }
+    }
+}

+ 4 - 0
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>

+ 77 - 0
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