Merge with default

This commit is contained in:
ebr11 Eric Reed spam 2012-09-20 11:28:02 -04:00
commit e2ae376eef
36 changed files with 622 additions and 707 deletions

View File

@ -170,7 +170,7 @@ namespace MediaBrowser.Api
dto.TrailerUrl = item.TrailerUrl; dto.TrailerUrl = item.TrailerUrl;
dto.Type = item.GetType().Name; dto.Type = item.GetType().Name;
dto.UserRating = item.UserRating; dto.CommunityRating = item.CommunityRating;
dto.UserData = GetDtoUserItemData(item.GetUserData(user, false)); dto.UserData = GetDtoUserItemData(item.GetUserData(user, false));

View File

@ -90,14 +90,15 @@ namespace MediaBrowser.Api.HttpHandlers
} }
} }
public override Task<string> GetContentType() protected override Task<ResponseInfo> GetResponseInfo()
{ {
return Task.FromResult(MimeTypes.GetMimeType("." + GetConversionOutputFormat())); ResponseInfo info = new ResponseInfo
} {
ContentType = MimeTypes.GetMimeType("." + GetConversionOutputFormat()),
CompressResponse = false
};
public override bool ShouldCompressResponse(string contentType) return Task.FromResult<ResponseInfo>(info);
{
return false;
} }
public override Task ProcessRequest(HttpListenerContext ctx) public override Task ProcessRequest(HttpListenerContext ctx)

View File

@ -1,13 +1,12 @@
using MediaBrowser.Common.Logging; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
using System; using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -22,6 +21,7 @@ namespace MediaBrowser.Api.HttpHandlers
} }
private string _imagePath; private string _imagePath;
private async Task<string> GetImagePath() private async Task<string> GetImagePath()
{ {
_imagePath = _imagePath ?? await DiscoverImagePath(); _imagePath = _imagePath ?? await DiscoverImagePath();
@ -29,121 +29,101 @@ namespace MediaBrowser.Api.HttpHandlers
return _imagePath; return _imagePath;
} }
private BaseEntity _sourceEntity;
private async Task<BaseEntity> GetSourceEntity()
{
if (_sourceEntity == null)
{
if (!string.IsNullOrEmpty(QueryString["personname"]))
{
_sourceEntity =
await Kernel.Instance.ItemController.GetPerson(QueryString["personname"]).ConfigureAwait(false);
}
else if (!string.IsNullOrEmpty(QueryString["genre"]))
{
_sourceEntity =
await Kernel.Instance.ItemController.GetGenre(QueryString["genre"]).ConfigureAwait(false);
}
else if (!string.IsNullOrEmpty(QueryString["year"]))
{
_sourceEntity =
await
Kernel.Instance.ItemController.GetYear(int.Parse(QueryString["year"])).ConfigureAwait(false);
}
else if (!string.IsNullOrEmpty(QueryString["studio"]))
{
_sourceEntity =
await Kernel.Instance.ItemController.GetStudio(QueryString["studio"]).ConfigureAwait(false);
}
else if (!string.IsNullOrEmpty(QueryString["userid"]))
{
_sourceEntity = ApiService.GetUserById(QueryString["userid"], false);
}
else
{
_sourceEntity = ApiService.GetItemById(QueryString["id"]);
}
}
return _sourceEntity;
}
private async Task<string> DiscoverImagePath() private async Task<string> DiscoverImagePath()
{ {
string personName = QueryString["personname"]; var entity = await GetSourceEntity().ConfigureAwait(false);
if (!string.IsNullOrEmpty(personName)) return ImageProcessor.GetImagePath(entity, ImageType, ImageIndex);
{
return (await Kernel.Instance.ItemController.GetPerson(personName).ConfigureAwait(false)).PrimaryImagePath;
}
string genreName = QueryString["genre"];
if (!string.IsNullOrEmpty(genreName))
{
return (await Kernel.Instance.ItemController.GetGenre(genreName).ConfigureAwait(false)).PrimaryImagePath;
}
string year = QueryString["year"];
if (!string.IsNullOrEmpty(year))
{
return (await Kernel.Instance.ItemController.GetYear(int.Parse(year)).ConfigureAwait(false)).PrimaryImagePath;
}
string studio = QueryString["studio"];
if (!string.IsNullOrEmpty(studio))
{
return (await Kernel.Instance.ItemController.GetStudio(studio).ConfigureAwait(false)).PrimaryImagePath;
}
string userId = QueryString["userid"];
if (!string.IsNullOrEmpty(userId))
{
return ApiService.GetUserById(userId, false).PrimaryImagePath;
}
BaseItem item = ApiService.GetItemById(QueryString["id"]);
string imageIndex = QueryString["index"];
int index = string.IsNullOrEmpty(imageIndex) ? 0 : int.Parse(imageIndex);
return GetImagePathFromTypes(item, ImageType, index);
} }
private Stream _sourceStream; protected async override Task<ResponseInfo> GetResponseInfo()
private async Task<Stream> GetSourceStream()
{ {
await EnsureSourceStream().ConfigureAwait(false); string path = await GetImagePath().ConfigureAwait(false);
return _sourceStream;
}
private bool _sourceStreamEnsured; ResponseInfo info = new ResponseInfo
private async Task EnsureSourceStream()
{
if (!_sourceStreamEnsured)
{ {
try CacheDuration = TimeSpan.FromDays(365),
ContentType = MimeTypes.GetMimeType(path)
};
DateTime? date = File.GetLastWriteTimeUtc(path);
// If the file does not exist it will return jan 1, 1601
// http://msdn.microsoft.com/en-us/library/system.io.file.getlastwritetimeutc.aspx
if (date.Value.Year == 1601)
{
if (!File.Exists(path))
{ {
_sourceStream = File.OpenRead(await GetImagePath().ConfigureAwait(false)); info.StatusCode = 404;
} date = null;
catch (FileNotFoundException ex)
{
StatusCode = 404;
Logger.LogException(ex);
}
catch (DirectoryNotFoundException ex)
{
StatusCode = 404;
Logger.LogException(ex);
}
catch (UnauthorizedAccessException ex)
{
StatusCode = 403;
Logger.LogException(ex);
}
finally
{
_sourceStreamEnsured = true;
} }
} }
info.DateLastModified = date;
return info;
} }
public async override Task<string> GetContentType() private int ImageIndex
{
await EnsureSourceStream().ConfigureAwait(false);
if (await GetSourceStream().ConfigureAwait(false) == null)
{
return null;
}
return MimeTypes.GetMimeType(await GetImagePath().ConfigureAwait(false));
}
public override TimeSpan CacheDuration
{ {
get get
{ {
return TimeSpan.FromDays(365); string val = QueryString["index"];
if (string.IsNullOrEmpty(val))
{
return 0;
}
return int.Parse(val);
} }
} }
protected async override Task<DateTime?> GetLastDateModified()
{
await EnsureSourceStream().ConfigureAwait(false);
if (await GetSourceStream().ConfigureAwait(false) == null)
{
return null;
}
return File.GetLastWriteTimeUtc(await GetImagePath().ConfigureAwait(false));
}
private int? Height private int? Height
{ {
get get
@ -236,33 +216,9 @@ namespace MediaBrowser.Api.HttpHandlers
protected override async Task WriteResponseToOutputStream(Stream stream) protected override async Task WriteResponseToOutputStream(Stream stream)
{ {
ImageProcessor.ProcessImage(await GetSourceStream().ConfigureAwait(false), stream, Width, Height, MaxWidth, MaxHeight, Quality); var entity = await GetSourceEntity().ConfigureAwait(false);
}
private string GetImagePathFromTypes(BaseItem item, ImageType imageType, int imageIndex) ImageProcessor.ProcessImage(entity, ImageType, ImageIndex, stream, Width, Height, MaxWidth, MaxHeight, Quality);
{
if (imageType == ImageType.Logo)
{
return item.LogoImagePath;
}
if (imageType == ImageType.Backdrop)
{
return item.BackdropImagePaths.ElementAt(imageIndex);
}
if (imageType == ImageType.Banner)
{
return item.BannerImagePath;
}
if (imageType == ImageType.Art)
{
return item.ArtImagePath;
}
if (imageType == ImageType.Thumbnail)
{
return item.ThumbnailImagePath;
}
return item.PrimaryImagePath;
} }
} }
} }

View File

@ -16,7 +16,7 @@ namespace MediaBrowser.Api.HttpHandlers
return ApiService.IsApiUrlMatch("pluginassembly", request); return ApiService.IsApiUrlMatch("pluginassembly", request);
} }
public override Task<string> GetContentType() protected override Task<ResponseInfo> GetResponseInfo()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@ -39,18 +39,15 @@ namespace MediaBrowser.Api.HttpHandlers
return Task.FromResult(Plugin.Configuration); return Task.FromResult(Plugin.Configuration);
} }
public override TimeSpan CacheDuration protected override async Task<ResponseInfo> GetResponseInfo()
{ {
get var info = await base.GetResponseInfo().ConfigureAwait(false);
{
return TimeSpan.FromDays(7);
}
}
protected override Task<DateTime?> GetLastDateModified() info.DateLastModified = Plugin.ConfigurationDateLastModified;
{
return Task.FromResult<DateTime?>(Plugin.ConfigurationDateLastModified);
}
info.CacheDuration = TimeSpan.FromDays(7);
return info;
}
} }
} }

View File

@ -22,17 +22,16 @@ namespace MediaBrowser.Api.HttpHandlers
return Task.FromResult(Kernel.Instance.Configuration); return Task.FromResult(Kernel.Instance.Configuration);
} }
public override TimeSpan CacheDuration protected override async Task<ResponseInfo> GetResponseInfo()
{ {
get var info = await base.GetResponseInfo().ConfigureAwait(false);
{
return TimeSpan.FromDays(7);
}
}
protected override Task<DateTime?> GetLastDateModified() info.DateLastModified =
{ File.GetLastWriteTimeUtc(Kernel.Instance.ApplicationPaths.SystemConfigurationFilePath);
return Task.FromResult<DateTime?>(File.GetLastWriteTimeUtc(Kernel.Instance.ApplicationPaths.SystemConfigurationFilePath));
info.CacheDuration = TimeSpan.FromDays(7);
return info;
} }
} }
} }

View File

@ -1,5 +1,5 @@
using MediaBrowser.Common.Drawing; using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO; using MediaBrowser.Model.DTO;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -7,7 +7,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Drawing; using System.Drawing;
using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;

View File

@ -3,6 +3,7 @@ using MediaBrowser.Controller;
using MediaBrowser.Model.Weather; using MediaBrowser.Model.Weather;
using System; using System;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Linq;
using System.Net; using System.Net;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -27,18 +28,16 @@ namespace MediaBrowser.Api.HttpHandlers
zipCode = Kernel.Instance.Configuration.WeatherZipCode; zipCode = Kernel.Instance.Configuration.WeatherZipCode;
} }
return Kernel.Instance.WeatherClient.GetWeatherInfoAsync(zipCode); return Kernel.Instance.WeatherProviders.First().GetWeatherInfoAsync(zipCode);
} }
/// <summary> protected override async Task<ResponseInfo> GetResponseInfo()
/// Tell the client to cache the weather info for 15 minutes
/// </summary>
public override TimeSpan CacheDuration
{ {
get var info = await base.GetResponseInfo().ConfigureAwait(false);
{
return TimeSpan.FromMinutes(15); info.CacheDuration = TimeSpan.FromMinutes(15);
}
return info;
} }
} }
} }

View File

@ -83,7 +83,6 @@
<Compile Include="HttpHandlers\WeatherHandler.cs" /> <Compile Include="HttpHandlers\WeatherHandler.cs" />
<Compile Include="HttpHandlers\YearHandler.cs" /> <Compile Include="HttpHandlers\YearHandler.cs" />
<Compile Include="HttpHandlers\YearsHandler.cs" /> <Compile Include="HttpHandlers\YearsHandler.cs" />
<Compile Include="ImageProcessor.cs" />
<Compile Include="Plugin.cs" /> <Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup> </ItemGroup>

View File

@ -1,5 +1,4 @@
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Plugins;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
namespace MediaBrowser.Api namespace MediaBrowser.Api

View File

@ -0,0 +1,12 @@
using System;
namespace MediaBrowser.Common.Events
{
/// <summary>
/// Provides a generic EventArgs subclass that can hold any kind of object
/// </summary>
public class GenericEventArgs<T> : EventArgs
{
public T Argument { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Logging; using MediaBrowser.Common.Events;
using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Net; using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers; using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Common.Plugins; using MediaBrowser.Common.Plugins;
@ -9,7 +10,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Hosting;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -24,6 +24,34 @@ namespace MediaBrowser.Common.Kernel
where TConfigurationType : BaseApplicationConfiguration, new() where TConfigurationType : BaseApplicationConfiguration, new()
where TApplicationPathsType : BaseApplicationPaths, new() where TApplicationPathsType : BaseApplicationPaths, new()
{ {
#region ReloadBeginning Event
/// <summary>
/// Fires whenever the kernel begins reloading
/// </summary>
public event EventHandler<GenericEventArgs<IProgress<TaskProgress>>> ReloadBeginning;
private void OnReloadBeginning(IProgress<TaskProgress> progress)
{
if (ReloadBeginning != null)
{
ReloadBeginning(this, new GenericEventArgs<IProgress<TaskProgress>> { Argument = progress });
}
}
#endregion
#region ReloadCompleted Event
/// <summary>
/// Fires whenever the kernel completes reloading
/// </summary>
public event EventHandler<GenericEventArgs<IProgress<TaskProgress>>> ReloadCompleted;
private void OnReloadCompleted(IProgress<TaskProgress> progress)
{
if (ReloadCompleted != null)
{
ReloadCompleted(this, new GenericEventArgs<IProgress<TaskProgress>> { Argument = progress });
}
}
#endregion
/// <summary> /// <summary>
/// Gets the current configuration /// Gets the current configuration
/// </summary> /// </summary>
@ -43,6 +71,12 @@ namespace MediaBrowser.Common.Kernel
[ImportMany(typeof(BaseHandler))] [ImportMany(typeof(BaseHandler))]
private IEnumerable<BaseHandler> HttpHandlers { get; set; } private IEnumerable<BaseHandler> HttpHandlers { get; set; }
/// <summary>
/// Gets the list of currently registered Loggers
/// </summary>
[ImportMany(typeof(BaseLogger))]
public IEnumerable<BaseLogger> Loggers { get; set; }
/// <summary> /// <summary>
/// Both the Ui and server will have a built-in HttpServer. /// Both the Ui and server will have a built-in HttpServer.
/// People will inevitably want remote control apps so it's needed in the Ui too. /// People will inevitably want remote control apps so it's needed in the Ui too.
@ -54,6 +88,8 @@ namespace MediaBrowser.Common.Kernel
/// </summary> /// </summary>
private IDisposable HttpListener { get; set; } private IDisposable HttpListener { get; set; }
private CompositionContainer CompositionContainer { get; set; }
protected virtual string HttpServerUrlPrefix protected virtual string HttpServerUrlPrefix
{ {
get get
@ -72,13 +108,13 @@ namespace MediaBrowser.Common.Kernel
/// </summary> /// </summary>
public async Task Init(IProgress<TaskProgress> progress) public async Task Init(IProgress<TaskProgress> progress)
{ {
Logger.Kernel = this;
// Performs initializations that only occur once // Performs initializations that only occur once
InitializeInternal(progress); InitializeInternal(progress);
// Performs initializations that can be reloaded at anytime // Performs initializations that can be reloaded at anytime
await Reload(progress).ConfigureAwait(false); await Reload(progress).ConfigureAwait(false);
progress.Report(new TaskProgress { Description = "Loading Complete" });
} }
/// <summary> /// <summary>
@ -88,43 +124,38 @@ namespace MediaBrowser.Common.Kernel
{ {
ApplicationPaths = new TApplicationPathsType(); ApplicationPaths = new TApplicationPathsType();
ReloadLogger(); ReportProgress(progress, "Loading Configuration");
progress.Report(new TaskProgress { Description = "Loading configuration" });
ReloadConfiguration(); ReloadConfiguration();
progress.Report(new TaskProgress { Description = "Starting Http server" }); ReportProgress(progress, "Loading Http Server");
ReloadHttpServer(); ReloadHttpServer();
} }
/// <summary> /// <summary>
/// Performs initializations that can be reloaded at anytime /// Performs initializations that can be reloaded at anytime
/// </summary> /// </summary>
public virtual async Task Reload(IProgress<TaskProgress> progress) public async Task Reload(IProgress<TaskProgress> progress)
{ {
await Task.Run(() => OnReloadBeginning(progress);
{
progress.Report(new TaskProgress { Description = "Loading Plugins" });
ReloadComposableParts();
}).ConfigureAwait(false); await ReloadInternal(progress).ConfigureAwait(false);
OnReloadCompleted(progress);
ReportProgress(progress, "Kernel.Reload Complete");
} }
/// <summary> /// <summary>
/// Disposes the current logger and creates a new one /// Performs initializations that can be reloaded at anytime
/// </summary> /// </summary>
private void ReloadLogger() protected virtual async Task ReloadInternal(IProgress<TaskProgress> progress)
{ {
DisposeLogger(); await Task.Run(() =>
{
ReportProgress(progress, "Loading Plugins");
ReloadComposableParts();
DateTime now = DateTime.Now; }).ConfigureAwait(false);
string logFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "log-" + now.ToString("dMyyyy") + "-" + now.Ticks + ".log");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));
Trace.AutoFlush = true;
Logger.LoggerInstance = new TraceLogger();
} }
/// <summary> /// <summary>
@ -135,14 +166,13 @@ namespace MediaBrowser.Common.Kernel
{ {
DisposeComposableParts(); DisposeComposableParts();
var container = GetCompositionContainer(includeCurrentAssembly: true); CompositionContainer = GetCompositionContainer(includeCurrentAssembly: true);
container.ComposeParts(this); CompositionContainer.ComposeParts(this);
OnComposablePartsLoaded(); OnComposablePartsLoaded();
container.Catalog.Dispose(); CompositionContainer.Catalog.Dispose();
container.Dispose();
} }
/// <summary> /// <summary>
@ -157,8 +187,7 @@ namespace MediaBrowser.Common.Kernel
var catalog = new AggregateCatalog(pluginAssemblies.Select(a => new AssemblyCatalog(a))); var catalog = new AggregateCatalog(pluginAssemblies.Select(a => new AssemblyCatalog(a)));
// Include composable parts in the Common assembly // Include composable parts in the Common assembly
// Uncomment this if it's ever needed catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
//catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
if (includeCurrentAssembly) if (includeCurrentAssembly)
{ {
@ -174,8 +203,13 @@ namespace MediaBrowser.Common.Kernel
/// </summary> /// </summary>
protected virtual void OnComposablePartsLoaded() protected virtual void OnComposablePartsLoaded()
{ {
foreach (var logger in Loggers)
{
logger.Initialize(this);
}
// Start-up each plugin // Start-up each plugin
foreach (BasePlugin plugin in Plugins) foreach (var plugin in Plugins)
{ {
plugin.Initialize(this); plugin.Initialize(this);
} }
@ -189,17 +223,16 @@ namespace MediaBrowser.Common.Kernel
//Configuration information for anything other than server-specific configuration will have to come via the API... -ebr //Configuration information for anything other than server-specific configuration will have to come via the API... -ebr
// Deserialize config // Deserialize config
if (!File.Exists(ApplicationPaths.SystemConfigurationFilePath)) // Use try/catch to avoid the extra file system lookup using File.Exists
try
{
Configuration = XmlSerializer.DeserializeFromFile<TConfigurationType>(ApplicationPaths.SystemConfigurationFilePath);
}
catch (FileNotFoundException)
{ {
Configuration = new TConfigurationType(); Configuration = new TConfigurationType();
XmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath); XmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath);
} }
else
{
Configuration = XmlSerializer.DeserializeFromFile<TConfigurationType>(ApplicationPaths.SystemConfigurationFilePath);
}
Logger.LoggerInstance.LogSeverity = Configuration.EnableDebugLevelLogging ? LogSeverity.Debug : LogSeverity.Info;
} }
/// <summary> /// <summary>
@ -234,11 +267,9 @@ namespace MediaBrowser.Common.Kernel
{ {
Logger.LogInfo("Beginning Kernel.Dispose"); Logger.LogInfo("Beginning Kernel.Dispose");
DisposeComposableParts();
DisposeHttpServer(); DisposeHttpServer();
DisposeLogger(); DisposeComposableParts();
} }
/// <summary> /// <summary>
@ -246,22 +277,9 @@ namespace MediaBrowser.Common.Kernel
/// </summary> /// </summary>
protected virtual void DisposeComposableParts() protected virtual void DisposeComposableParts()
{ {
DisposePlugins(); if (CompositionContainer != null)
}
/// <summary>
/// Disposes all plugins
/// </summary>
private void DisposePlugins()
{
if (Plugins != null)
{ {
Logger.LogInfo("Disposing Plugins"); CompositionContainer.Dispose();
foreach (BasePlugin plugin in Plugins)
{
plugin.Dispose();
}
} }
} }
@ -283,21 +301,6 @@ namespace MediaBrowser.Common.Kernel
} }
} }
/// <summary>
/// Disposes the current Logger instance
/// </summary>
private void DisposeLogger()
{
Trace.Listeners.Clear();
if (Logger.LoggerInstance != null)
{
Logger.LogInfo("Disposing Logger");
Logger.LoggerInstance.Dispose();
}
}
/// <summary> /// <summary>
/// Gets the current application version /// Gets the current application version
/// </summary> /// </summary>
@ -309,6 +312,13 @@ namespace MediaBrowser.Common.Kernel
} }
} }
protected void ReportProgress(IProgress<TaskProgress> progress, string message)
{
progress.Report(new TaskProgress { Description = message });
Logger.LogInfo(message);
}
BaseApplicationPaths IKernel.ApplicationPaths BaseApplicationPaths IKernel.ApplicationPaths
{ {
get { return ApplicationPaths; } get { return ApplicationPaths; }
@ -322,6 +332,7 @@ namespace MediaBrowser.Common.Kernel
Task Init(IProgress<TaskProgress> progress); Task Init(IProgress<TaskProgress> progress);
Task Reload(IProgress<TaskProgress> progress); Task Reload(IProgress<TaskProgress> progress);
IEnumerable<BaseLogger> Loggers { get; }
void Dispose(); void Dispose();
} }
} }

View File

@ -1,92 +1,16 @@
using System; using MediaBrowser.Common.Kernel;
using System.Text; using System;
using System.Threading;
namespace MediaBrowser.Common.Logging namespace MediaBrowser.Common.Logging
{ {
public abstract class BaseLogger : IDisposable public abstract class BaseLogger : IDisposable
{ {
public LogSeverity LogSeverity { get; set; } public abstract void Initialize(IKernel kernel);
public abstract void LogEntry(LogRow row);
public void LogInfo(string message, params object[] paramList)
{
LogEntry(message, LogSeverity.Info, paramList);
}
public void LogDebugInfo(string message, params object[] paramList)
{
LogEntry(message, LogSeverity.Debug, paramList);
}
public void LogError(string message, params object[] paramList)
{
LogEntry(message, LogSeverity.Error, paramList);
}
public void LogException(string message, Exception exception, params object[] paramList)
{
var builder = new StringBuilder();
if (exception != null)
{
builder.AppendFormat("Exception. Type={0} Msg={1} StackTrace={3}{2}",
exception.GetType().FullName,
exception.Message,
exception.StackTrace,
Environment.NewLine);
}
message = FormatMessage(message, paramList);
LogError(string.Format("{0} ( {1} )", message, builder));
}
public void LogWarning(string message, params object[] paramList)
{
LogEntry(message, LogSeverity.Warning, paramList);
}
private string FormatMessage(string message, params object[] paramList)
{
if (paramList != null)
{
for (int i = 0; i < paramList.Length; i++)
{
message = message.Replace("{" + i + "}", paramList[i].ToString());
}
}
return message;
}
private void LogEntry(string message, LogSeverity severity, params object[] paramList)
{
if (severity < LogSeverity) return;
message = FormatMessage(message, paramList);
Thread currentThread = Thread.CurrentThread;
var row = new LogRow
{
Severity = severity,
Message = message,
ThreadId = currentThread.ManagedThreadId,
ThreadName = currentThread.Name,
Time = DateTime.Now
};
LogEntry(row);
}
protected virtual void Flush()
{
}
public virtual void Dispose() public virtual void Dispose()
{ {
Logger.LogInfo("Disposing " + GetType().Name);
} }
protected abstract void LogEntry(LogRow row);
} }
} }

View File

@ -1,4 +1,4 @@
using System; using System;
namespace MediaBrowser.Common.Logging namespace MediaBrowser.Common.Logging
{ {

View File

@ -1,24 +1,28 @@
using System; using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using MediaBrowser.Common.Kernel;
namespace MediaBrowser.Common.Logging namespace MediaBrowser.Common.Logging
{ {
public static class Logger public static class Logger
{ {
public static BaseLogger LoggerInstance { get; set; } internal static IKernel Kernel { get; set; }
public static void LogInfo(string message, params object[] paramList) public static void LogInfo(string message, params object[] paramList)
{ {
LoggerInstance.LogInfo(message, paramList); LogEntry(message, LogSeverity.Info, paramList);
} }
public static void LogDebugInfo(string message, params object[] paramList) public static void LogDebugInfo(string message, params object[] paramList)
{ {
LoggerInstance.LogDebugInfo(message, paramList); LogEntry(message, LogSeverity.Debug, paramList);
} }
public static void LogError(string message, params object[] paramList) public static void LogError(string message, params object[] paramList)
{ {
LoggerInstance.LogError(message, paramList); LogEntry(message, LogSeverity.Error, paramList);
} }
public static void LogException(Exception ex, params object[] paramList) public static void LogException(Exception ex, params object[] paramList)
@ -28,12 +32,62 @@ namespace MediaBrowser.Common.Logging
public static void LogException(string message, Exception ex, params object[] paramList) public static void LogException(string message, Exception ex, params object[] paramList)
{ {
LoggerInstance.LogException(message, ex, paramList); var builder = new StringBuilder();
if (ex != null)
{
builder.AppendFormat("Exception. Type={0} Msg={1} StackTrace={3}{2}",
ex.GetType().FullName,
ex.Message,
ex.StackTrace,
Environment.NewLine);
}
message = FormatMessage(message, paramList);
LogError(string.Format("{0} ( {1} )", message, builder));
} }
public static void LogWarning(string message, params object[] paramList) public static void LogWarning(string message, params object[] paramList)
{ {
LoggerInstance.LogWarning(message, paramList); LogEntry(message, LogSeverity.Warning, paramList);
}
private static void LogEntry(string message, LogSeverity severity, params object[] paramList)
{
message = FormatMessage(message, paramList);
Thread currentThread = Thread.CurrentThread;
var row = new LogRow
{
Severity = severity,
Message = message,
ThreadId = currentThread.ManagedThreadId,
ThreadName = currentThread.Name,
Time = DateTime.Now
};
if (Kernel.Loggers != null)
{
foreach (var logger in Kernel.Loggers)
{
logger.LogEntry(row);
}
}
}
private static string FormatMessage(string message, params object[] paramList)
{
if (paramList != null)
{
for (int i = 0; i < paramList.Length; i++)
{
message = message.Replace("{" + i + "}", paramList[i].ToString());
}
}
return message;
} }
} }
} }

View File

@ -1,37 +0,0 @@
using System;
using System.IO;
using System.Text;
namespace MediaBrowser.Common.Logging
{
/// <summary>
/// Provides a Logger that can write to any Stream
/// </summary>
public class StreamLogger : BaseLogger
{
private Stream Stream { get; set; }
public StreamLogger(Stream stream)
: base()
{
Stream = stream;
}
protected override void LogEntry(LogRow row)
{
byte[] bytes = new UTF8Encoding().GetBytes(row.ToString() + Environment.NewLine);
lock (Stream)
{
Stream.Write(bytes, 0, bytes.Length);
Stream.Flush();
}
}
public override void Dispose()
{
base.Dispose();
Stream.Dispose();
}
}
}

View File

@ -0,0 +1,38 @@
using MediaBrowser.Common.Kernel;
using System;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO;
namespace MediaBrowser.Common.Logging
{
[Export(typeof(BaseLogger))]
public class TraceFileLogger : BaseLogger
{
private TraceListener Listener { get; set; }
public override void Initialize(IKernel kernel)
{
DateTime now = DateTime.Now;
string logFilePath = Path.Combine(kernel.ApplicationPaths.LogDirectoryPath, "log-" + now.ToString("dMyyyy") + "-" + now.Ticks + ".log");
Listener = new TextWriterTraceListener(logFilePath);
Trace.Listeners.Add(Listener);
Trace.AutoFlush = true;
}
public override void Dispose()
{
base.Dispose();
Trace.Listeners.Remove(Listener);
Listener.Dispose();
}
public override void LogEntry(LogRow row)
{
Trace.WriteLine(row.ToString());
}
}
}

View File

@ -1,12 +0,0 @@
using System.Diagnostics;
namespace MediaBrowser.Common.Logging
{
public class TraceLogger : BaseLogger
{
protected override void LogEntry(LogRow row)
{
Trace.WriteLine(row.ToString());
}
}
}

View File

@ -82,9 +82,11 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Extensions\BaseExtensions.cs" /> <Compile Include="Extensions\BaseExtensions.cs" />
<Compile Include="Events\GenericEventArgs.cs" />
<Compile Include="Kernel\BaseApplicationPaths.cs" /> <Compile Include="Kernel\BaseApplicationPaths.cs" />
<Compile Include="Drawing\DrawingUtils.cs" /> <Compile Include="Logging\BaseLogger.cs" />
<Compile Include="Logging\TraceLogger.cs" /> <Compile Include="Logging\LogSeverity.cs" />
<Compile Include="Logging\TraceFileLogger.cs" />
<Compile Include="Net\Handlers\StaticFileHandler.cs" /> <Compile Include="Net\Handlers\StaticFileHandler.cs" />
<Compile Include="Net\MimeTypes.cs" /> <Compile Include="Net\MimeTypes.cs" />
<Compile Include="Plugins\BaseTheme.cs" /> <Compile Include="Plugins\BaseTheme.cs" />
@ -96,11 +98,8 @@
<Compile Include="Serialization\JsonSerializer.cs" /> <Compile Include="Serialization\JsonSerializer.cs" />
<Compile Include="Kernel\BaseKernel.cs" /> <Compile Include="Kernel\BaseKernel.cs" />
<Compile Include="Kernel\KernelContext.cs" /> <Compile Include="Kernel\KernelContext.cs" />
<Compile Include="Logging\BaseLogger.cs" />
<Compile Include="Logging\Logger.cs" /> <Compile Include="Logging\Logger.cs" />
<Compile Include="Logging\LogRow.cs" /> <Compile Include="Logging\LogRow.cs" />
<Compile Include="Logging\LogSeverity.cs" />
<Compile Include="Logging\StreamLogger.cs" />
<Compile Include="Net\Handlers\BaseEmbeddedResourceHandler.cs" /> <Compile Include="Net\Handlers\BaseEmbeddedResourceHandler.cs" />
<Compile Include="Net\Handlers\BaseHandler.cs" /> <Compile Include="Net\Handlers\BaseHandler.cs" />
<Compile Include="Net\Handlers\BaseSerializationHandler.cs" /> <Compile Include="Net\Handlers\BaseSerializationHandler.cs" />
@ -152,6 +151,7 @@
<ItemGroup> <ItemGroup>
<Resource Include="Resources\Images\spinner.gif" /> <Resource Include="Resources\Images\spinner.gif" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -13,11 +13,6 @@ namespace MediaBrowser.Common.Net.Handlers
protected string ResourcePath { get; set; } protected string ResourcePath { get; set; }
public override Task<string> GetContentType()
{
return Task.FromResult(MimeTypes.GetMimeType(ResourcePath));
}
protected override Task WriteResponseToOutputStream(Stream stream) protected override Task WriteResponseToOutputStream(Stream stream)
{ {
return GetEmbeddedResourceStream().CopyToAsync(stream); return GetEmbeddedResourceStream().CopyToAsync(stream);

View File

@ -112,32 +112,6 @@ namespace MediaBrowser.Common.Net.Handlers
} }
} }
/// <summary>
/// Gets the MIME type to include in the response headers
/// </summary>
public abstract Task<string> GetContentType();
/// <summary>
/// Gets the status code to include in the response headers
/// </summary>
protected int StatusCode { get; set; }
/// <summary>
/// Gets the cache duration to include in the response headers
/// </summary>
public virtual TimeSpan CacheDuration
{
get
{
return TimeSpan.FromTicks(0);
}
}
public virtual bool ShouldCompressResponse(string contentType)
{
return true;
}
private bool ClientSupportsCompression private bool ClientSupportsCompression
{ {
get get
@ -186,15 +160,21 @@ namespace MediaBrowser.Common.Net.Handlers
ctx.Response.Headers["Accept-Ranges"] = "bytes"; ctx.Response.Headers["Accept-Ranges"] = "bytes";
} }
// Set the initial status code ResponseInfo responseInfo = await GetResponseInfo().ConfigureAwait(false);
// When serving a range request, we need to return status code 206 to indicate a partial response body
StatusCode = SupportsByteRangeRequests && IsRangeRequest ? 206 : 200;
ctx.Response.ContentType = await GetContentType().ConfigureAwait(false); if (responseInfo.IsResponseValid)
{
// Set the initial status code
// When serving a range request, we need to return status code 206 to indicate a partial response body
responseInfo.StatusCode = SupportsByteRangeRequests && IsRangeRequest ? 206 : 200;
}
TimeSpan cacheDuration = CacheDuration; ctx.Response.ContentType = responseInfo.ContentType;
DateTime? lastDateModified = await GetLastDateModified().ConfigureAwait(false); if (!string.IsNullOrEmpty(responseInfo.Etag))
{
ctx.Response.Headers["ETag"] = responseInfo.Etag;
}
if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since")) if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
{ {
@ -203,26 +183,26 @@ namespace MediaBrowser.Common.Net.Handlers
if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"], out ifModifiedSince)) if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"], out ifModifiedSince))
{ {
// If the cache hasn't expired yet just return a 304 // If the cache hasn't expired yet just return a 304
if (IsCacheValid(ifModifiedSince.ToUniversalTime(), cacheDuration, lastDateModified)) if (IsCacheValid(ifModifiedSince.ToUniversalTime(), responseInfo.CacheDuration, responseInfo.DateLastModified))
{ {
StatusCode = 304; // ETag must also match (if supplied)
if ((responseInfo.Etag ?? string.Empty).Equals(ctx.Request.Headers["If-None-Match"] ?? string.Empty))
{
responseInfo.StatusCode = 304;
}
} }
} }
} }
await PrepareResponse().ConfigureAwait(false); Logger.LogInfo("Responding with status code {0} for url {1}", responseInfo.StatusCode, url);
Logger.LogInfo("Responding with status code {0} for url {1}", StatusCode, url); if (responseInfo.IsResponseValid)
if (IsResponseValid)
{ {
bool compressResponse = ShouldCompressResponse(ctx.Response.ContentType) && ClientSupportsCompression; await ProcessUncachedRequest(ctx, responseInfo).ConfigureAwait(false);
await ProcessUncachedRequest(ctx, compressResponse, cacheDuration, lastDateModified).ConfigureAwait(false);
} }
else else
{ {
ctx.Response.StatusCode = StatusCode; ctx.Response.StatusCode = responseInfo.StatusCode;
ctx.Response.SendChunked = false; ctx.Response.SendChunked = false;
} }
} }
@ -239,7 +219,7 @@ namespace MediaBrowser.Common.Net.Handlers
} }
} }
private async Task ProcessUncachedRequest(HttpListenerContext ctx, bool compressResponse, TimeSpan cacheDuration, DateTime? lastDateModified) private async Task ProcessUncachedRequest(HttpListenerContext ctx, ResponseInfo responseInfo)
{ {
long? totalContentLength = TotalContentLength; long? totalContentLength = TotalContentLength;
@ -258,22 +238,29 @@ namespace MediaBrowser.Common.Net.Handlers
ctx.Response.ContentLength64 = totalContentLength.Value; ctx.Response.ContentLength64 = totalContentLength.Value;
} }
var compressResponse = responseInfo.CompressResponse && ClientSupportsCompression;
// Add the compression header // Add the compression header
if (compressResponse) if (compressResponse)
{ {
ctx.Response.AddHeader("Content-Encoding", CompressionMethod); ctx.Response.AddHeader("Content-Encoding", CompressionMethod);
} }
// Add caching headers if (responseInfo.DateLastModified.HasValue)
if (cacheDuration.Ticks > 0)
{ {
CacheResponse(ctx.Response, cacheDuration, lastDateModified); ctx.Response.Headers[HttpResponseHeader.LastModified] = responseInfo.DateLastModified.Value.ToString("r");
}
// Add caching headers
if (responseInfo.CacheDuration.Ticks > 0)
{
CacheResponse(ctx.Response, responseInfo.CacheDuration);
} }
// Set the status code // Set the status code
ctx.Response.StatusCode = StatusCode; ctx.Response.StatusCode = responseInfo.StatusCode;
if (IsResponseValid) if (responseInfo.IsResponseValid)
{ {
// Finally, write the response data // Finally, write the response data
Stream outputStream = ctx.Response.OutputStream; Stream outputStream = ctx.Response.OutputStream;
@ -300,23 +287,10 @@ namespace MediaBrowser.Common.Net.Handlers
} }
} }
private void CacheResponse(HttpListenerResponse response, TimeSpan duration, DateTime? dateModified) private void CacheResponse(HttpListenerResponse response, TimeSpan duration)
{ {
DateTime now = DateTime.UtcNow;
DateTime lastModified = dateModified ?? now;
response.Headers[HttpResponseHeader.CacheControl] = "public, max-age=" + Convert.ToInt32(duration.TotalSeconds); response.Headers[HttpResponseHeader.CacheControl] = "public, max-age=" + Convert.ToInt32(duration.TotalSeconds);
response.Headers[HttpResponseHeader.Expires] = now.Add(duration).ToString("r"); response.Headers[HttpResponseHeader.Expires] = DateTime.UtcNow.Add(duration).ToString("r");
response.Headers[HttpResponseHeader.LastModified] = lastModified.ToString("r");
}
/// <summary>
/// Gives subclasses a chance to do any prep work, and also to validate data and set an error status code, if needed
/// </summary>
protected virtual Task PrepareResponse()
{
return Task.FromResult<object>(null);
} }
protected abstract Task WriteResponseToOutputStream(Stream stream); protected abstract Task WriteResponseToOutputStream(Stream stream);
@ -364,20 +338,7 @@ namespace MediaBrowser.Common.Net.Handlers
return null; return null;
} }
protected virtual Task<DateTime?> GetLastDateModified() protected abstract Task<ResponseInfo> GetResponseInfo();
{
DateTime? value = null;
return Task.FromResult(value);
}
private bool IsResponseValid
{
get
{
return StatusCode == 200 || StatusCode == 206;
}
}
private Hashtable _formValues; private Hashtable _formValues;
@ -439,4 +400,31 @@ namespace MediaBrowser.Common.Net.Handlers
return formVars; return formVars;
} }
} }
public class ResponseInfo
{
public string ContentType { get; set; }
public string Etag { get; set; }
public DateTime? DateLastModified { get; set; }
public TimeSpan CacheDuration { get; set; }
public bool CompressResponse { get; set; }
public int StatusCode { get; set; }
public ResponseInfo()
{
CacheDuration = TimeSpan.FromTicks(0);
CompressResponse = true;
StatusCode = 200;
}
public bool IsResponseValid
{
get
{
return StatusCode == 200 || StatusCode == 206;
}
}
}
} }

View File

@ -1,7 +1,7 @@
using System; using MediaBrowser.Common.Serialization;
using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using MediaBrowser.Common.Serialization;
namespace MediaBrowser.Common.Net.Handlers namespace MediaBrowser.Common.Net.Handlers
{ {
@ -23,60 +23,60 @@ namespace MediaBrowser.Common.Net.Handlers
} }
} }
public override Task<string> GetContentType() protected string ContentType
{ {
switch (SerializationFormat) get
{ {
case SerializationFormat.Jsv: switch (SerializationFormat)
return Task.FromResult("text/plain");
case SerializationFormat.Protobuf:
return Task.FromResult("application/x-protobuf");
default:
return Task.FromResult(MimeTypes.JsonMimeType);
}
}
private bool _objectToSerializeEnsured;
private T _objectToSerialize;
private async Task EnsureObjectToSerialize()
{
if (!_objectToSerializeEnsured)
{
_objectToSerialize = await GetObjectToSerialize().ConfigureAwait(false);
if (_objectToSerialize == null)
{ {
StatusCode = 404; case SerializationFormat.Jsv:
return "text/plain";
case SerializationFormat.Protobuf:
return "application/x-protobuf";
default:
return MimeTypes.JsonMimeType;
} }
_objectToSerializeEnsured = true;
} }
} }
protected override async Task<ResponseInfo> GetResponseInfo()
{
ResponseInfo info = new ResponseInfo
{
ContentType = ContentType
};
_objectToSerialize = await GetObjectToSerialize().ConfigureAwait(false);
if (_objectToSerialize == null)
{
info.StatusCode = 404;
}
return info;
}
private T _objectToSerialize;
protected abstract Task<T> GetObjectToSerialize(); protected abstract Task<T> GetObjectToSerialize();
protected override Task PrepareResponse() protected override Task WriteResponseToOutputStream(Stream stream)
{ {
return EnsureObjectToSerialize(); return Task.Run(() =>
}
protected async override Task WriteResponseToOutputStream(Stream stream)
{
await EnsureObjectToSerialize().ConfigureAwait(false);
switch (SerializationFormat)
{ {
case SerializationFormat.Jsv: switch (SerializationFormat)
JsvSerializer.SerializeToStream(_objectToSerialize, stream); {
break; case SerializationFormat.Jsv:
case SerializationFormat.Protobuf: JsvSerializer.SerializeToStream(_objectToSerialize, stream);
ProtobufSerializer.SerializeToStream(_objectToSerialize, stream); break;
break; case SerializationFormat.Protobuf:
default: ProtobufSerializer.SerializeToStream(_objectToSerialize, stream);
JsonSerializer.SerializeToStream(_objectToSerialize, stream); break;
break; default:
} JsonSerializer.SerializeToStream(_objectToSerialize, stream);
break;
}
});
} }
} }

View File

@ -33,46 +33,7 @@ namespace MediaBrowser.Common.Net.Handlers
} }
} }
private bool _sourceStreamEnsured; private Stream SourceStream { get; set; }
private Stream _sourceStream;
private Stream SourceStream
{
get
{
EnsureSourceStream();
return _sourceStream;
}
}
private void EnsureSourceStream()
{
if (!_sourceStreamEnsured)
{
try
{
_sourceStream = File.OpenRead(Path);
}
catch (FileNotFoundException ex)
{
StatusCode = 404;
Logger.LogException(ex);
}
catch (DirectoryNotFoundException ex)
{
StatusCode = 404;
Logger.LogException(ex);
}
catch (UnauthorizedAccessException ex)
{
StatusCode = 403;
Logger.LogException(ex);
}
finally
{
_sourceStreamEnsured = true;
}
}
}
protected override bool SupportsByteRangeRequests protected override bool SupportsByteRangeRequests
{ {
@ -82,7 +43,7 @@ namespace MediaBrowser.Common.Net.Handlers
} }
} }
public override bool ShouldCompressResponse(string contentType) private bool ShouldCompressResponse(string contentType)
{ {
// Can't compress these // Can't compress these
if (IsRangeRequest) if (IsRangeRequest)
@ -105,29 +66,41 @@ namespace MediaBrowser.Common.Net.Handlers
return SourceStream.Length; return SourceStream.Length;
} }
protected override Task<DateTime?> GetLastDateModified() protected override Task<ResponseInfo> GetResponseInfo()
{ {
DateTime? value = null; ResponseInfo info = new ResponseInfo
{
ContentType = MimeTypes.GetMimeType(Path),
};
EnsureSourceStream(); try
{
SourceStream = File.OpenRead(Path);
}
catch (FileNotFoundException ex)
{
info.StatusCode = 404;
Logger.LogException(ex);
}
catch (DirectoryNotFoundException ex)
{
info.StatusCode = 404;
Logger.LogException(ex);
}
catch (UnauthorizedAccessException ex)
{
info.StatusCode = 403;
Logger.LogException(ex);
}
info.CompressResponse = ShouldCompressResponse(info.ContentType);
if (SourceStream != null) if (SourceStream != null)
{ {
value = File.GetLastWriteTimeUtc(Path); info.DateLastModified = File.GetLastWriteTimeUtc(Path);
} }
return Task.FromResult(value); return Task.FromResult<ResponseInfo>(info);
}
public override Task<string> GetContentType()
{
return Task.FromResult(MimeTypes.GetMimeType(Path));
}
protected override Task PrepareResponse()
{
EnsureSourceStream();
return Task.FromResult<object>(null);
} }
protected override Task WriteResponseToOutputStream(Stream stream) protected override Task WriteResponseToOutputStream(Stream stream)

View File

@ -1,4 +1,5 @@
using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Serialization; using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Plugins; using MediaBrowser.Model.Plugins;
using System; using System;
@ -200,6 +201,8 @@ namespace MediaBrowser.Common.Plugins
/// </summary> /// </summary>
public void Dispose() public void Dispose()
{ {
Logger.LogInfo("Disposing {0} Plugin", Name);
if (Context == KernelContext.Server) if (Context == KernelContext.Server)
{ {
DisposeOnServer(); DisposeOnServer();

View File

@ -5,7 +5,6 @@ using Microsoft.Shell;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
namespace MediaBrowser.Common.UI namespace MediaBrowser.Common.UI
@ -44,8 +43,6 @@ namespace MediaBrowser.Common.UI
var progress = new Progress<TaskProgress>(); var progress = new Progress<TaskProgress>();
progress.ProgressChanged += progress_ProgressChanged;
var splash = new Splash(progress); var splash = new Splash(progress);
splash.Show(); splash.Show();
@ -56,8 +53,6 @@ namespace MediaBrowser.Common.UI
await Kernel.Init(progress); await Kernel.Init(progress);
progress.ProgressChanged -= progress_ProgressChanged;
Logger.LogInfo("Kernel.Init completed in {0} seconds.", (DateTime.UtcNow - now).TotalSeconds); Logger.LogInfo("Kernel.Init completed in {0} seconds.", (DateTime.UtcNow - now).TotalSeconds);
splash.Close(); splash.Close();
@ -69,12 +64,7 @@ namespace MediaBrowser.Common.UI
} }
catch (Exception ex) catch (Exception ex)
{ {
progress.ProgressChanged -= progress_ProgressChanged; Logger.LogException(ex);
if (Logger.LoggerInstance != null)
{
Logger.LogException(ex);
}
MessageBox.Show("There was an error launching Media Browser: " + ex.Message); MessageBox.Show("There was an error launching Media Browser: " + ex.Message);
splash.Close(); splash.Close();
@ -84,44 +74,8 @@ namespace MediaBrowser.Common.UI
} }
} }
public async Task ReloadKernel()
{
var progress = new Progress<TaskProgress>();
progress.ProgressChanged += progress_ProgressChanged;
try
{
DateTime now = DateTime.UtcNow;
await Kernel.Reload(progress);
progress.ProgressChanged -= progress_ProgressChanged;
Logger.LogInfo("Kernel.Reload completed in {0} seconds.", (DateTime.UtcNow - now).TotalSeconds);
}
catch (Exception ex)
{
progress.ProgressChanged -= progress_ProgressChanged;
Logger.LogException(ex);
// Shutdown the app with an error code
Shutdown(1);
}
}
void progress_ProgressChanged(object sender, TaskProgress e)
{
if (Logger.LoggerInstance != null)
{
Logger.LogInfo(e.Description);
}
}
protected virtual void OnKernelLoaded() protected virtual void OnKernelLoaded()
{ {
} }
protected override void OnExit(ExitEventArgs e) protected override void OnExit(ExitEventArgs e)

View File

@ -1,7 +1,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
namespace MediaBrowser.Common.Drawing namespace MediaBrowser.Controller.Drawing
{ {
public static class DrawingUtils public static class DrawingUtils
{ {

View File

@ -1,26 +1,33 @@
using MediaBrowser.Common.Drawing; using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using System; using System;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using System.Linq;
namespace MediaBrowser.Api namespace MediaBrowser.Controller.Drawing
{ {
public static class ImageProcessor public static class ImageProcessor
{ {
/// <summary> /// <summary>
/// Resizes an image from a source stream and saves the result to an output stream /// Processes an image by resizing to target dimensions
/// </summary> /// </summary>
/// <param name="entity">The entity that owns the image</param>
/// <param name="imageType">The image type</param>
/// <param name="imageIndex">The image index (currently only used with backdrops)</param>
/// <param name="toStream">The stream to save the new image to</param>
/// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param> /// <param name="width">Use if a fixed width is required. Aspect ratio will be preserved.</param>
/// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param> /// <param name="height">Use if a fixed height is required. Aspect ratio will be preserved.</param>
/// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param> /// <param name="maxWidth">Use if a max width is required. Aspect ratio will be preserved.</param>
/// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param> /// <param name="maxHeight">Use if a max height is required. Aspect ratio will be preserved.</param>
/// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param> /// <param name="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public static void ProcessImage(Stream sourceImageStream, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality) public static void ProcessImage(BaseEntity entity, ImageType imageType, int imageIndex, Stream toStream, int? width, int? height, int? maxWidth, int? maxHeight, int? quality)
{ {
Image originalImage = Image.FromStream(sourceImageStream); Image originalImage = Image.FromFile(GetImagePath(entity, imageType, imageIndex));
// Determine the output size based on incoming parameters
Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight); Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);
Bitmap thumbnail; Bitmap thumbnail;
@ -35,6 +42,9 @@ namespace MediaBrowser.Api
thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat); thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat);
} }
thumbnail.MakeTransparent();
// Preserve the original resolution
thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution); thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
Graphics thumbnailGraph = Graphics.FromImage(thumbnail); Graphics thumbnailGraph = Graphics.FromImage(thumbnail);
@ -47,32 +57,66 @@ namespace MediaBrowser.Api
thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height); thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height);
Write(originalImage, thumbnail, toStream, quality); ImageFormat outputFormat = originalImage.RawFormat;
// Write to the output stream
SaveImage(outputFormat, thumbnail, toStream, quality);
thumbnailGraph.Dispose(); thumbnailGraph.Dispose();
thumbnail.Dispose(); thumbnail.Dispose();
originalImage.Dispose(); originalImage.Dispose();
} }
private static void Write(Image originalImage, Image newImage, Stream toStream, int? quality) public static string GetImagePath(BaseEntity entity, ImageType imageType, int imageIndex)
{
var item = entity as BaseItem;
if (item != null)
{
if (imageType == ImageType.Logo)
{
return item.LogoImagePath;
}
if (imageType == ImageType.Backdrop)
{
return item.BackdropImagePaths.ElementAt(imageIndex);
}
if (imageType == ImageType.Banner)
{
return item.BannerImagePath;
}
if (imageType == ImageType.Art)
{
return item.ArtImagePath;
}
if (imageType == ImageType.Thumbnail)
{
return item.ThumbnailImagePath;
}
}
return entity.PrimaryImagePath;
}
public static void SaveImage(ImageFormat outputFormat, Image newImage, Stream toStream, int? quality)
{ {
// Use special save methods for jpeg and png that will result in a much higher quality image // Use special save methods for jpeg and png that will result in a much higher quality image
// All other formats use the generic Image.Save // All other formats use the generic Image.Save
if (ImageFormat.Jpeg.Equals(originalImage.RawFormat)) if (ImageFormat.Jpeg.Equals(outputFormat))
{ {
SaveJpeg(newImage, toStream, quality); SaveJpeg(newImage, toStream, quality);
} }
else if (ImageFormat.Png.Equals(originalImage.RawFormat)) else if (ImageFormat.Png.Equals(outputFormat))
{ {
newImage.Save(toStream, ImageFormat.Png); newImage.Save(toStream, ImageFormat.Png);
} }
else else
{ {
newImage.Save(toStream, originalImage.RawFormat); newImage.Save(toStream, outputFormat);
} }
} }
private static void SaveJpeg(Image newImage, Stream target, int? quality) public static void SaveJpeg(Image image, Stream target, int? quality)
{ {
if (!quality.HasValue) if (!quality.HasValue)
{ {
@ -82,11 +126,11 @@ namespace MediaBrowser.Api
using (var encoderParameters = new EncoderParameters(1)) using (var encoderParameters = new EncoderParameters(1))
{ {
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value); encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value);
newImage.Save(target, GetImageCodeInfo("image/jpeg"), encoderParameters); image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
} }
} }
private static ImageCodecInfo GetImageCodeInfo(string mimeType) public static ImageCodecInfo GetImageCodecInfo(string mimeType)
{ {
ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders(); ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();

View File

@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Entities
public string DisplayMediaType { get; set; } public string DisplayMediaType { get; set; }
public float? UserRating { get; set; } public float? CommunityRating { get; set; }
public long? RunTimeTicks { get; set; } public long? RunTimeTicks { get; set; }
public string AspectRatio { get; set; } public string AspectRatio { get; set; }

View File

@ -1,5 +1,6 @@
using MediaBrowser.Common.Kernel; using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Logging; using MediaBrowser.Common.Logging;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
@ -43,7 +44,6 @@ namespace MediaBrowser.Controller
public static Kernel Instance { get; private set; } public static Kernel Instance { get; private set; }
public ItemController ItemController { get; private set; } public ItemController ItemController { get; private set; }
public WeatherClient WeatherClient { get; private set; }
public IEnumerable<User> Users { get; private set; } public IEnumerable<User> Users { get; private set; }
public Folder RootFolder { get; private set; } public Folder RootFolder { get; private set; }
@ -63,6 +63,12 @@ namespace MediaBrowser.Controller
get { return KernelContext.Server; } get { return KernelContext.Server; }
} }
/// <summary>
/// Gets the list of currently registered weather prvoiders
/// </summary>
[ImportMany(typeof(BaseWeatherProvider))]
public IEnumerable<BaseWeatherProvider> WeatherProviders { get; private set; }
/// <summary> /// <summary>
/// Gets the list of currently registered metadata prvoiders /// Gets the list of currently registered metadata prvoiders
/// </summary> /// </summary>
@ -101,28 +107,27 @@ namespace MediaBrowser.Controller
/// </summary> /// </summary>
protected override void InitializeInternal(IProgress<TaskProgress> progress) protected override void InitializeInternal(IProgress<TaskProgress> progress)
{ {
base.InitializeInternal(progress);
ItemController = new ItemController(); ItemController = new ItemController();
DirectoryWatchers = new DirectoryWatchers(); DirectoryWatchers = new DirectoryWatchers();
base.InitializeInternal(progress); ExtractFFMpeg();
} }
/// <summary> /// <summary>
/// Performs initializations that can be reloaded at anytime /// Performs initializations that can be reloaded at anytime
/// </summary> /// </summary>
public override async Task Reload(IProgress<TaskProgress> progress) protected override async Task ReloadInternal(IProgress<TaskProgress> progress)
{ {
await base.Reload(progress).ConfigureAwait(false); await base.ReloadInternal(progress).ConfigureAwait(false);
ReloadWeatherClient(); ReportProgress(progress, "Loading Users");
ExtractFFMpeg();
progress.Report(new TaskProgress { Description = "Loading Users" });
ReloadUsers(); ReloadUsers();
progress.Report(new TaskProgress { Description = "Loading Media Library" }); ReportProgress(progress, "Loading Media Library");
await ReloadRoot(allowInternetProviders: false).ConfigureAwait(false); await ReloadRoot(allowInternetProviders: false).ConfigureAwait(false);
} }
@ -136,8 +141,6 @@ namespace MediaBrowser.Controller
DirectoryWatchers.Stop(); DirectoryWatchers.Stop();
DisposeWeatherClient();
} }
protected override void OnComposablePartsLoaded() protected override void OnComposablePartsLoaded()
@ -380,26 +383,5 @@ namespace MediaBrowser.Controller
} }
} }
} }
/// <summary>
/// Disposes the current WeatherClient
/// </summary>
private void DisposeWeatherClient()
{
if (WeatherClient != null)
{
WeatherClient.Dispose();
}
}
/// <summary>
/// Disposes the current WeatherClient and creates a new one
/// </summary>
private void ReloadWeatherClient()
{
DisposeWeatherClient();
WeatherClient = new WeatherClient();
}
} }
} }

View File

@ -36,6 +36,7 @@
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" /> <Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" /> <Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Reactive.Core, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL"> <Reference Include="System.Reactive.Core, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
@ -58,6 +59,8 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Drawing\DrawingUtils.cs" />
<Compile Include="Drawing\ImageProcessor.cs" />
<Compile Include="Entities\Audio.cs" /> <Compile Include="Entities\Audio.cs" />
<Compile Include="Entities\BaseEntity.cs" /> <Compile Include="Entities\BaseEntity.cs" />
<Compile Include="Entities\BaseItem.cs" /> <Compile Include="Entities\BaseItem.cs" />
@ -111,7 +114,8 @@
<Compile Include="Resolvers\BaseItemResolver.cs" /> <Compile Include="Resolvers\BaseItemResolver.cs" />
<Compile Include="Resolvers\FolderResolver.cs" /> <Compile Include="Resolvers\FolderResolver.cs" />
<Compile Include="Resolvers\VideoResolver.cs" /> <Compile Include="Resolvers\VideoResolver.cs" />
<Compile Include="Weather\WeatherClient.cs" /> <Compile Include="Weather\BaseWeatherProvider.cs" />
<Compile Include="Weather\WeatherProvider.cs" />
<Compile Include="Providers\BaseItemXmlParser.cs" /> <Compile Include="Providers\BaseItemXmlParser.cs" />
<Compile Include="Xml\XmlExtensions.cs" /> <Compile Include="Xml\XmlExtensions.cs" />
</ItemGroup> </ItemGroup>

View File

@ -220,7 +220,7 @@ namespace MediaBrowser.Controller.Providers
if (float.TryParse(rating, out val)) if (float.TryParse(rating, out val))
{ {
item.UserRating = val; item.CommunityRating = val;
} }
} }
break; break;

View File

@ -0,0 +1,34 @@
using MediaBrowser.Common.Logging;
using MediaBrowser.Model.Weather;
using System;
using System.Net;
using System.Net.Cache;
using System.Net.Http;
using System.Threading.Tasks;
namespace MediaBrowser.Controller.Weather
{
public abstract class BaseWeatherProvider : IDisposable
{
protected HttpClient HttpClient { get; private set; }
protected BaseWeatherProvider()
{
var handler = new WebRequestHandler { };
handler.AutomaticDecompression = DecompressionMethods.Deflate;
handler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate);
HttpClient = new HttpClient(handler);
}
public virtual void Dispose()
{
Logger.LogInfo("Disposing " + GetType().Name);
HttpClient.Dispose();
}
public abstract Task<WeatherInfo> GetWeatherInfoAsync(string zipCode);
}
}

View File

@ -2,11 +2,9 @@
using MediaBrowser.Common.Serialization; using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Weather; using MediaBrowser.Model.Weather;
using System; using System;
using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net;
using System.Net.Cache;
using System.Net.Http;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MediaBrowser.Controller.Weather namespace MediaBrowser.Controller.Weather
@ -15,21 +13,10 @@ namespace MediaBrowser.Controller.Weather
/// Based on http://www.worldweatheronline.com/free-weather-feed.aspx /// Based on http://www.worldweatheronline.com/free-weather-feed.aspx
/// The classes in this file are a reproduction of the json output, which will then be converted to our weather model classes /// The classes in this file are a reproduction of the json output, which will then be converted to our weather model classes
/// </summary> /// </summary>
public class WeatherClient : IDisposable [Export(typeof(BaseWeatherProvider))]
public class WeatherProvider : BaseWeatherProvider
{ {
private HttpClient HttpClient { get; set; } public override async Task<WeatherInfo> GetWeatherInfoAsync(string zipCode)
public WeatherClient()
{
var handler = new WebRequestHandler { };
handler.AutomaticDecompression = DecompressionMethods.Deflate;
handler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate);
HttpClient = new HttpClient(handler);
}
public async Task<WeatherInfo> GetWeatherInfoAsync(string zipCode)
{ {
if (string.IsNullOrWhiteSpace(zipCode)) if (string.IsNullOrWhiteSpace(zipCode))
{ {
@ -73,11 +60,6 @@ namespace MediaBrowser.Controller.Weather
return info; return info;
} }
public void Dispose()
{
HttpClient.Dispose();
}
} }
class WeatherResult class WeatherResult

View File

@ -46,7 +46,7 @@ namespace MediaBrowser.Model.DTO
public string DisplayMediaType { get; set; } public string DisplayMediaType { get; set; }
[ProtoMember(12)] [ProtoMember(12)]
public float? UserRating { get; set; } public float? CommunityRating { get; set; }
[ProtoMember(13)] [ProtoMember(13)]
public long? RunTimeTicks { get; set; } public long? RunTimeTicks { get; set; }

View File

@ -1,4 +1,7 @@
using Hardcodet.Wpf.TaskbarNotification; using Hardcodet.Wpf.TaskbarNotification;
using MediaBrowser.Common.Events;
using MediaBrowser.Controller;
using MediaBrowser.Model.Progress;
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Threading; using System.Threading;
@ -11,15 +14,36 @@ namespace MediaBrowser.ServerApplication
/// </summary> /// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged public partial class MainWindow : Window, INotifyPropertyChanged
{ {
private Timer LoadingIconTimer { get; set; }
public MainWindow() public MainWindow()
{ {
InitializeComponent(); InitializeComponent();
Loaded += MainWindow_Loaded; Loaded += MainWindowLoaded;
} }
void MainWindow_Loaded(object sender, RoutedEventArgs e) void MainWindowLoaded(object sender, RoutedEventArgs e)
{ {
DataContext = this; DataContext = this;
Kernel.Instance.ReloadBeginning += KernelReloadBeginning;
Kernel.Instance.ReloadCompleted += KernelReloadCompleted;
}
void KernelReloadBeginning(object sender, GenericEventArgs<IProgress<TaskProgress>> e)
{
MbTaskbarIcon.ShowBalloonTip("Media Browser is reloading", "Please wait...", BalloonIcon.Info);
LoadingImageIndex = 0;
LoadingIconTimer = new Timer(LoadingIconTimerCallback, null, 0, 250);
}
void KernelReloadCompleted(object sender, GenericEventArgs<IProgress<TaskProgress>> e)
{
LoadingIconTimer.Dispose();
LoadingImageIndex = 0;
} }
public event PropertyChangedEventHandler PropertyChanged; public event PropertyChangedEventHandler PropertyChanged;
@ -62,17 +86,7 @@ namespace MediaBrowser.ServerApplication
private async void cmdReloadServer_click(object sender, RoutedEventArgs e) private async void cmdReloadServer_click(object sender, RoutedEventArgs e)
{ {
MbTaskbarIcon.ShowBalloonTip("Media Browser is reloading", "Please wait...", BalloonIcon.Info); await Kernel.Instance.Reload(new Progress<TaskProgress>()).ConfigureAwait(false);
LoadingImageIndex = 0;
Timer timer = new Timer(LoadingIconTimerCallback, null, 0, 250);
await (Application.Current as App).ReloadKernel().ConfigureAwait(false);
timer.Dispose();
LoadingImageIndex = 0;
} }
private void LoadingIconTimerCallback(object stateInfo) private void LoadingIconTimerCallback(object stateInfo)

View File

@ -52,4 +52,7 @@ Global
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
EndGlobalSection EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal EndGlobal