mirror of
https://github.com/jellyfin/jellyfin.git
synced 2026-04-20 17:14:42 +01:00
Initial check-in
This commit is contained in:
94
MediaBrowser.Controller/Events/ItemResolveEventArgs.cs
Normal file
94
MediaBrowser.Controller/Events/ItemResolveEventArgs.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
152
MediaBrowser.Controller/IO/DirectoryWatchers.cs
Normal file
152
MediaBrowser.Controller/IO/DirectoryWatchers.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
182
MediaBrowser.Controller/IO/Shortcut.cs
Normal file
182
MediaBrowser.Controller/IO/Shortcut.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
258
MediaBrowser.Controller/Kernel.cs
Normal file
258
MediaBrowser.Controller/Kernel.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
326
MediaBrowser.Controller/Library/ItemController.cs
Normal file
326
MediaBrowser.Controller/Library/ItemController.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
MediaBrowser.Controller/Library/ItemDataCache.cs
Normal file
32
MediaBrowser.Controller/Library/ItemDataCache.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
92
MediaBrowser.Controller/MediaBrowser.Controller.csproj
Normal file
92
MediaBrowser.Controller/MediaBrowser.Controller.csproj
Normal 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>
|
||||
14
MediaBrowser.Controller/Net/CollectionExtensions.cs
Normal file
14
MediaBrowser.Controller/Net/CollectionExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
MediaBrowser.Controller/Net/HttpServer.cs
Normal file
47
MediaBrowser.Controller/Net/HttpServer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
MediaBrowser.Controller/Net/Request.cs
Normal file
18
MediaBrowser.Controller/Net/Request.cs
Normal 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()); }
|
||||
}
|
||||
}
|
||||
}
|
||||
37
MediaBrowser.Controller/Net/RequestContext.cs
Normal file
37
MediaBrowser.Controller/Net/RequestContext.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
MediaBrowser.Controller/Net/Response.cs
Normal file
49
MediaBrowser.Controller/Net/Response.cs
Normal 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))
|
||||
{
|
||||
}
|
||||
}*/
|
||||
}
|
||||
20
MediaBrowser.Controller/Net/StreamExtensions.cs
Normal file
20
MediaBrowser.Controller/Net/StreamExtensions.cs
Normal 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;
|
||||
})();
|
||||
}
|
||||
}
|
||||
}
|
||||
36
MediaBrowser.Controller/Properties/AssemblyInfo.cs
Normal file
36
MediaBrowser.Controller/Properties/AssemblyInfo.cs
Normal 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")]
|
||||
44
MediaBrowser.Controller/Resolvers/AudioResolver.cs
Normal file
44
MediaBrowser.Controller/Resolvers/AudioResolver.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
146
MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
Normal file
146
MediaBrowser.Controller/Resolvers/BaseItemResolver.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
45
MediaBrowser.Controller/Resolvers/FolderResolver.cs
Normal file
45
MediaBrowser.Controller/Resolvers/FolderResolver.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
114
MediaBrowser.Controller/Resolvers/VideoResolver.cs
Normal file
114
MediaBrowser.Controller/Resolvers/VideoResolver.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
MediaBrowser.Controller/UserController.cs
Normal file
60
MediaBrowser.Controller/UserController.cs
Normal 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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
591
MediaBrowser.Controller/Xml/BaseItemXmlParser.cs
Normal file
591
MediaBrowser.Controller/Xml/BaseItemXmlParser.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
MediaBrowser.Controller/Xml/FolderXmlParser.cs
Normal file
8
MediaBrowser.Controller/Xml/FolderXmlParser.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
using MediaBrowser.Model.Entities;
|
||||
|
||||
namespace MediaBrowser.Controller.Xml
|
||||
{
|
||||
public class FolderXmlParser : BaseItemXmlParser<Folder>
|
||||
{
|
||||
}
|
||||
}
|
||||
74
MediaBrowser.Controller/Xml/XmlExtensions.cs
Normal file
74
MediaBrowser.Controller/Xml/XmlExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
5
MediaBrowser.Controller/packages.config
Normal file
5
MediaBrowser.Controller/packages.config
Normal 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>
|
||||
Reference in New Issue
Block a user