Initial check-in

This commit is contained in:
LukePulverenti Luke Pulverenti luke pulverenti
2012-07-12 02:55:27 -04:00
commit b50f78e5da
93 changed files with 5325 additions and 0 deletions

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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()); }
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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))
{
}
}*/
}

View File

@@ -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;
})();
}
}
}

View File

@@ -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")]

View File

@@ -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;
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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"));
}
}
}

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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;
}
}
}

View File

@@ -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>