Merge pull request #1 from MediaBrowser/Repo

Repo
This commit is contained in:
Luke 2013-02-20 17:10:49 -08:00
commit 845554722e
261 changed files with 19594 additions and 0 deletions

43
.hgignore Normal file
View File

@ -0,0 +1,43 @@
# use glob syntax
syntax: glob
*.obj
*.pdb
*.user
*.aps
*.pch
*.vspscc
*.vssscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
*.lib
*.sbr
*.scc
*.psess
*.vsp
*.orig
[Bb]in
[Dd]ebug*/
obj/
[Rr]elease*/
ProgramData*/
ProgramData-Server*/
ProgramData-UI*/
_ReSharper*/
[Tt]humbs.db
[Tt]est[Rr]esult*
[Bb]uild[Ll]og.*
*.[Pp]ublish.xml
*.resharper
# ncrunch files
*.ncrunchsolution
*.ncrunchproject

View File

@ -0,0 +1,438 @@
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Model.DTO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api
{
/// <summary>
/// Contains some helpers for the api
/// </summary>
public static class ApiService
{
/// <summary>
/// Gets an Item by Id, or the root item if none is supplied
/// </summary>
public static BaseItem GetItemById(string id)
{
Guid guid = string.IsNullOrEmpty(id) ? Guid.Empty : new Guid(id);
return Kernel.Instance.GetItemById(guid);
}
/// <summary>
/// Gets a User by Id
/// </summary>
/// <param name="logActivity">Whether or not to update the user's LastActivityDate</param>
public static User GetUserById(string id, bool logActivity)
{
var guid = new Guid(id);
var user = Kernel.Instance.Users.FirstOrDefault(u => u.Id == guid);
if (logActivity)
{
LogUserActivity(user);
}
return user;
}
/// <summary>
/// Gets the default User
/// </summary>
/// <param name="logActivity">Whether or not to update the user's LastActivityDate</param>
public static User GetDefaultUser(bool logActivity)
{
User user = Kernel.Instance.GetDefaultUser();
if (logActivity)
{
LogUserActivity(user);
}
return user;
}
/// <summary>
/// Updates LastActivityDate for a given User
/// </summary>
public static void LogUserActivity(User user)
{
user.LastActivityDate = DateTime.UtcNow;
Kernel.Instance.SaveUser(user);
}
/// <summary>
/// Converts a BaseItem to a DTOBaseItem
/// </summary>
public async static Task<DtoBaseItem> GetDtoBaseItem(BaseItem item, User user,
bool includeChildren = true,
bool includePeople = true)
{
var dto = new DtoBaseItem();
var tasks = new List<Task>();
tasks.Add(AttachStudios(dto, item));
if (includeChildren)
{
tasks.Add(AttachChildren(dto, item, user));
tasks.Add(AttachLocalTrailers(dto, item, user));
}
if (includePeople)
{
tasks.Add(AttachPeople(dto, item));
}
AttachBasicFields(dto, item, user);
// Make sure all the tasks we kicked off have completed.
if (tasks.Count > 0)
{
await Task.WhenAll(tasks).ConfigureAwait(false);
}
return dto;
}
/// <summary>
/// Sets simple property values on a DTOBaseItem
/// </summary>
private static void AttachBasicFields(DtoBaseItem dto, BaseItem item, User user)
{
dto.AspectRatio = item.AspectRatio;
dto.BackdropCount = item.BackdropImagePaths == null ? 0 : item.BackdropImagePaths.Count();
dto.DateCreated = item.DateCreated;
dto.DisplayMediaType = item.DisplayMediaType;
if (item.Genres != null)
{
dto.Genres = item.Genres.ToArray();
}
dto.HasArt = !string.IsNullOrEmpty(item.ArtImagePath);
dto.HasBanner = !string.IsNullOrEmpty(item.BannerImagePath);
dto.HasLogo = !string.IsNullOrEmpty(item.LogoImagePath);
dto.HasPrimaryImage = !string.IsNullOrEmpty(item.PrimaryImagePath);
dto.HasThumb = !string.IsNullOrEmpty(item.ThumbnailImagePath);
dto.Id = item.Id;
dto.IsNew = item.IsRecentlyAdded(user);
dto.IndexNumber = item.IndexNumber;
dto.IsFolder = item.IsFolder;
dto.Language = item.Language;
dto.LocalTrailerCount = item.LocalTrailers == null ? 0 : item.LocalTrailers.Count();
dto.Name = item.Name;
dto.OfficialRating = item.OfficialRating;
dto.Overview = item.Overview;
// If there are no backdrops, indicate what parent has them in case the Ui wants to allow inheritance
if (dto.BackdropCount == 0)
{
int backdropCount;
dto.ParentBackdropItemId = GetParentBackdropItemId(item, out backdropCount);
dto.ParentBackdropCount = backdropCount;
}
if (item.Parent != null)
{
dto.ParentId = item.Parent.Id;
}
dto.ParentIndexNumber = item.ParentIndexNumber;
// If there is no logo, indicate what parent has one in case the Ui wants to allow inheritance
if (!dto.HasLogo)
{
dto.ParentLogoItemId = GetParentLogoItemId(item);
}
dto.Path = item.Path;
dto.PremiereDate = item.PremiereDate;
dto.ProductionYear = item.ProductionYear;
dto.ProviderIds = item.ProviderIds;
dto.RunTimeTicks = item.RunTimeTicks;
dto.SortName = item.SortName;
if (item.Taglines != null)
{
dto.Taglines = item.Taglines.ToArray();
}
dto.TrailerUrl = item.TrailerUrl;
dto.Type = item.GetType().Name;
dto.CommunityRating = item.CommunityRating;
dto.UserData = GetDtoUserItemData(item.GetUserData(user, false));
var folder = item as Folder;
if (folder != null)
{
dto.SpecialCounts = folder.GetSpecialCounts(user);
dto.IsRoot = folder.IsRoot;
dto.IsVirtualFolder = folder.IsVirtualFolder;
}
// Add AudioInfo
var audio = item as Audio;
if (audio != null)
{
dto.AudioInfo = new AudioInfo
{
Album = audio.Album,
AlbumArtist = audio.AlbumArtist,
Artist = audio.Artist,
BitRate = audio.BitRate,
Channels = audio.Channels
};
}
// Add VideoInfo
var video = item as Video;
if (video != null)
{
dto.VideoInfo = new VideoInfo
{
Height = video.Height,
Width = video.Width,
Codec = video.Codec,
VideoType = video.VideoType,
ScanType = video.ScanType
};
if (video.AudioStreams != null)
{
dto.VideoInfo.AudioStreams = video.AudioStreams.ToArray();
}
if (video.Subtitles != null)
{
dto.VideoInfo.Subtitles = video.Subtitles.ToArray();
}
}
// Add SeriesInfo
var series = item as Series;
if (series != null)
{
DayOfWeek[] airDays = series.AirDays == null ? new DayOfWeek[] { } : series.AirDays.ToArray();
dto.SeriesInfo = new SeriesInfo
{
AirDays = airDays,
AirTime = series.AirTime,
Status = series.Status
};
}
// Add MovieInfo
var movie = item as Movie;
if (movie != null)
{
int specialFeatureCount = movie.SpecialFeatures == null ? 0 : movie.SpecialFeatures.Count();
dto.MovieInfo = new MovieInfo
{
SpecialFeatureCount = specialFeatureCount
};
}
}
/// <summary>
/// Attaches Studio DTO's to a DTOBaseItem
/// </summary>
private static async Task AttachStudios(DtoBaseItem dto, BaseItem item)
{
// Attach Studios by transforming them into BaseItemStudio (DTO)
if (item.Studios != null)
{
Studio[] entities = await Task.WhenAll(item.Studios.Select(c => Kernel.Instance.ItemController.GetStudio(c))).ConfigureAwait(false);
dto.Studios = new BaseItemStudio[entities.Length];
for (int i = 0; i < entities.Length; i++)
{
Studio entity = entities[i];
var baseItemStudio = new BaseItemStudio{};
baseItemStudio.Name = entity.Name;
baseItemStudio.HasImage = !string.IsNullOrEmpty(entity.PrimaryImagePath);
dto.Studios[i] = baseItemStudio;
}
}
}
/// <summary>
/// Attaches child DTO's to a DTOBaseItem
/// </summary>
private static async Task AttachChildren(DtoBaseItem dto, BaseItem item, User user)
{
var folder = item as Folder;
if (folder != null)
{
IEnumerable<BaseItem> children = folder.GetChildren(user);
dto.Children = await Task.WhenAll(children.Select(c => GetDtoBaseItem(c, user, false, false))).ConfigureAwait(false);
}
}
/// <summary>
/// Attaches trailer DTO's to a DTOBaseItem
/// </summary>
private static async Task AttachLocalTrailers(DtoBaseItem dto, BaseItem item, User user)
{
if (item.LocalTrailers != null && item.LocalTrailers.Any())
{
dto.LocalTrailers = await Task.WhenAll(item.LocalTrailers.Select(c => GetDtoBaseItem(c, user, false, false))).ConfigureAwait(false);
}
}
/// <summary>
/// Attaches People DTO's to a DTOBaseItem
/// </summary>
private static async Task AttachPeople(DtoBaseItem dto, BaseItem item)
{
// Attach People by transforming them into BaseItemPerson (DTO)
if (item.People != null)
{
IEnumerable<Person> entities = await Task.WhenAll(item.People.Select(c => Kernel.Instance.ItemController.GetPerson(c.Key))).ConfigureAwait(false);
dto.People = item.People.Select(p =>
{
var baseItemPerson = new BaseItemPerson{};
baseItemPerson.Name = p.Key;
baseItemPerson.Overview = p.Value.Overview;
baseItemPerson.Type = p.Value.Type;
Person ibnObject = entities.First(i => i.Name.Equals(p.Key, StringComparison.OrdinalIgnoreCase));
if (ibnObject != null)
{
baseItemPerson.HasImage = !string.IsNullOrEmpty(ibnObject.PrimaryImagePath);
}
return baseItemPerson;
}).ToArray();
}
}
/// <summary>
/// If an item does not any backdrops, this can be used to find the first parent that does have one
/// </summary>
private static Guid? GetParentBackdropItemId(BaseItem item, out int backdropCount)
{
backdropCount = 0;
var parent = item.Parent;
while (parent != null)
{
if (parent.BackdropImagePaths != null && parent.BackdropImagePaths.Any())
{
backdropCount = parent.BackdropImagePaths.Count();
return parent.Id;
}
parent = parent.Parent;
}
return null;
}
/// <summary>
/// If an item does not have a logo, this can be used to find the first parent that does have one
/// </summary>
private static Guid? GetParentLogoItemId(BaseItem item)
{
var parent = item.Parent;
while (parent != null)
{
if (!string.IsNullOrEmpty(parent.LogoImagePath))
{
return parent.Id;
}
parent = parent.Parent;
}
return null;
}
/// <summary>
/// Gets an ImagesByName entity along with the number of items containing it
/// </summary>
public static IbnItem GetIbnItem(BaseEntity entity, int itemCount)
{
return new IbnItem
{
Id = entity.Id,
BaseItemCount = itemCount,
HasImage = !string.IsNullOrEmpty(entity.PrimaryImagePath),
Name = entity.Name
};
}
/// <summary>
/// Converts a User to a DTOUser
/// </summary>
public static DtoUser GetDtoUser(User user)
{
return new DtoUser
{
Id = user.Id,
Name = user.Name,
HasImage = !string.IsNullOrEmpty(user.PrimaryImagePath),
HasPassword = !string.IsNullOrEmpty(user.Password),
LastActivityDate = user.LastActivityDate,
LastLoginDate = user.LastLoginDate
};
}
/// <summary>
/// Converts a UserItemData to a DTOUserItemData
/// </summary>
public static DtoUserItemData GetDtoUserItemData(UserItemData data)
{
if (data == null)
{
return null;
}
return new DtoUserItemData
{
IsFavorite = data.IsFavorite,
Likes = data.Likes,
PlaybackPositionTicks = data.PlaybackPositionTicks,
PlayCount = data.PlayCount,
Rating = data.Rating
};
}
public static bool IsApiUrlMatch(string url, HttpListenerRequest request)
{
url = "/api/" + url;
return request.Url.LocalPath.EndsWith(url, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@ -0,0 +1,81 @@
using System;
using System.Drawing;
namespace MediaBrowser.Api.Drawing
{
public static class DrawingUtils
{
/// <summary>
/// Resizes a set of dimensions
/// </summary>
public static Size Resize(int currentWidth, int currentHeight, int? width, int? height, int? maxWidth, int? maxHeight)
{
return Resize(new Size(currentWidth, currentHeight), width, height, maxWidth, maxHeight);
}
/// <summary>
/// Resizes a set of dimensions
/// </summary>
/// <param name="size">The original size object</param>
/// <param name="width">A new fixed width, if desired</param>
/// <param name="height">A new fixed neight, if desired</param>
/// <param name="maxWidth">A max fixed width, if desired</param>
/// <param name="maxHeight">A max fixed height, if desired</param>
/// <returns>A new size object</returns>
public static Size Resize(Size size, int? width, int? height, int? maxWidth, int? maxHeight)
{
decimal newWidth = size.Width;
decimal newHeight = size.Height;
if (width.HasValue && height.HasValue)
{
newWidth = width.Value;
newHeight = height.Value;
}
else if (height.HasValue)
{
newWidth = GetNewWidth(newHeight, newWidth, height.Value);
newHeight = height.Value;
}
else if (width.HasValue)
{
newHeight = GetNewHeight(newHeight, newWidth, width.Value);
newWidth = width.Value;
}
if (maxHeight.HasValue && maxHeight < newHeight)
{
newWidth = GetNewWidth(newHeight, newWidth, maxHeight.Value);
newHeight = maxHeight.Value;
}
if (maxWidth.HasValue && maxWidth < newWidth)
{
newHeight = GetNewHeight(newHeight, newWidth, maxWidth.Value);
newWidth = maxWidth.Value;
}
return new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight));
}
private static decimal GetNewWidth(decimal currentHeight, decimal currentWidth, int newHeight)
{
decimal scaleFactor = newHeight;
scaleFactor /= currentHeight;
scaleFactor *= currentWidth;
return scaleFactor;
}
private static decimal GetNewHeight(decimal currentHeight, decimal currentWidth, int newWidth)
{
decimal scaleFactor = newWidth;
scaleFactor /= currentWidth;
scaleFactor *= currentHeight;
return scaleFactor;
}
}
}

View File

@ -0,0 +1,148 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
namespace MediaBrowser.Api.Drawing
{
public static class ImageProcessor
{
/// <summary>
/// Processes an image by resizing to target dimensions
/// </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="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="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>
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.FromFile(GetImagePath(entity, imageType, imageIndex));
// Determine the output size based on incoming parameters
Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);
Bitmap thumbnail;
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed))
{
thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height);
}
else
{
thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat);
}
thumbnail.MakeTransparent();
// Preserve the original resolution
thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
Graphics thumbnailGraph = Graphics.FromImage(thumbnail);
thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
thumbnailGraph.CompositingMode = CompositingMode.SourceOver;
thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height);
ImageFormat outputFormat = originalImage.RawFormat;
// Write to the output stream
SaveImage(outputFormat, thumbnail, toStream, quality);
thumbnailGraph.Dispose();
thumbnail.Dispose();
originalImage.Dispose();
}
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
// All other formats use the generic Image.Save
if (ImageFormat.Jpeg.Equals(outputFormat))
{
SaveJpeg(newImage, toStream, quality);
}
else if (ImageFormat.Png.Equals(outputFormat))
{
newImage.Save(toStream, ImageFormat.Png);
}
else
{
newImage.Save(toStream, outputFormat);
}
}
public static void SaveJpeg(Image image, Stream target, int? quality)
{
if (!quality.HasValue)
{
quality = 90;
}
using (var encoderParameters = new EncoderParameters(1))
{
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value);
image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
}
}
public static ImageCodecInfo GetImageCodecInfo(string mimeType)
{
ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();
for (int i = 0; i < info.Length; i++)
{
ImageCodecInfo ici = info[i];
if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))
{
return ici;
}
}
return info[1];
}
}
}

View File

@ -0,0 +1,119 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Net;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Supported output formats are: mp3,flac,ogg,wav,asf,wma,aac
/// </summary>
[Export(typeof(BaseHandler))]
public class AudioHandler : BaseMediaHandler<Audio, AudioOutputFormats>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("audio", request);
}
/// <summary>
/// We can output these formats directly, but we cannot encode to them.
/// </summary>
protected override IEnumerable<AudioOutputFormats> UnsupportedOutputEncodingFormats
{
get
{
return new AudioOutputFormats[] { AudioOutputFormats.Aac, AudioOutputFormats.Flac, AudioOutputFormats.Wma };
}
}
private int? GetMaxAcceptedBitRate(AudioOutputFormats audioFormat)
{
return GetMaxAcceptedBitRate(audioFormat.ToString());
}
private int? GetMaxAcceptedBitRate(string audioFormat)
{
if (audioFormat.Equals("mp3", System.StringComparison.OrdinalIgnoreCase))
{
return 320000;
}
return null;
}
/// <summary>
/// Determines whether or not the original file requires transcoding
/// </summary>
protected override bool RequiresConversion()
{
if (base.RequiresConversion())
{
return true;
}
string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty);
int? bitrate = GetMaxAcceptedBitRate(currentFormat);
// If the bitrate is greater than our desired bitrate, we need to transcode
if (bitrate.HasValue && bitrate.Value < LibraryItem.BitRate)
{
return true;
}
// If the number of channels is greater than our desired channels, we need to transcode
if (AudioChannels.HasValue && AudioChannels.Value < LibraryItem.Channels)
{
return true;
}
// If the sample rate is greater than our desired sample rate, we need to transcode
if (AudioSampleRate.HasValue && AudioSampleRate.Value < LibraryItem.SampleRate)
{
return true;
}
// Yay
return false;
}
/// <summary>
/// Creates arguments to pass to ffmpeg
/// </summary>
protected override string GetCommandLineArguments()
{
var audioTranscodeParams = new List<string>();
AudioOutputFormats outputFormat = GetConversionOutputFormat();
int? bitrate = GetMaxAcceptedBitRate(outputFormat);
if (bitrate.HasValue)
{
audioTranscodeParams.Add("-ab " + bitrate.Value);
}
int? channels = GetNumAudioChannelsParam(LibraryItem.Channels);
if (channels.HasValue)
{
audioTranscodeParams.Add("-ac " + channels.Value);
}
int? sampleRate = GetSampleRateParam(LibraryItem.SampleRate);
if (sampleRate.HasValue)
{
audioTranscodeParams.Add("-ar " + sampleRate.Value);
}
audioTranscodeParams.Add("-f " + outputFormat);
return "-i \"" + LibraryItem.Path + "\" -vn " + string.Join(" ", audioTranscodeParams.ToArray()) + " -";
}
}
}

View File

@ -0,0 +1,255 @@
using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
public abstract class BaseMediaHandler<TBaseItemType, TOutputType> : BaseHandler
where TBaseItemType : BaseItem, new()
{
/// <summary>
/// Supported values: mp3,flac,ogg,wav,asf,wma,aac
/// </summary>
protected virtual IEnumerable<TOutputType> OutputFormats
{
get
{
return QueryString["outputformats"].Split(',').Select(o => (TOutputType)Enum.Parse(typeof(TOutputType), o, true));
}
}
/// <summary>
/// These formats can be outputted directly but cannot be encoded to
/// </summary>
protected virtual IEnumerable<TOutputType> UnsupportedOutputEncodingFormats
{
get
{
return new TOutputType[] { };
}
}
private TBaseItemType _libraryItem;
/// <summary>
/// Gets the library item that will be played, if any
/// </summary>
protected TBaseItemType LibraryItem
{
get
{
if (_libraryItem == null)
{
string id = QueryString["id"];
if (!string.IsNullOrEmpty(id))
{
_libraryItem = Kernel.Instance.GetItemById(Guid.Parse(id)) as TBaseItemType;
}
}
return _libraryItem;
}
}
public int? AudioChannels
{
get
{
string val = QueryString["audiochannels"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
public int? AudioSampleRate
{
get
{
string val = QueryString["audiosamplerate"];
if (string.IsNullOrEmpty(val))
{
return 44100;
}
return int.Parse(val);
}
}
protected override Task<ResponseInfo> GetResponseInfo()
{
ResponseInfo info = new ResponseInfo
{
ContentType = MimeTypes.GetMimeType("." + GetConversionOutputFormat()),
CompressResponse = false
};
return Task.FromResult<ResponseInfo>(info);
}
public override Task ProcessRequest(HttpListenerContext ctx)
{
HttpListenerContext = ctx;
if (!RequiresConversion())
{
return new StaticFileHandler { Path = LibraryItem.Path }.ProcessRequest(ctx);
}
return base.ProcessRequest(ctx);
}
protected abstract string GetCommandLineArguments();
/// <summary>
/// Gets the format we'll be converting to
/// </summary>
protected virtual TOutputType GetConversionOutputFormat()
{
return OutputFormats.First(f => !UnsupportedOutputEncodingFormats.Any(s => s.ToString().Equals(f.ToString(), StringComparison.OrdinalIgnoreCase)));
}
protected virtual bool RequiresConversion()
{
string currentFormat = Path.GetExtension(LibraryItem.Path).Replace(".", string.Empty);
if (OutputFormats.Any(f => currentFormat.EndsWith(f.ToString(), StringComparison.OrdinalIgnoreCase)))
{
// We can output these files directly, but we can't encode them
if (UnsupportedOutputEncodingFormats.Any(f => currentFormat.EndsWith(f.ToString(), StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
else
{
// If it's not in a format the consumer accepts, return true
return true;
}
return false;
}
private FileStream LogFileStream { get; set; }
protected async override Task WriteResponseToOutputStream(Stream stream)
{
var startInfo = new ProcessStartInfo{};
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
// Must consume both or ffmpeg may hang due to deadlocks. See comments below.
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.FileName = Kernel.Instance.ApplicationPaths.FFMpegPath;
startInfo.WorkingDirectory = Kernel.Instance.ApplicationPaths.FFMpegDirectory;
startInfo.Arguments = GetCommandLineArguments();
Logger.LogInfo(startInfo.FileName + " " + startInfo.Arguments);
var process = new Process{};
process.StartInfo = startInfo;
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
LogFileStream = new FileStream(Path.Combine(Kernel.Instance.ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid().ToString() + ".txt"), FileMode.Create);
process.EnableRaisingEvents = true;
process.Exited += ProcessExited;
try
{
process.Start();
// MUST read both stdout and stderr asynchronously or a deadlock may occurr
// Kick off two tasks
Task mediaTask = process.StandardOutput.BaseStream.CopyToAsync(stream);
Task debugLogTask = process.StandardError.BaseStream.CopyToAsync(LogFileStream);
await mediaTask.ConfigureAwait(false);
//await debugLogTask.ConfigureAwait(false);
}
catch (Exception ex)
{
Logger.LogException(ex);
// Hate having to do this
try
{
process.Kill();
}
catch
{
}
}
}
void ProcessExited(object sender, EventArgs e)
{
if (LogFileStream != null)
{
LogFileStream.Dispose();
}
var process = sender as Process;
Logger.LogInfo("FFMpeg exited with code " + process.ExitCode);
process.Dispose();
}
/// <summary>
/// Gets the number of audio channels to specify on the command line
/// </summary>
protected int? GetNumAudioChannelsParam(int libraryItemChannels)
{
// If the user requested a max number of channels
if (AudioChannels.HasValue)
{
// Only specify the param if we're going to downmix
if (AudioChannels.Value < libraryItemChannels)
{
return AudioChannels.Value;
}
}
return null;
}
/// <summary>
/// Gets the number of audio channels to specify on the command line
/// </summary>
protected int? GetSampleRateParam(int libraryItemSampleRate)
{
// If the user requested a max value
if (AudioSampleRate.HasValue)
{
// Only specify the param if we're going to downmix
if (AudioSampleRate.Value < libraryItemSampleRate)
{
return AudioSampleRate.Value;
}
}
return null;
}
}
}

View File

@ -0,0 +1,38 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Provides a handler to set user favorite status for an item
/// </summary>
[Export(typeof(BaseHandler))]
public class FavoriteStatusHandler : BaseSerializationHandler<DtoUserItemData>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("FavoriteStatus", request);
}
protected override Task<DtoUserItemData> GetObjectToSerialize()
{
// Get the item
BaseItem item = ApiService.GetItemById(QueryString["id"]);
// Get the user
User user = ApiService.GetUserById(QueryString["userid"], true);
// Get the user data for this item
UserItemData data = item.GetUserData(user, true);
// Set favorite status
data.IsFavorite = QueryString["isfavorite"] == "1";
return Task.FromResult(ApiService.GetDtoUserItemData(data));
}
}
}

View File

@ -0,0 +1,57 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Gets a single genre
/// </summary>
[Export(typeof(BaseHandler))]
public class GenreHandler : BaseSerializationHandler<IbnItem>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("genre", request);
}
protected override Task<IbnItem> GetObjectToSerialize()
{
var parent = ApiService.GetItemById(QueryString["id"]) as Folder;
var user = ApiService.GetUserById(QueryString["userid"], true);
string name = QueryString["name"];
return GetGenre(parent, user, name);
}
/// <summary>
/// Gets a Genre
/// </summary>
private async Task<IbnItem> GetGenre(Folder parent, User user, string name)
{
int count = 0;
// Get all the allowed recursive children
IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
if (item.Genres != null && item.Genres.Any(s => s.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
count++;
}
}
// Get the original entity so that we can also supply the PrimaryImagePath
return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetGenre(name).ConfigureAwait(false), count);
}
}
}

View File

@ -0,0 +1,78 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
public class GenresHandler : BaseSerializationHandler<IbnItem[]>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("genres", request);
}
protected override Task<IbnItem[]> GetObjectToSerialize()
{
var parent = ApiService.GetItemById(QueryString["id"]) as Folder;
User user = ApiService.GetUserById(QueryString["userid"], true);
return GetAllGenres(parent, user);
}
/// <summary>
/// Gets all genres from all recursive children of a folder
/// The CategoryInfo class is used to keep track of the number of times each genres appears
/// </summary>
private async Task<IbnItem[]> GetAllGenres(Folder parent, User user)
{
var data = new Dictionary<string, int>();
// Get all the allowed recursive children
IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
// Add each genre from the item to the data dictionary
// If the genre already exists, increment the count
if (item.Genres == null)
{
continue;
}
foreach (string val in item.Genres)
{
if (!data.ContainsKey(val))
{
data.Add(val, 1);
}
else
{
data[val]++;
}
}
}
// Get the Genre objects
Genre[] entities = await Task.WhenAll(data.Keys.Select(key => Kernel.Instance.ItemController.GetGenre(key))).ConfigureAwait(false);
// Convert to an array of IBNItem
var items = new IbnItem[entities.Length];
for (int i = 0; i < entities.Length; i++)
{
Genre e = entities[i];
items[i] = ApiService.GetIbnItem(e, data[e.Name]);
}
return items;
}
}
}

View File

@ -0,0 +1,224 @@
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
public class ImageHandler : BaseHandler
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("image", request);
}
private string _imagePath;
private async Task<string> GetImagePath()
{
_imagePath = _imagePath ?? await DiscoverImagePath();
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()
{
var entity = await GetSourceEntity().ConfigureAwait(false);
return ImageProcessor.GetImagePath(entity, ImageType, ImageIndex);
}
protected async override Task<ResponseInfo> GetResponseInfo()
{
string path = await GetImagePath().ConfigureAwait(false);
ResponseInfo info = new ResponseInfo
{
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))
{
info.StatusCode = 404;
date = null;
}
}
info.DateLastModified = date;
return info;
}
private int ImageIndex
{
get
{
string val = QueryString["index"];
if (string.IsNullOrEmpty(val))
{
return 0;
}
return int.Parse(val);
}
}
private int? Height
{
get
{
string val = QueryString["height"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
private int? Width
{
get
{
string val = QueryString["width"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
private int? MaxHeight
{
get
{
string val = QueryString["maxheight"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
private int? MaxWidth
{
get
{
string val = QueryString["maxwidth"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
private int? Quality
{
get
{
string val = QueryString["quality"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
private ImageType ImageType
{
get
{
string imageType = QueryString["type"];
if (string.IsNullOrEmpty(imageType))
{
return ImageType.Primary;
}
return (ImageType)Enum.Parse(typeof(ImageType), imageType, true);
}
}
protected override async Task WriteResponseToOutputStream(Stream stream)
{
var entity = await GetSourceEntity().ConfigureAwait(false);
ImageProcessor.ProcessImage(entity, ImageType, ImageIndex, stream, Width, Height, MaxWidth, MaxHeight, Quality);
}
}
}

View File

@ -0,0 +1,35 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Provides a handler to retrieve a single item
/// </summary>
[Export(typeof(BaseHandler))]
public class ItemHandler : BaseSerializationHandler<DtoBaseItem>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("item", request);
}
protected override Task<DtoBaseItem> GetObjectToSerialize()
{
User user = ApiService.GetUserById(QueryString["userid"], true);
BaseItem item = ApiService.GetItemById(QueryString["id"]);
if (item == null)
{
return null;
}
return ApiService.GetDtoBaseItem(item, user);
}
}
}

View File

@ -0,0 +1,84 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
public class ItemListHandler : BaseSerializationHandler<DtoBaseItem[]>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("itemlist", request);
}
protected override Task<DtoBaseItem[]> GetObjectToSerialize()
{
User user = ApiService.GetUserById(QueryString["userid"], true);
return Task.WhenAll(GetItemsToSerialize(user).Select(i => ApiService.GetDtoBaseItem(i, user, includeChildren: false, includePeople: false)));
}
private IEnumerable<BaseItem> GetItemsToSerialize(User user)
{
var parent = ApiService.GetItemById(ItemId) as Folder;
if (ListType.Equals("inprogressitems", StringComparison.OrdinalIgnoreCase))
{
return parent.GetInProgressItems(user);
}
if (ListType.Equals("recentlyaddeditems", StringComparison.OrdinalIgnoreCase))
{
return parent.GetRecentlyAddedItems(user);
}
if (ListType.Equals("recentlyaddedunplayeditems", StringComparison.OrdinalIgnoreCase))
{
return parent.GetRecentlyAddedUnplayedItems(user);
}
if (ListType.Equals("itemswithgenre", StringComparison.OrdinalIgnoreCase))
{
return parent.GetItemsWithGenre(QueryString["name"], user);
}
if (ListType.Equals("itemswithyear", StringComparison.OrdinalIgnoreCase))
{
return parent.GetItemsWithYear(int.Parse(QueryString["year"]), user);
}
if (ListType.Equals("itemswithstudio", StringComparison.OrdinalIgnoreCase))
{
return parent.GetItemsWithStudio(QueryString["name"], user);
}
if (ListType.Equals("itemswithperson", StringComparison.OrdinalIgnoreCase))
{
return parent.GetItemsWithPerson(QueryString["name"], null, user);
}
if (ListType.Equals("favorites", StringComparison.OrdinalIgnoreCase))
{
return parent.GetFavoriteItems(user);
}
throw new InvalidOperationException();
}
protected string ItemId
{
get
{
return QueryString["id"];
}
}
private string ListType
{
get
{
return QueryString["listtype"] ?? string.Empty;
}
}
}
}

View File

@ -0,0 +1,46 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Model.DTO;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// This handler retrieves special features for movies
/// </summary>
[Export(typeof(BaseHandler))]
public class MovieSpecialFeaturesHandler : BaseSerializationHandler<DtoBaseItem[]>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("MovieSpecialFeatures", request);
}
protected override Task<DtoBaseItem[]> GetObjectToSerialize()
{
User user = ApiService.GetUserById(QueryString["userid"], true);
var movie = ApiService.GetItemById(ItemId) as Movie;
// If none
if (movie.SpecialFeatures == null)
{
return Task.FromResult(new DtoBaseItem[] { });
}
return Task.WhenAll(movie.SpecialFeatures.Select(i => ApiService.GetDtoBaseItem(i, user, includeChildren: false)));
}
protected string ItemId
{
get
{
return QueryString["id"];
}
}
}
}

View File

@ -0,0 +1,55 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Gets a single Person
/// </summary>
[Export(typeof(BaseHandler))]
public class PersonHandler : BaseSerializationHandler<IbnItem>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("person", request);
}
protected override Task<IbnItem> GetObjectToSerialize()
{
var parent = ApiService.GetItemById(QueryString["id"]) as Folder;
var user = ApiService.GetUserById(QueryString["userid"], true);
string name = QueryString["name"];
return GetPerson(parent, user, name);
}
/// <summary>
/// Gets a Person
/// </summary>
private async Task<IbnItem> GetPerson(Folder parent, User user, string name)
{
int count = 0;
// Get all the allowed recursive children
IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
if (item.People != null && item.People.ContainsKey(name))
{
count++;
}
}
// Get the original entity so that we can also supply the PrimaryImagePath
return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetPerson(name).ConfigureAwait(false), count);
}
}
}

View File

@ -0,0 +1,38 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Provides a handler to set played status for an item
/// </summary>
[Export(typeof(BaseHandler))]
public class PlayedStatusHandler : BaseSerializationHandler<DtoUserItemData>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("PlayedStatus", request);
}
protected override Task<DtoUserItemData> GetObjectToSerialize()
{
// Get the item
BaseItem item = ApiService.GetItemById(QueryString["id"]);
// Get the user
User user = ApiService.GetUserById(QueryString["userid"], true);
bool wasPlayed = QueryString["played"] == "1";
item.SetPlayedStatus(user, wasPlayed);
UserItemData data = item.GetUserData(user, true);
return Task.FromResult(ApiService.GetDtoUserItemData(data));
}
}
}

View File

@ -0,0 +1,38 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
class PluginAssemblyHandler : BaseHandler
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("pluginassembly", request);
}
protected override Task<ResponseInfo> GetResponseInfo()
{
throw new NotImplementedException();
}
protected override Task WriteResponseToOutputStream(Stream stream)
{
throw new NotImplementedException();
}
public override Task ProcessRequest(HttpListenerContext ctx)
{
string filename = ctx.Request.QueryString["assemblyfilename"];
string path = Path.Combine(Kernel.Instance.ApplicationPaths.PluginsPath, filename);
return new StaticFileHandler { Path = path }.ProcessRequest(ctx);
}
}
}

View File

@ -0,0 +1,53 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Controller;
using MediaBrowser.Model.Plugins;
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
public class PluginConfigurationHandler : BaseSerializationHandler<BasePluginConfiguration>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("pluginconfiguration", request);
}
private BasePlugin _plugin;
private BasePlugin Plugin
{
get
{
if (_plugin == null)
{
string name = QueryString["assemblyfilename"];
_plugin = Kernel.Instance.Plugins.First(p => p.AssemblyFileName.Equals(name, StringComparison.OrdinalIgnoreCase));
}
return _plugin;
}
}
protected override Task<BasePluginConfiguration> GetObjectToSerialize()
{
return Task.FromResult(Plugin.Configuration);
}
protected override async Task<ResponseInfo> GetResponseInfo()
{
var info = await base.GetResponseInfo().ConfigureAwait(false);
info.DateLastModified = Plugin.ConfigurationDateLastModified;
info.CacheDuration = TimeSpan.FromDays(7);
return info;
}
}
}

View File

@ -0,0 +1,38 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Provides information about installed plugins
/// </summary>
[Export(typeof(BaseHandler))]
public class PluginsHandler : BaseSerializationHandler<IEnumerable<PluginInfo>>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("plugins", request);
}
protected override Task<IEnumerable<PluginInfo>> GetObjectToSerialize()
{
var plugins = Kernel.Instance.Plugins.Select(p => new PluginInfo
{
Name = p.Name,
Enabled = p.Enabled,
DownloadToUI = p.DownloadToUi,
Version = p.Version.ToString(),
AssemblyFileName = p.AssemblyFileName,
ConfigurationDateLastModified = p.ConfigurationDateLastModified
});
return Task.FromResult(plugins);
}
}
}

View File

@ -0,0 +1,37 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Model.Configuration;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
class ServerConfigurationHandler : BaseSerializationHandler<ServerConfiguration>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("serverconfiguration", request);
}
protected override Task<ServerConfiguration> GetObjectToSerialize()
{
return Task.FromResult(Kernel.Instance.Configuration);
}
protected override async Task<ResponseInfo> GetResponseInfo()
{
var info = await base.GetResponseInfo().ConfigureAwait(false);
info.DateLastModified =
File.GetLastWriteTimeUtc(Kernel.Instance.ApplicationPaths.SystemConfigurationFilePath);
info.CacheDuration = TimeSpan.FromDays(7);
return info;
}
}
}

View File

@ -0,0 +1,57 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Gets a single studio
/// </summary>
[Export(typeof(BaseHandler))]
public class StudioHandler : BaseSerializationHandler<IbnItem>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("studio", request);
}
protected override Task<IbnItem> GetObjectToSerialize()
{
var parent = ApiService.GetItemById(QueryString["id"]) as Folder;
var user = ApiService.GetUserById(QueryString["userid"], true);
string name = QueryString["name"];
return GetStudio(parent, user, name);
}
/// <summary>
/// Gets a Studio
/// </summary>
private async Task<IbnItem> GetStudio(Folder parent, User user, string name)
{
int count = 0;
// Get all the allowed recursive children
IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
if (item.Studios != null && item.Studios.Any(s => s.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
count++;
}
}
// Get the original entity so that we can also supply the PrimaryImagePath
return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetStudio(name).ConfigureAwait(false), count);
}
}
}

View File

@ -0,0 +1,78 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
public class StudiosHandler : BaseSerializationHandler<IbnItem[]>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("studios", request);
}
protected override Task<IbnItem[]> GetObjectToSerialize()
{
var parent = ApiService.GetItemById(QueryString["id"]) as Folder;
var user = ApiService.GetUserById(QueryString["userid"], true);
return GetAllStudios(parent, user);
}
/// <summary>
/// Gets all studios from all recursive children of a folder
/// The CategoryInfo class is used to keep track of the number of times each studio appears
/// </summary>
private async Task<IbnItem[]> GetAllStudios(Folder parent, User user)
{
var data = new Dictionary<string, int>();
// Get all the allowed recursive children
IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
// Add each studio from the item to the data dictionary
// If the studio already exists, increment the count
if (item.Studios == null)
{
continue;
}
foreach (string val in item.Studios)
{
if (!data.ContainsKey(val))
{
data.Add(val, 1);
}
else
{
data[val]++;
}
}
}
// Get the Studio objects
Studio[] entities = await Task.WhenAll(data.Keys.Select(key => Kernel.Instance.ItemController.GetStudio(key))).ConfigureAwait(false);
// Convert to an array of IBNItem
var items = new IbnItem[entities.Length];
for (int i = 0; i < entities.Length; i++)
{
Studio e = entities[i];
items[i] = ApiService.GetIbnItem(e, data[e.Name]);
}
return items;
}
}
}

View File

@ -0,0 +1,29 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Authentication;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
class UserAuthenticationHandler : BaseSerializationHandler<AuthenticationResult>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("UserAuthentication", request);
}
protected override async Task<AuthenticationResult> GetObjectToSerialize()
{
string userId = await GetFormValue("userid").ConfigureAwait(false);
User user = ApiService.GetUserById(userId, false);
string password = await GetFormValue("password").ConfigureAwait(false);
return Kernel.Instance.AuthenticateUser(user, password);
}
}
}

View File

@ -0,0 +1,29 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
class UserHandler : BaseSerializationHandler<DtoUser>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("user", request);
}
protected override Task<DtoUser> GetObjectToSerialize()
{
string id = QueryString["id"];
User user = string.IsNullOrEmpty(id) ? ApiService.GetDefaultUser(false) : ApiService.GetUserById(id, false);
DtoUser dto = ApiService.GetDtoUser(user);
return Task.FromResult(dto);
}
}
}

View File

@ -0,0 +1,46 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Provides a handler to set a user's rating for an item
/// </summary>
[Export(typeof(BaseHandler))]
public class UserItemRatingHandler : BaseSerializationHandler<DtoUserItemData>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("UserItemRating", request);
}
protected override Task<DtoUserItemData> GetObjectToSerialize()
{
// Get the item
BaseItem item = ApiService.GetItemById(QueryString["id"]);
// Get the user
User user = ApiService.GetUserById(QueryString["userid"], true);
// Get the user data for this item
UserItemData data = item.GetUserData(user, true);
// If clearing the rating, set it to null
if (QueryString["clear"] == "1")
{
data.Rating = null;
}
else
{
data.Likes = QueryString["likes"] == "1";
}
return Task.FromResult(ApiService.GetDtoUserItemData(data));
}
}
}

View File

@ -0,0 +1,25 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
class UsersHandler : BaseSerializationHandler<IEnumerable<DtoUser>>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("users", request);
}
protected override Task<IEnumerable<DtoUser>> GetObjectToSerialize()
{
return Task.FromResult(Kernel.Instance.Users.Select(u => ApiService.GetDtoUser(u)));
}
}
}

View File

@ -0,0 +1,424 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller.Drawing;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Linq;
using System.Net;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Supported output formats: mkv,m4v,mp4,asf,wmv,mov,webm,ogv,3gp,avi,ts,flv
/// </summary>
[Export(typeof(BaseHandler))]
class VideoHandler : BaseMediaHandler<Video, VideoOutputFormats>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("video", request);
}
/// <summary>
/// We can output these files directly, but we can't encode them
/// </summary>
protected override IEnumerable<VideoOutputFormats> UnsupportedOutputEncodingFormats
{
get
{
// mp4, 3gp, mov - muxer does not support non-seekable output
// avi, mov, mkv, m4v - can't stream these when encoding. the player will try to download them completely before starting playback.
// wmv - can't seem to figure out the output format name
return new VideoOutputFormats[] { VideoOutputFormats.Mp4, VideoOutputFormats.ThreeGp, VideoOutputFormats.M4V, VideoOutputFormats.Mkv, VideoOutputFormats.Avi, VideoOutputFormats.Mov, VideoOutputFormats.Wmv };
}
}
/// <summary>
/// Determines whether or not we can just output the original file directly
/// </summary>
protected override bool RequiresConversion()
{
if (base.RequiresConversion())
{
return true;
}
// See if the video requires conversion
if (RequiresVideoConversion())
{
return true;
}
// See if the audio requires conversion
AudioStream audioStream = (LibraryItem.AudioStreams ?? new List<AudioStream>()).FirstOrDefault();
if (audioStream != null)
{
if (RequiresAudioConversion(audioStream))
{
return true;
}
}
// Yay
return false;
}
/// <summary>
/// Translates the output file extension to the format param that follows "-f" on the ffmpeg command line
/// </summary>
private string GetFfMpegOutputFormat(VideoOutputFormats outputFormat)
{
if (outputFormat == VideoOutputFormats.Mkv)
{
return "matroska";
}
if (outputFormat == VideoOutputFormats.Ts)
{
return "mpegts";
}
if (outputFormat == VideoOutputFormats.Ogv)
{
return "ogg";
}
return outputFormat.ToString().ToLower();
}
/// <summary>
/// Creates arguments to pass to ffmpeg
/// </summary>
protected override string GetCommandLineArguments()
{
VideoOutputFormats outputFormat = GetConversionOutputFormat();
return string.Format("-i \"{0}\" -threads 0 {1} {2} -f {3} -",
LibraryItem.Path,
GetVideoArguments(outputFormat),
GetAudioArguments(outputFormat),
GetFfMpegOutputFormat(outputFormat)
);
}
/// <summary>
/// Gets video arguments to pass to ffmpeg
/// </summary>
private string GetVideoArguments(VideoOutputFormats outputFormat)
{
// Get the output codec name
string codec = GetVideoCodec(outputFormat);
string args = "-vcodec " + codec;
// If we're encoding video, add additional params
if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
// Add resolution params, if specified
if (Width.HasValue || Height.HasValue || MaxHeight.HasValue || MaxWidth.HasValue)
{
Size size = DrawingUtils.Resize(LibraryItem.Width, LibraryItem.Height, Width, Height, MaxWidth, MaxHeight);
args += string.Format(" -s {0}x{1}", size.Width, size.Height);
}
}
return args;
}
/// <summary>
/// Gets audio arguments to pass to ffmpeg
/// </summary>
private string GetAudioArguments(VideoOutputFormats outputFormat)
{
AudioStream audioStream = (LibraryItem.AudioStreams ?? new List<AudioStream>()).FirstOrDefault();
// If the video doesn't have an audio stream, return empty
if (audioStream == null)
{
return string.Empty;
}
// Get the output codec name
string codec = GetAudioCodec(audioStream, outputFormat);
string args = "-acodec " + codec;
// If we're encoding audio, add additional params
if (!codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
{
// Add the number of audio channels
int? channels = GetNumAudioChannelsParam(codec, audioStream.Channels);
if (channels.HasValue)
{
args += " -ac " + channels.Value;
}
// Add the audio sample rate
int? sampleRate = GetSampleRateParam(audioStream.SampleRate);
if (sampleRate.HasValue)
{
args += " -ar " + sampleRate.Value;
}
}
return args;
}
/// <summary>
/// Gets the name of the output video codec
/// </summary>
private string GetVideoCodec(VideoOutputFormats outputFormat)
{
// Some output containers require specific codecs
if (outputFormat == VideoOutputFormats.Webm)
{
// Per webm specification, it must be vpx
return "libvpx";
}
if (outputFormat == VideoOutputFormats.Asf)
{
return "wmv2";
}
if (outputFormat == VideoOutputFormats.Wmv)
{
return "wmv2";
}
if (outputFormat == VideoOutputFormats.Ogv)
{
return "libtheora";
}
// Skip encoding when possible
if (!RequiresVideoConversion())
{
return "copy";
}
return "libx264";
}
/// <summary>
/// Gets the name of the output audio codec
/// </summary>
private string GetAudioCodec(AudioStream audioStream, VideoOutputFormats outputFormat)
{
// Some output containers require specific codecs
if (outputFormat == VideoOutputFormats.Webm)
{
// Per webm specification, it must be vorbis
return "libvorbis";
}
if (outputFormat == VideoOutputFormats.Asf)
{
return "wmav2";
}
if (outputFormat == VideoOutputFormats.Wmv)
{
return "wmav2";
}
if (outputFormat == VideoOutputFormats.Ogv)
{
return "libvorbis";
}
// Skip encoding when possible
if (!RequiresAudioConversion(audioStream))
{
return "copy";
}
return "libvo_aacenc";
}
/// <summary>
/// Gets the number of audio channels to specify on the command line
/// </summary>
private int? GetNumAudioChannelsParam(string audioCodec, int libraryItemChannels)
{
if (libraryItemChannels > 2)
{
if (audioCodec.Equals("libvo_aacenc"))
{
// libvo_aacenc currently only supports two channel output
return 2;
}
if (audioCodec.Equals("wmav2"))
{
// wmav2 currently only supports two channel output
return 2;
}
}
return GetNumAudioChannelsParam(libraryItemChannels);
}
/// <summary>
/// Determines if the video stream requires encoding
/// </summary>
private bool RequiresVideoConversion()
{
// Check dimensions
// If a specific width is required, validate that
if (Width.HasValue)
{
if (Width.Value != LibraryItem.Width)
{
return true;
}
}
// If a specific height is required, validate that
if (Height.HasValue)
{
if (Height.Value != LibraryItem.Height)
{
return true;
}
}
// If a max width is required, validate that
if (MaxWidth.HasValue)
{
if (MaxWidth.Value < LibraryItem.Width)
{
return true;
}
}
// If a max height is required, validate that
if (MaxHeight.HasValue)
{
if (MaxHeight.Value < LibraryItem.Height)
{
return true;
}
}
// If the codec is already h264, don't encode
if (LibraryItem.Codec.IndexOf("264", StringComparison.OrdinalIgnoreCase) != -1 || LibraryItem.Codec.IndexOf("avc", StringComparison.OrdinalIgnoreCase) != -1)
{
return false;
}
return false;
}
/// <summary>
/// Determines if the audio stream requires encoding
/// </summary>
private bool RequiresAudioConversion(AudioStream audio)
{
// If the input stream has more audio channels than the client can handle, we need to encode
if (AudioChannels.HasValue)
{
if (audio.Channels > AudioChannels.Value)
{
return true;
}
}
// Aac, ac-3 and mp3 are all pretty much universally supported. No need to encode them
if (audio.Codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1)
{
return false;
}
if (audio.Codec.IndexOf("ac-3", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("ac3", StringComparison.OrdinalIgnoreCase) != -1)
{
return false;
}
if (audio.Codec.IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) != -1 || audio.Codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
{
return false;
}
return true;
}
/// <summary>
/// Gets the fixed output video height, in pixels
/// </summary>
private int? Height
{
get
{
string val = QueryString["height"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
/// <summary>
/// Gets the fixed output video width, in pixels
/// </summary>
private int? Width
{
get
{
string val = QueryString["width"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
/// <summary>
/// Gets the maximum output video height, in pixels
/// </summary>
private int? MaxHeight
{
get
{
string val = QueryString["maxheight"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
/// <summary>
/// Gets the maximum output video width, in pixels
/// </summary>
private int? MaxWidth
{
get
{
string val = QueryString["maxwidth"];
if (string.IsNullOrEmpty(val))
{
return null;
}
return int.Parse(val);
}
}
}
}

View File

@ -0,0 +1,43 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Model.Weather;
using System;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
class WeatherHandler : BaseSerializationHandler<WeatherInfo>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("weather", request);
}
protected override Task<WeatherInfo> GetObjectToSerialize()
{
// If a specific zip code was requested on the query string, use that. Otherwise use the value from configuration
string zipCode = QueryString["zipcode"];
if (string.IsNullOrWhiteSpace(zipCode))
{
zipCode = Kernel.Instance.Configuration.WeatherZipCode;
}
return Kernel.Instance.WeatherProviders.First().GetWeatherInfoAsync(zipCode);
}
protected override async Task<ResponseInfo> GetResponseInfo()
{
var info = await base.GetResponseInfo().ConfigureAwait(false);
info.CacheDuration = TimeSpan.FromMinutes(15);
return info;
}
}
}

View File

@ -0,0 +1,55 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
/// <summary>
/// Gets a single year
/// </summary>
[Export(typeof(BaseHandler))]
public class YearHandler : BaseSerializationHandler<IbnItem>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("year", request);
}
protected override Task<IbnItem> GetObjectToSerialize()
{
var parent = ApiService.GetItemById(QueryString["id"]) as Folder;
var user = ApiService.GetUserById(QueryString["userid"], true);
string year = QueryString["year"];
return GetYear(parent, user, int.Parse(year));
}
/// <summary>
/// Gets a Year
/// </summary>
private async Task<IbnItem> GetYear(Folder parent, User user, int year)
{
int count = 0;
// Get all the allowed recursive children
IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
if (item.ProductionYear.HasValue && item.ProductionYear.Value == year)
{
count++;
}
}
// Get the original entity so that we can also supply the PrimaryImagePath
return ApiService.GetIbnItem(await Kernel.Instance.ItemController.GetYear(year).ConfigureAwait(false), count);
}
}
}

View File

@ -0,0 +1,75 @@
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.DTO;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Api.HttpHandlers
{
[Export(typeof(BaseHandler))]
public class YearsHandler : BaseSerializationHandler<IbnItem[]>
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return ApiService.IsApiUrlMatch("years", request);
}
protected override Task<IbnItem[]> GetObjectToSerialize()
{
var parent = ApiService.GetItemById(QueryString["id"]) as Folder;
User user = ApiService.GetUserById(QueryString["userid"], true);
return GetAllYears(parent, user);
}
/// <summary>
/// Gets all years from all recursive children of a folder
/// The CategoryInfo class is used to keep track of the number of times each year appears
/// </summary>
private async Task<IbnItem[]> GetAllYears(Folder parent, User user)
{
var data = new Dictionary<int, int>();
// Get all the allowed recursive children
IEnumerable<BaseItem> allItems = parent.GetRecursiveChildren(user);
foreach (var item in allItems)
{
// Add the year from the item to the data dictionary
// If the year already exists, increment the count
if (item.ProductionYear == null)
{
continue;
}
if (!data.ContainsKey(item.ProductionYear.Value))
{
data.Add(item.ProductionYear.Value, 1);
}
else
{
data[item.ProductionYear.Value]++;
}
}
// Get the Year objects
Year[] entities = await Task.WhenAll(data.Keys.Select(key => Kernel.Instance.ItemController.GetYear(key))).ConfigureAwait(false);
// Convert to an array of IBNItem
var items = new IbnItem[entities.Length];
for (int i = 0; i < entities.Length; i++)
{
Year e = entities[i];
items[i] = ApiService.GetIbnItem(e, data[int.Parse(e.Name)]);
}
return items;
}
}
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{4FD51AC5-2C16-4308-A993-C3A84F3B4582}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Api</RootNamespace>
<AssemblyName>MediaBrowser.Api</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup>
<RunPostBuildEvent>Always</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Reactive.Core, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Rx-Core.2.0.20823\lib\Net45\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Rx-Interfaces.2.0.20823\lib\Net45\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Rx-Linq.2.0.20823\lib\Net45\System.Reactive.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<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="ApiService.cs" />
<Compile Include="HttpHandlers\AudioHandler.cs" />
<Compile Include="HttpHandlers\BaseMediaHandler.cs" />
<Compile Include="HttpHandlers\FavoriteStatusHandler.cs" />
<Compile Include="HttpHandlers\MovieSpecialFeaturesHandler.cs" />
<Compile Include="HttpHandlers\PlayedStatusHandler.cs" />
<Compile Include="HttpHandlers\UserHandler.cs" />
<Compile Include="HttpHandlers\GenreHandler.cs" />
<Compile Include="HttpHandlers\GenresHandler.cs" />
<Compile Include="HttpHandlers\ImageHandler.cs" />
<Compile Include="HttpHandlers\ItemHandler.cs" />
<Compile Include="HttpHandlers\ItemListHandler.cs" />
<Compile Include="HttpHandlers\PersonHandler.cs" />
<Compile Include="HttpHandlers\PluginAssemblyHandler.cs" />
<Compile Include="HttpHandlers\PluginConfigurationHandler.cs" />
<Compile Include="HttpHandlers\PluginsHandler.cs" />
<Compile Include="HttpHandlers\ServerConfigurationHandler.cs" />
<Compile Include="HttpHandlers\StudioHandler.cs" />
<Compile Include="HttpHandlers\StudiosHandler.cs" />
<Compile Include="HttpHandlers\UserAuthenticationHandler.cs" />
<Compile Include="HttpHandlers\UserItemRatingHandler.cs" />
<Compile Include="HttpHandlers\UsersHandler.cs" />
<Compile Include="HttpHandlers\VideoHandler.cs" />
<Compile Include="HttpHandlers\WeatherHandler.cs" />
<Compile Include="HttpHandlers\YearHandler.cs" />
<Compile Include="HttpHandlers\YearsHandler.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">
<Project>{9142eefa-7570-41e1-bfcc-468bb571af2f}</Project>
<Name>MediaBrowser.Common</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj">
<Project>{17e1f4e6-8abd-4fe5-9ecf-43d4b6087ba2}</Project>
<Name>MediaBrowser.Controller</Name>
</ProjectReference>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy "$(TargetPath)" "$(SolutionDir)\ProgramData-Server\Plugins\" /y</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,14 @@
using MediaBrowser.Common.Plugins;
using System.ComponentModel.Composition;
namespace MediaBrowser.Api
{
[Export(typeof(BasePlugin))]
public class Plugin : BasePlugin
{
public override string Name
{
get { return "Media Browser API"; }
}
}
}

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MediaBrowser.Api")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MediaBrowser.Api")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("13464b02-f033-48b8-9e1c-d071f8860935")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.0.20823" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.0.20823" targetFramework="net45" />
<package id="Rx-Linq" version="2.0.20823" targetFramework="net45" />
</packages>

View File

@ -0,0 +1,12 @@
using System.Net.Http;
namespace MediaBrowser.ApiInteraction
{
public class ApiClient : BaseHttpApiClient
{
public ApiClient(HttpClientHandler handler)
: base(handler)
{
}
}
}

View File

@ -0,0 +1,78 @@
using Newtonsoft.Json;
using System;
using System.IO;
namespace MediaBrowser.ApiInteraction
{
public static class DataSerializer
{
/// <summary>
/// This is an auto-generated Protobuf Serialization assembly for best performance.
/// It is created during the Model project's post-build event.
/// This means that this class can currently only handle types within the Model project.
/// If we need to, we can always add a param indicating whether or not the model serializer should be used.
/// </summary>
private static readonly ProtobufModelSerializer ProtobufModelSerializer = new ProtobufModelSerializer();
public static T DeserializeFromStream<T>(Stream stream, SerializationFormats format)
where T : class
{
if (format == ApiInteraction.SerializationFormats.Protobuf)
{
return ProtobufModelSerializer.Deserialize(stream, null, typeof(T)) as T;
}
else if (format == ApiInteraction.SerializationFormats.Jsv)
{
throw new NotImplementedException();
}
else if (format == ApiInteraction.SerializationFormats.Json)
{
using (StreamReader streamReader = new StreamReader(stream))
{
using (JsonReader jsonReader = new JsonTextReader(streamReader))
{
return JsonSerializer.Create(new JsonSerializerSettings()).Deserialize<T>(jsonReader);
}
}
}
throw new NotImplementedException();
}
public static object DeserializeFromStream(Stream stream, SerializationFormats format, Type type)
{
if (format == ApiInteraction.SerializationFormats.Protobuf)
{
return ProtobufModelSerializer.Deserialize(stream, null, type);
}
else if (format == ApiInteraction.SerializationFormats.Jsv)
{
throw new NotImplementedException();
}
else if (format == ApiInteraction.SerializationFormats.Json)
{
using (StreamReader streamReader = new StreamReader(stream))
{
using (JsonReader jsonReader = new JsonTextReader(streamReader))
{
return JsonSerializer.Create(new JsonSerializerSettings()).Deserialize(jsonReader, type);
}
}
}
throw new NotImplementedException();
}
public static void Configure()
{
}
public static bool CanDeSerializeJsv
{
get
{
return false;
}
}
}
}

View File

@ -0,0 +1,74 @@
<?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>{94CEA07A-307C-4663-AA43-7BD852808574}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.ApiInteraction.Metro</RootNamespace>
<AssemblyName>MediaBrowser.ApiInteraction.Metro</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<TargetFrameworkProfile>Profile7</TargetFrameworkProfile>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
</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>
<!-- A reference to the entire .NET Framework is automatically included -->
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="..\MediaBrowser.ApiInteraction\BaseApiClient.cs">
<Link>BaseApiClient.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.ApiInteraction\BaseHttpApiClient.cs">
<Link>BaseHttpApiClient.cs</Link>
</Compile>
<Compile Include="..\MediaBrowser.ApiInteraction\SerializationFormats.cs">
<Link>SerializationFormats.cs</Link>
</Compile>
<Compile Include="ApiClient.cs" />
<Compile Include="DataSerializer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\Json.Net\Portable\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="protobuf-net">
<HintPath>..\protobuf-net\Full\portable\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="ProtobufModelSerializer">
<HintPath>..\MediaBrowser.Model\bin\ProtobufModelSerializer.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,30 @@
using System.Resources;
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.ApiInteraction.Metro")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MediaBrowser.ApiInteraction.Metro")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: NeutralResourcesLanguage("en")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.Model", "MediaBrowser.Model\MediaBrowser.Model.csproj", "{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ApiInteraction", "MediaBrowser.ApiInteraction\MediaBrowser.ApiInteraction.csproj", "{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{F0E0E64C-2A6F-4E35-9533-D53AC07C2CD1}"
ProjectSection(SolutionItems) = preProject
.nuget\packages.config = .nuget\packages.config
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MediaBrowser.ApiInteraction.Metro", "MediaBrowser.ApiInteraction.Metro\MediaBrowser.ApiInteraction.Metro.csproj", "{94CEA07A-307C-4663-AA43-7BD852808574}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7EEEB4BB-F3E8-48FC-B4C5-70F0FFF8329B}.Release|Any CPU.Build.0 = Release|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Debug|Any CPU.Build.0 = Debug|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.ActiveCfg = Release|Any CPU
{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}.Release|Any CPU.Build.0 = Release|Any CPU
{94CEA07A-307C-4663-AA43-7BD852808574}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94CEA07A-307C-4663-AA43-7BD852808574}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94CEA07A-307C-4663-AA43-7BD852808574}.Release|Any CPU.ActiveCfg = Release|Any CPU
{94CEA07A-307C-4663-AA43-7BD852808574}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,18 @@
using System.Net.Cache;
using System.Net.Http;
namespace MediaBrowser.ApiInteraction
{
public class ApiClient : BaseHttpApiClient
{
public ApiClient(HttpClientHandler handler)
: base(handler)
{
}
public ApiClient()
: this(new WebRequestHandler { CachePolicy = new RequestCachePolicy(RequestCacheLevel.Revalidate) })
{
}
}
}

View File

@ -0,0 +1,446 @@
using MediaBrowser.Model.DTO;
using MediaBrowser.Model.Entities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MediaBrowser.ApiInteraction
{
/// <summary>
/// Provides api methods that are usable on all platforms
/// </summary>
public abstract class BaseApiClient : IDisposable
{
protected BaseApiClient()
{
DataSerializer.Configure();
}
/// <summary>
/// Gets or sets the server host name (myserver or 192.168.x.x)
/// </summary>
public string ServerHostName { get; set; }
/// <summary>
/// Gets or sets the port number used by the API
/// </summary>
public int ServerApiPort { get; set; }
/// <summary>
/// Gets the current api url based on hostname and port.
/// </summary>
protected string ApiUrl
{
get
{
return string.Format("http://{0}:{1}/mediabrowser/api", ServerHostName, ServerApiPort);
}
}
/// <summary>
/// Gets the default data format to request from the server
/// </summary>
protected SerializationFormats SerializationFormat
{
get
{
return SerializationFormats.Protobuf;
}
}
/// <summary>
/// Gets an image url that can be used to download an image from the api
/// </summary>
/// <param name="itemId">The Id of the item</param>
/// <param name="imageType">The type of image requested</param>
/// <param name="imageIndex">The image index, if there are multiple. Currently only applies to backdrops. Supply null or 0 for first backdrop.</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string GetImageUrl(Guid itemId, ImageType imageType, int? imageIndex = null, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
string url = ApiUrl + "/image";
url += "?id=" + itemId.ToString();
url += "&type=" + imageType.ToString();
if (imageIndex.HasValue)
{
url += "&index=" + imageIndex;
}
if (width.HasValue)
{
url += "&width=" + width;
}
if (height.HasValue)
{
url += "&height=" + height;
}
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth;
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight;
}
if (quality.HasValue)
{
url += "&quality=" + quality;
}
return url;
}
/// <summary>
/// Gets an image url that can be used to download an image from the api
/// </summary>
/// <param name="userId">The Id of the user</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string GetUserImageUrl(Guid userId, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
string url = ApiUrl + "/image";
url += "?userId=" + userId.ToString();
if (width.HasValue)
{
url += "&width=" + width;
}
if (height.HasValue)
{
url += "&height=" + height;
}
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth;
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight;
}
if (quality.HasValue)
{
url += "&quality=" + quality;
}
return url;
}
/// <summary>
/// Gets an image url that can be used to download an image from the api
/// </summary>
/// <param name="name">The name of the person</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string GetPersonImageUrl(string name, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
string url = ApiUrl + "/image";
url += "?personname=" + name;
if (width.HasValue)
{
url += "&width=" + width;
}
if (height.HasValue)
{
url += "&height=" + height;
}
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth;
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight;
}
if (quality.HasValue)
{
url += "&quality=" + quality;
}
return url;
}
/// <summary>
/// Gets an image url that can be used to download an image from the api
/// </summary>
/// <param name="year">The year</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string GetYearImageUrl(int year, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
string url = ApiUrl + "/image";
url += "?year=" + year;
if (width.HasValue)
{
url += "&width=" + width;
}
if (height.HasValue)
{
url += "&height=" + height;
}
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth;
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight;
}
if (quality.HasValue)
{
url += "&quality=" + quality;
}
return url;
}
/// <summary>
/// Gets an image url that can be used to download an image from the api
/// </summary>
/// <param name="name">The name of the genre</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string GetGenreImageUrl(string name, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
string url = ApiUrl + "/image";
url += "?genre=" + name;
if (width.HasValue)
{
url += "&width=" + width;
}
if (height.HasValue)
{
url += "&height=" + height;
}
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth;
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight;
}
if (quality.HasValue)
{
url += "&quality=" + quality;
}
return url;
}
/// <summary>
/// Gets an image url that can be used to download an image from the api
/// </summary>
/// <param name="name">The name of the studio</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string GetStudioImageUrl(string name, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
string url = ApiUrl + "/image";
url += "?studio=" + name;
if (width.HasValue)
{
url += "&width=" + width;
}
if (height.HasValue)
{
url += "&height=" + height;
}
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth;
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight;
}
if (quality.HasValue)
{
url += "&quality=" + quality;
}
return url;
}
/// <summary>
/// This is a helper to get a list of backdrop url's from a given ApiBaseItemWrapper. If the actual item does not have any backdrops it will return backdrops from the first parent that does.
/// </summary>
/// <param name="item">A given item.</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string[] GetBackdropImageUrls(DtoBaseItem item, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
Guid? backdropItemId;
int backdropCount;
if (item.BackdropCount == 0)
{
backdropItemId = item.ParentBackdropItemId;
backdropCount = item.ParentBackdropCount ?? 0;
}
else
{
backdropItemId = item.Id;
backdropCount = item.BackdropCount;
}
if (backdropItemId == null)
{
return new string[] { };
}
var files = new string[backdropCount];
for (int i = 0; i < backdropCount; i++)
{
files[i] = GetImageUrl(backdropItemId.Value, ImageType.Backdrop, i, width, height, maxWidth, maxHeight, quality);
}
return files;
}
/// <summary>
/// This is a helper to get the logo image url from a given ApiBaseItemWrapper. If the actual item does not have a logo, it will return the logo from the first parent that does, or null.
/// </summary>
/// <param name="item">A given item.</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="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="quality">Quality level, from 0-100. Currently only applies to JPG. The default value should suffice.</param>
public string GetLogoImageUrl(DtoBaseItem item, int? width = null, int? height = null, int? maxWidth = null, int? maxHeight = null, int? quality = null)
{
Guid? logoItemId = item.HasLogo ? item.Id : item.ParentLogoItemId;
if (logoItemId.HasValue)
{
return GetImageUrl(logoItemId.Value, ImageType.Logo, null, width, height, maxWidth, maxHeight, quality);
}
return null;
}
/// <summary>
/// Gets the url needed to stream an audio file
/// </summary>
/// <param name="itemId">The id of the item</param>
/// <param name="supportedOutputFormats">List all the output formats the decice is capable of playing. The more, the better, as it will decrease the likelyhood of having to encode, which will put a load on the server.</param>
/// <param name="maxAudioChannels">The maximum number of channels that the device can play. Omit this if it doesn't matter. Phones and tablets should generally specify 2.</param>
/// <param name="maxAudioSampleRate">The maximum sample rate that the device can play. This should generally be omitted. The server will default this to 44100, so only override if a different max is needed.</param>
public string GetAudioStreamUrl(Guid itemId, IEnumerable<AudioOutputFormats> supportedOutputFormats, int? maxAudioChannels = null, int? maxAudioSampleRate = null)
{
string url = ApiUrl + "/audio?id=" + itemId;
url += "&outputformats=" + string.Join(",", supportedOutputFormats.Select(s => s.ToString()).ToArray());
if (maxAudioChannels.HasValue)
{
url += "&audiochannels=" + maxAudioChannels.Value;
}
if (maxAudioSampleRate.HasValue)
{
url += "&audiosamplerate=" + maxAudioSampleRate.Value;
}
return url;
}
/// <summary>
/// Gets the url needed to stream a video file
/// </summary>
/// <param name="itemId">The id of the item</param>
/// <param name="supportedOutputFormats">List all the output formats the decice is capable of playing. The more, the better, as it will decrease the likelyhood of having to encode, which will put a load on the server.</param>
/// <param name="maxAudioChannels">The maximum number of channels that the device can play. Omit this if it doesn't matter. Phones and tablets should generally specify 2.</param>
/// <param name="maxAudioSampleRate">The maximum sample rate that the device can play. This should generally be omitted. The server will default this to 44100, so only override if a different max is needed.</param>
/// <param name="width">Specify this is a fixed video width is required</param>
/// <param name="height">Specify this is a fixed video height is required</param>
/// <param name="maxWidth">Specify this is a max video width is required</param>
/// <param name="maxHeight">Specify this is a max video height is required</param>
public string GetVideoStreamUrl(Guid itemId,
IEnumerable<VideoOutputFormats> supportedOutputFormats,
int? maxAudioChannels = null,
int? maxAudioSampleRate = null,
int? width = null,
int? height = null,
int? maxWidth = null,
int? maxHeight = null)
{
string url = ApiUrl + "/video?id=" + itemId;
url += "&outputformats=" + string.Join(",", supportedOutputFormats.Select(s => s.ToString()).ToArray());
if (maxAudioChannels.HasValue)
{
url += "&audiochannels=" + maxAudioChannels.Value;
}
if (maxAudioSampleRate.HasValue)
{
url += "&audiosamplerate=" + maxAudioSampleRate.Value;
}
if (width.HasValue)
{
url += "&width=" + width.Value;
}
if (height.HasValue)
{
url += "&height=" + height.Value;
}
if (maxWidth.HasValue)
{
url += "&maxWidth=" + maxWidth.Value;
}
if (maxHeight.HasValue)
{
url += "&maxHeight=" + maxHeight.Value;
}
return url;
}
protected T DeserializeFromStream<T>(Stream stream)
where T : class
{
return DataSerializer.DeserializeFromStream<T>(stream, SerializationFormat);
}
public virtual void Dispose()
{
}
}
}

View File

@ -0,0 +1,611 @@
using MediaBrowser.Model.Authentication;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.DTO;
using MediaBrowser.Model.Weather;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading.Tasks;
#if WINDOWS_PHONE
using SharpGIS;
#else
using System.Net.Http;
#endif
namespace MediaBrowser.ApiInteraction
{
/// <summary>
/// Provides api methods centered around an HttpClient
/// </summary>
public abstract class BaseHttpApiClient : BaseApiClient
{
#if WINDOWS_PHONE
public BaseHttpApiClient()
{
HttpClient = new GZipWebClient();
}
private WebClient HttpClient { get; set; }
#else
protected BaseHttpApiClient(HttpClientHandler handler)
: base()
{
handler.AutomaticDecompression = DecompressionMethods.Deflate;
HttpClient = new HttpClient(handler);
}
private HttpClient HttpClient { get; set; }
#endif
/// <summary>
/// Gets an image stream based on a url
/// </summary>
public Task<Stream> GetImageStreamAsync(string url)
{
return GetStreamAsync(url);
}
/// <summary>
/// Gets a BaseItem
/// </summary>
public async Task<DtoBaseItem> GetItemAsync(Guid id, Guid userId)
{
string url = ApiUrl + "/item?userId=" + userId.ToString();
if (id != Guid.Empty)
{
url += "&id=" + id.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem>(stream);
}
}
/// <summary>
/// Gets all Users
/// </summary>
public async Task<DtoUser[]> GetAllUsersAsync()
{
string url = ApiUrl + "/users";
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoUser[]>(stream);
}
}
/// <summary>
/// Gets all Genres
/// </summary>
public async Task<IbnItem[]> GetAllGenresAsync(Guid userId)
{
string url = ApiUrl + "/genres?userId=" + userId.ToString();
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<IbnItem[]>(stream);
}
}
/// <summary>
/// Gets in-progress items
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetInProgressItemsItemsAsync(Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=inprogressitems&userId=" + userId.ToString();
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets recently added items
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetRecentlyAddedItemsAsync(Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=recentlyaddeditems&userId=" + userId.ToString();
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets favorite items
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetFavoriteItemsAsync(Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=favorites&userId=" + userId.ToString();
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets recently added items that are unplayed.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetRecentlyAddedUnplayedItemsAsync(Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=recentlyaddedunplayeditems&userId=" + userId.ToString();
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets all Years
/// </summary>
public async Task<IbnItem[]> GetAllYearsAsync(Guid userId)
{
string url = ApiUrl + "/years?userId=" + userId.ToString();
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<IbnItem[]>(stream);
}
}
/// <summary>
/// Gets all items that contain a given Year
/// </summary>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetItemsWithYearAsync(string name, Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=itemswithyear&userId=" + userId.ToString() + "&name=" + name;
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets all items that contain a given Genre
/// </summary>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetItemsWithGenreAsync(string name, Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=itemswithgenre&userId=" + userId.ToString() + "&name=" + name;
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets all items that contain a given Person
/// </summary>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetItemsWithPersonAsync(string name, Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name;
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets all items that contain a given Person
/// </summary>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetItemsWithPersonAsync(string name, string personType, Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=itemswithperson&userId=" + userId.ToString() + "&name=" + name;
url += "&persontype=" + personType;
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets all studious
/// </summary>
public async Task<IbnItem[]> GetAllStudiosAsync(Guid userId)
{
string url = ApiUrl + "/studios?userId=" + userId.ToString();
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<IbnItem[]>(stream);
}
}
/// <summary>
/// Gets all items that contain a given Studio
/// </summary>
/// <param name="folderId">(Optional) Specify a folder Id to localize the search to a specific folder.</param>
public async Task<DtoBaseItem[]> GetItemsWithStudioAsync(string name, Guid userId, Guid? folderId = null)
{
string url = ApiUrl + "/itemlist?listtype=itemswithstudio&userId=" + userId.ToString() + "&name=" + name;
if (folderId.HasValue)
{
url += "&id=" + folderId.ToString();
}
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Gets a studio
/// </summary>
public async Task<IbnItem> GetStudioAsync(Guid userId, string name)
{
string url = ApiUrl + "/studio?userId=" + userId.ToString() + "&name=" + name;
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<IbnItem>(stream);
}
}
/// <summary>
/// Gets a genre
/// </summary>
public async Task<IbnItem> GetGenreAsync(Guid userId, string name)
{
string url = ApiUrl + "/genre?userId=" + userId.ToString() + "&name=" + name;
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<IbnItem>(stream);
}
}
/// <summary>
/// Gets a person
/// </summary>
public async Task<IbnItem> GetPersonAsync(Guid userId, string name)
{
string url = ApiUrl + "/person?userId=" + userId.ToString() + "&name=" + name;
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<IbnItem>(stream);
}
}
/// <summary>
/// Gets a year
/// </summary>
public async Task<IbnItem> GetYearAsync(Guid userId, int year)
{
string url = ApiUrl + "/year?userId=" + userId.ToString() + "&year=" + year;
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<IbnItem>(stream);
}
}
/// <summary>
/// Gets a list of plugins installed on the server
/// </summary>
public async Task<PluginInfo[]> GetInstalledPluginsAsync()
{
string url = ApiUrl + "/plugins";
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<PluginInfo[]>(stream);
}
}
/// <summary>
/// Gets a list of plugins installed on the server
/// </summary>
public Task<Stream> GetPluginAssemblyAsync(PluginInfo plugin)
{
string url = ApiUrl + "/pluginassembly?assemblyfilename=" + plugin.AssemblyFileName;
return GetStreamAsync(url);
}
/// <summary>
/// Gets the current server configuration
/// </summary>
public async Task<ServerConfiguration> GetServerConfigurationAsync()
{
string url = ApiUrl + "/ServerConfiguration";
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<ServerConfiguration>(stream);
}
}
/// <summary>
/// Gets weather information for the default location as set in configuration
/// </summary>
public async Task<object> GetPluginConfigurationAsync(PluginInfo plugin, Type configurationType)
{
string url = ApiUrl + "/PluginConfiguration?assemblyfilename=" + plugin.AssemblyFileName;
// At the moment this can't be retrieved in protobuf format
SerializationFormats format = DataSerializer.CanDeSerializeJsv ? SerializationFormats.Jsv : SerializationFormats.Json;
using (Stream stream = await GetSerializedStreamAsync(url, format).ConfigureAwait(false))
{
return DataSerializer.DeserializeFromStream(stream, format, configurationType);
}
}
/// <summary>
/// Gets the default user
/// </summary>
public async Task<DtoUser> GetDefaultUserAsync()
{
string url = ApiUrl + "/user";
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoUser>(stream);
}
}
/// <summary>
/// Gets a user by id
/// </summary>
public async Task<DtoUser> GetUserAsync(Guid id)
{
string url = ApiUrl + "/user?id=" + id.ToString();
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoUser>(stream);
}
}
/// <summary>
/// Gets weather information for the default location as set in configuration
/// </summary>
public async Task<WeatherInfo> GetWeatherInfoAsync()
{
string url = ApiUrl + "/weather";
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<WeatherInfo>(stream);
}
}
/// <summary>
/// Gets weather information for a specific zip code
/// </summary>
public async Task<WeatherInfo> GetWeatherInfoAsync(string zipCode)
{
string url = ApiUrl + "/weather?zipcode=" + zipCode;
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<WeatherInfo>(stream);
}
}
/// <summary>
/// Gets special features for a Movie
/// </summary>
public async Task<DtoBaseItem[]> GetMovieSpecialFeaturesAsync(Guid itemId, Guid userId)
{
string url = ApiUrl + "/MovieSpecialFeatures?id=" + itemId;
url += "&userid=" + userId;
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoBaseItem[]>(stream);
}
}
/// <summary>
/// Updates played status for an item
/// </summary>
public async Task<DtoUserItemData> UpdatePlayedStatusAsync(Guid itemId, Guid userId, bool wasPlayed)
{
string url = ApiUrl + "/PlayedStatus?id=" + itemId;
url += "&userid=" + userId;
url += "&played=" + (wasPlayed ? "1" : "0");
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoUserItemData>(stream);
}
}
/// <summary>
/// Updates a user's favorite status for an item and returns the updated UserItemData object.
/// </summary>
public async Task<DtoUserItemData> UpdateFavoriteStatusAsync(Guid itemId, Guid userId, bool isFavorite)
{
string url = ApiUrl + "/favoritestatus?id=" + itemId;
url += "&userid=" + userId;
url += "&isfavorite=" + (isFavorite ? "1" : "0");
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoUserItemData>(stream);
}
}
/// <summary>
/// Clears a user's rating for an item
/// </summary>
public async Task<DtoUserItemData> ClearUserItemRatingAsync(Guid itemId, Guid userId)
{
string url = ApiUrl + "/UserItemRating?id=" + itemId;
url += "&userid=" + userId;
url += "&clear=1";
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoUserItemData>(stream);
}
}
/// <summary>
/// Updates a user's rating for an item, based on likes or dislikes
/// </summary>
public async Task<DtoUserItemData> UpdateUserItemRatingAsync(Guid itemId, Guid userId, bool likes)
{
string url = ApiUrl + "/UserItemRating?id=" + itemId;
url += "&userid=" + userId;
url += "&likes=" + (likes ? "1" : "0");
using (Stream stream = await GetSerializedStreamAsync(url).ConfigureAwait(false))
{
return DeserializeFromStream<DtoUserItemData>(stream);
}
}
/// <summary>
/// Authenticates a user and returns the result
/// </summary>
public async Task<AuthenticationResult> AuthenticateUserAsync(Guid userId, string password)
{
string url = ApiUrl + "/UserAuthentication?dataformat=" + SerializationFormat.ToString();
// Create the post body
string postContent = string.Format("userid={0}", userId);
if (!string.IsNullOrEmpty(password))
{
postContent += "&password=" + password;
}
#if WINDOWS_PHONE
HttpClient.Headers["Content-Type"] = "application/x-www-form-urlencoded";
var result = await HttpClient.UploadStringTaskAsync(url, "POST", postContent);
var byteArray = Encoding.UTF8.GetBytes(result);
using (MemoryStream stream = new MemoryStream(byteArray))
{
return DeserializeFromStream<AuthenticationResult>(stream);
}
#else
HttpContent content = new StringContent(postContent, Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage msg = await HttpClient.PostAsync(url, content).ConfigureAwait(false);
using (Stream stream = await msg.Content.ReadAsStreamAsync().ConfigureAwait(false))
{
return DeserializeFromStream<AuthenticationResult>(stream);
}
#endif
}
/// <summary>
/// This is a helper around getting a stream from the server that contains serialized data
/// </summary>
private Task<Stream> GetSerializedStreamAsync(string url)
{
return GetSerializedStreamAsync(url, SerializationFormat);
}
/// <summary>
/// This is a helper around getting a stream from the server that contains serialized data
/// </summary>
private Task<Stream> GetSerializedStreamAsync(string url, SerializationFormats serializationFormat)
{
if (url.IndexOf('?') == -1)
{
url += "?dataformat=" + serializationFormat.ToString();
}
else
{
url += "&dataformat=" + serializationFormat.ToString();
}
return GetStreamAsync(url);
}
/// <summary>
/// This is just a helper around HttpClient
/// </summary>
private Task<Stream> GetStreamAsync(string url)
{
#if WINDOWS_PHONE
return HttpClient.OpenReadTaskAsync(url);
#else
return HttpClient.GetStreamAsync(url);
#endif
}
public override void Dispose()
{
#if !WINDOWS_PHONE
HttpClient.Dispose();
#endif
}
}
}

View File

@ -0,0 +1,77 @@
using ServiceStack.Text;
using System;
using System.IO;
namespace MediaBrowser.ApiInteraction
{
public static class DataSerializer
{
/// <summary>
/// This is an auto-generated Protobuf Serialization assembly for best performance.
/// It is created during the Model project's post-build event.
/// This means that this class can currently only handle types within the Model project.
/// If we need to, we can always add a param indicating whether or not the model serializer should be used.
/// </summary>
private static readonly ProtobufModelSerializer ProtobufModelSerializer = new ProtobufModelSerializer();
/// <summary>
/// Deserializes an object using generics
/// </summary>
public static T DeserializeFromStream<T>(Stream stream, SerializationFormats format)
where T : class
{
if (format == SerializationFormats.Protobuf)
{
//return Serializer.Deserialize<T>(stream);
return ProtobufModelSerializer.Deserialize(stream, null, typeof(T)) as T;
}
if (format == SerializationFormats.Jsv)
{
return TypeSerializer.DeserializeFromStream<T>(stream);
}
if (format == SerializationFormats.Json)
{
return JsonSerializer.DeserializeFromStream<T>(stream);
}
throw new NotImplementedException();
}
/// <summary>
/// Deserializes an object
/// </summary>
public static object DeserializeFromStream(Stream stream, SerializationFormats format, Type type)
{
if (format == SerializationFormats.Protobuf)
{
//throw new NotImplementedException();
return ProtobufModelSerializer.Deserialize(stream, null, type);
}
if (format == SerializationFormats.Jsv)
{
return TypeSerializer.DeserializeFromStream(type, stream);
}
if (format == SerializationFormats.Json)
{
return JsonSerializer.DeserializeFromStream(type, stream);
}
throw new NotImplementedException();
}
public static void Configure()
{
JsConfig.DateHandler = JsonDateHandler.ISO8601;
JsConfig.ExcludeTypeInfo = true;
JsConfig.IncludeNullValues = false;
}
public static bool CanDeSerializeJsv
{
get
{
return true;
}
}
}
}

View File

@ -0,0 +1,78 @@
<?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>{921C0F64-FDA7-4E9F-9E73-0CB0EEDB2422}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.ApiInteraction</RootNamespace>
<AssemblyName>MediaBrowser.ApiInteraction</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="protobuf-net">
<HintPath>..\protobuf-net\Full\net30\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="ProtobufModelSerializer">
<HintPath>..\MediaBrowser.Model\bin\ProtobufModelSerializer.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Text, Version=3.9.9.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ServiceStack.Text.3.9.9\lib\net35\ServiceStack.Text.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.WebRequest" />
<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="ApiClient.cs" />
<Compile Include="BaseApiClient.cs" />
<Compile Include="BaseHttpApiClient.cs" />
<Compile Include="DataSerializer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SerializationFormats.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,35 @@
using System.Reflection;
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.ApiInteraction")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MediaBrowser.ApiInteraction")]
[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("677618f2-de4b-44f4-8dfd-a90176297ee2")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,10 @@

namespace MediaBrowser.ApiInteraction
{
public enum SerializationFormats
{
Json,
Jsv,
Protobuf
}
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ServiceStack.Text" version="3.9.9" targetFramework="net45" />
</packages>

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

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;
namespace MediaBrowser.Common.Extensions
{
public static class BaseExtensions
{
static MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
public static Guid GetMD5(this string str)
{
lock (md5Provider)
{
return new Guid(md5Provider.ComputeHash(Encoding.Unicode.GetBytes(str)));
}
}
/// <summary>
/// Examine a list of strings assumed to be file paths to see if it contains a parent of
/// the provided path.
/// </summary>
/// <param name="lst"></param>
/// <param name="path"></param>
/// <returns></returns>
public static bool ContainsParentFolder(this List<string> lst, string path)
{
path = path.TrimEnd('\\');
foreach (var str in lst)
{
//this should be a little quicker than examining each actual parent folder...
var compare = str.TrimEnd('\\');
if (path.Equals(compare,StringComparison.OrdinalIgnoreCase)
|| (path.StartsWith(compare, StringComparison.OrdinalIgnoreCase) && path[compare.Length] == '\\')) return true;
}
return false;
}
/// <summary>
/// Helper method for Dictionaries since they throw on not-found keys
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="dictionary"></param>
/// <param name="key"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public static U GetValueOrDefault<T, U>(this Dictionary<T, U> dictionary, T key, U defaultValue)
{
U val;
if (!dictionary.TryGetValue(key, out val))
{
val = defaultValue;
}
return val;
}
}
}

View File

@ -0,0 +1,154 @@
using System.Configuration;
using System.IO;
using System.Reflection;
namespace MediaBrowser.Common.Kernel
{
/// <summary>
/// Provides a base class to hold common application paths used by both the Ui and Server.
/// This can be subclassed to add application-specific paths.
/// </summary>
public abstract class BaseApplicationPaths
{
private string _programDataPath;
/// <summary>
/// Gets the path to the program data folder
/// </summary>
public string ProgramDataPath
{
get
{
if (_programDataPath == null)
{
_programDataPath = GetProgramDataPath();
}
return _programDataPath;
}
}
private string _pluginsPath;
/// <summary>
/// Gets the path to the plugin directory
/// </summary>
public string PluginsPath
{
get
{
if (_pluginsPath == null)
{
_pluginsPath = Path.Combine(ProgramDataPath, "plugins");
if (!Directory.Exists(_pluginsPath))
{
Directory.CreateDirectory(_pluginsPath);
}
}
return _pluginsPath;
}
}
private string _pluginConfigurationsPath;
/// <summary>
/// Gets the path to the plugin configurations directory
/// </summary>
public string PluginConfigurationsPath
{
get
{
if (_pluginConfigurationsPath == null)
{
_pluginConfigurationsPath = Path.Combine(PluginsPath, "configurations");
if (!Directory.Exists(_pluginConfigurationsPath))
{
Directory.CreateDirectory(_pluginConfigurationsPath);
}
}
return _pluginConfigurationsPath;
}
}
private string _logDirectoryPath;
/// <summary>
/// Gets the path to the log directory
/// </summary>
public string LogDirectoryPath
{
get
{
if (_logDirectoryPath == null)
{
_logDirectoryPath = Path.Combine(ProgramDataPath, "logs");
if (!Directory.Exists(_logDirectoryPath))
{
Directory.CreateDirectory(_logDirectoryPath);
}
}
return _logDirectoryPath;
}
}
private string _configurationDirectoryPath;
/// <summary>
/// Gets the path to the application configuration root directory
/// </summary>
public string ConfigurationDirectoryPath
{
get
{
if (_configurationDirectoryPath == null)
{
_configurationDirectoryPath = Path.Combine(ProgramDataPath, "config");
if (!Directory.Exists(_configurationDirectoryPath))
{
Directory.CreateDirectory(_configurationDirectoryPath);
}
}
return _configurationDirectoryPath;
}
}
private string _systemConfigurationFilePath;
/// <summary>
/// Gets the path to the system configuration file
/// </summary>
public string SystemConfigurationFilePath
{
get
{
if (_systemConfigurationFilePath == null)
{
_systemConfigurationFilePath = Path.Combine(ConfigurationDirectoryPath, "system.xml");
}
return _systemConfigurationFilePath;
}
}
/// <summary>
/// Gets the path to the application's ProgramDataFolder
/// </summary>
private static string GetProgramDataPath()
{
string programDataPath = ConfigurationManager.AppSettings["ProgramDataPath"];
// If it's a relative path, e.g. "..\"
if (!Path.IsPathRooted(programDataPath))
{
string path = Assembly.GetExecutingAssembly().Location;
path = Path.GetDirectoryName(path);
programDataPath = Path.Combine(path, programDataPath);
programDataPath = Path.GetFullPath(programDataPath);
}
if (!Directory.Exists(programDataPath))
{
Directory.CreateDirectory(programDataPath);
}
return programDataPath;
}
}
}

View File

@ -0,0 +1,345 @@
using MediaBrowser.Common.Events;
using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Mef;
using MediaBrowser.Common.Net;
using MediaBrowser.Common.Net.Handlers;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Progress;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Kernel
{
/// <summary>
/// Represents a shared base kernel for both the Ui and server apps
/// </summary>
public abstract class BaseKernel<TConfigurationType, TApplicationPathsType> : IDisposable, IKernel
where TConfigurationType : BaseApplicationConfiguration, 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>
/// Gets the current configuration
/// </summary>
public TConfigurationType Configuration { get; private set; }
public TApplicationPathsType ApplicationPaths { get; private set; }
/// <summary>
/// Gets the list of currently loaded plugins
/// </summary>
[ImportMany(typeof(BasePlugin))]
public IEnumerable<BasePlugin> Plugins { get; private set; }
/// <summary>
/// Gets the list of currently registered http handlers
/// </summary>
[ImportMany(typeof(BaseHandler))]
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>
/// 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.
/// </summary>
public HttpServer HttpServer { get; private set; }
/// <summary>
/// This subscribes to HttpListener requests and finds the appropate BaseHandler to process it
/// </summary>
private IDisposable HttpListener { get; set; }
/// <summary>
/// Gets the MEF CompositionContainer
/// </summary>
private CompositionContainer CompositionContainer { get; set; }
protected virtual string HttpServerUrlPrefix
{
get
{
return "http://+:" + Configuration.HttpServerPortNumber + "/mediabrowser/";
}
}
/// <summary>
/// Gets the kernel context. Subclasses will have to override.
/// </summary>
public abstract KernelContext KernelContext { get; }
/// <summary>
/// Initializes the Kernel
/// </summary>
public async Task Init(IProgress<TaskProgress> progress)
{
Logger.Kernel = this;
// Performs initializations that only occur once
InitializeInternal(progress);
// Performs initializations that can be reloaded at anytime
await Reload(progress).ConfigureAwait(false);
}
/// <summary>
/// Performs initializations that only occur once
/// </summary>
protected virtual void InitializeInternal(IProgress<TaskProgress> progress)
{
ApplicationPaths = new TApplicationPathsType();
ReportProgress(progress, "Loading Configuration");
ReloadConfiguration();
ReportProgress(progress, "Loading Http Server");
ReloadHttpServer();
}
/// <summary>
/// Performs initializations that can be reloaded at anytime
/// </summary>
public async Task Reload(IProgress<TaskProgress> progress)
{
OnReloadBeginning(progress);
await ReloadInternal(progress).ConfigureAwait(false);
OnReloadCompleted(progress);
ReportProgress(progress, "Kernel.Reload Complete");
}
/// <summary>
/// Performs initializations that can be reloaded at anytime
/// </summary>
protected virtual async Task ReloadInternal(IProgress<TaskProgress> progress)
{
await Task.Run(() =>
{
ReportProgress(progress, "Loading Plugins");
ReloadComposableParts();
}).ConfigureAwait(false);
}
/// <summary>
/// Uses MEF to locate plugins
/// Subclasses can use this to locate types within plugins
/// </summary>
private void ReloadComposableParts()
{
DisposeComposableParts();
CompositionContainer = GetCompositionContainer(includeCurrentAssembly: true);
CompositionContainer.ComposeParts(this);
OnComposablePartsLoaded();
CompositionContainer.Catalog.Dispose();
}
/// <summary>
/// Constructs an MEF CompositionContainer based on the current running assembly and all plugin assemblies
/// </summary>
public CompositionContainer GetCompositionContainer(bool includeCurrentAssembly = false)
{
// Gets all plugin assemblies by first reading all bytes of the .dll and calling Assembly.Load against that
// This will prevent the .dll file from getting locked, and allow us to replace it when needed
IEnumerable<Assembly> pluginAssemblies = Directory.GetFiles(ApplicationPaths.PluginsPath, "*.dll", SearchOption.TopDirectoryOnly).Select(f => Assembly.Load(File.ReadAllBytes((f))));
var catalogs = new List<ComposablePartCatalog>();
catalogs.AddRange(pluginAssemblies.Select(a => new AssemblyCatalog(a)));
// Include composable parts in the Common assembly
catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
if (includeCurrentAssembly)
{
// Include composable parts in the subclass assembly
catalogs.Add(new AssemblyCatalog(GetType().Assembly));
}
return MefUtils.GetSafeCompositionContainer(catalogs);
}
/// <summary>
/// Fires after MEF finishes finding composable parts within plugin assemblies
/// </summary>
protected virtual void OnComposablePartsLoaded()
{
foreach (var logger in Loggers)
{
logger.Initialize(this);
}
// Start-up each plugin
foreach (var plugin in Plugins)
{
plugin.Initialize(this);
}
}
/// <summary>
/// Reloads application configuration from the config file
/// </summary>
private void ReloadConfiguration()
{
//Configuration information for anything other than server-specific configuration will have to come via the API... -ebr
// Deserialize config
// 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();
XmlSerializer.SerializeToFile(Configuration, ApplicationPaths.SystemConfigurationFilePath);
}
}
/// <summary>
/// Restarts the Http Server, or starts it if not currently running
/// </summary>
private void ReloadHttpServer()
{
DisposeHttpServer();
HttpServer = new HttpServer(HttpServerUrlPrefix);
HttpListener = HttpServer.Subscribe(ctx =>
{
BaseHandler handler = HttpHandlers.FirstOrDefault(h => h.HandlesRequest(ctx.Request));
// Find the appropiate http handler
if (handler != null)
{
// Need to create a new instance because handlers are currently stateful
handler = Activator.CreateInstance(handler.GetType()) as BaseHandler;
// No need to await this, despite the compiler warning
handler.ProcessRequest(ctx);
}
});
}
/// <summary>
/// Disposes all resources currently in use.
/// </summary>
public virtual void Dispose()
{
Logger.LogInfo("Beginning Kernel.Dispose");
DisposeHttpServer();
DisposeComposableParts();
}
/// <summary>
/// Disposes all objects gathered through MEF composable parts
/// </summary>
protected virtual void DisposeComposableParts()
{
if (CompositionContainer != null)
{
CompositionContainer.Dispose();
}
}
/// <summary>
/// Disposes the current HttpServer
/// </summary>
private void DisposeHttpServer()
{
if (HttpServer != null)
{
Logger.LogInfo("Disposing Http Server");
HttpServer.Dispose();
}
if (HttpListener != null)
{
HttpListener.Dispose();
}
}
/// <summary>
/// Gets the current application version
/// </summary>
public Version ApplicationVersion
{
get
{
return GetType().Assembly.GetName().Version;
}
}
protected void ReportProgress(IProgress<TaskProgress> progress, string message)
{
progress.Report(new TaskProgress { Description = message });
Logger.LogInfo(message);
}
BaseApplicationPaths IKernel.ApplicationPaths
{
get { return ApplicationPaths; }
}
}
public interface IKernel
{
BaseApplicationPaths ApplicationPaths { get; }
KernelContext KernelContext { get; }
Task Init(IProgress<TaskProgress> progress);
Task Reload(IProgress<TaskProgress> progress);
IEnumerable<BaseLogger> Loggers { get; }
void Dispose();
}
}

View File

@ -0,0 +1,9 @@

namespace MediaBrowser.Common.Kernel
{
public enum KernelContext
{
Server,
Ui
}
}

View File

@ -0,0 +1,16 @@
using MediaBrowser.Common.Kernel;
using System;
namespace MediaBrowser.Common.Logging
{
public abstract class BaseLogger : IDisposable
{
public abstract void Initialize(IKernel kernel);
public abstract void LogEntry(LogRow row);
public virtual void Dispose()
{
Logger.LogInfo("Disposing " + GetType().Name);
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Common.Logging
{
public struct LogRow
{
const string TimePattern = "h:mm:ss.fff tt d/M/yyyy";
public LogSeverity Severity { get; set; }
public string Message { get; set; }
public int ThreadId { get; set; }
public string ThreadName { get; set; }
public DateTime Time { get; set; }
public override string ToString()
{
var data = new List<string>();
data.Add(Time.ToString(TimePattern));
data.Add(Severity.ToString());
if (!string.IsNullOrEmpty(Message))
{
data.Add(Encode(Message));
}
data.Add(ThreadId.ToString());
if (!string.IsNullOrEmpty(ThreadName))
{
data.Add(Encode(ThreadName));
}
return string.Join(" , ", data.ToArray());
}
private string Encode(string str)
{
return (str ?? "").Replace(",", ",,").Replace(Environment.NewLine, " [n] ");
}
}
}

View File

@ -0,0 +1,14 @@
using System;
namespace MediaBrowser.Common.Logging
{
[Flags]
public enum LogSeverity
{
None = 0,
Debug = 1,
Info = 2,
Warning = 4,
Error = 8
}
}

View File

@ -0,0 +1,93 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using MediaBrowser.Common.Kernel;
namespace MediaBrowser.Common.Logging
{
public static class Logger
{
internal static IKernel Kernel { get; set; }
public static void LogInfo(string message, params object[] paramList)
{
LogEntry(message, LogSeverity.Info, paramList);
}
public static void LogDebugInfo(string message, params object[] paramList)
{
LogEntry(message, LogSeverity.Debug, paramList);
}
public static void LogError(string message, params object[] paramList)
{
LogEntry(message, LogSeverity.Error, paramList);
}
public static void LogException(Exception ex, params object[] paramList)
{
LogException(string.Empty, ex, paramList);
}
public static void LogException(string message, Exception ex, params object[] 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)
{
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

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

@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{9142EEFA-7570-41E1-BFCC-468BB571AF2F}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MediaBrowser.Common</RootNamespace>
<AssemblyName>MediaBrowser.Common</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<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>
<PropertyGroup>
<ApplicationIcon>Resources\Images\Icon.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<Reference Include="MahApps.Metro">
<HintPath>..\packages\MahApps.Metro.0.9.0.0\lib\net40\MahApps.Metro.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="protobuf-net">
<HintPath>..\protobuf-net\Full\net30\protobuf-net.dll</HintPath>
</Reference>
<Reference Include="ProtobufModelSerializer">
<HintPath>..\MediaBrowser.Model\bin\ProtobufModelSerializer.dll</HintPath>
</Reference>
<Reference Include="ServiceStack.Text, Version=3.9.9.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ServiceStack.Text.3.9.9\lib\net35\ServiceStack.Text.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Reactive.Core, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Rx-Core.2.0.20823\lib\Net45\System.Reactive.Core.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Interfaces, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Rx-Interfaces.2.0.20823\lib\Net45\System.Reactive.Interfaces.dll</HintPath>
</Reference>
<Reference Include="System.Reactive.Linq, Version=2.0.20823.0, Culture=neutral, PublicKeyToken=f300afd708cefcd3, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Rx-Linq.2.0.20823\lib\Net45\System.Reactive.Linq.dll</HintPath>
</Reference>
<Reference Include="System.Runtime.Remoting" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Web" />
<Reference Include="System.Windows.Interactivity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\MahApps.Metro.0.9.0.0\lib\net40\System.Windows.Interactivity.dll</HintPath>
</Reference>
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Extensions\BaseExtensions.cs" />
<Compile Include="Events\GenericEventArgs.cs" />
<Compile Include="Kernel\BaseApplicationPaths.cs" />
<Compile Include="Logging\BaseLogger.cs" />
<Compile Include="Logging\LogSeverity.cs" />
<Compile Include="Logging\TraceFileLogger.cs" />
<Compile Include="Mef\MefUtils.cs" />
<Compile Include="Net\Handlers\StaticFileHandler.cs" />
<Compile Include="Net\MimeTypes.cs" />
<Compile Include="Plugins\BaseTheme.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Serialization\JsonSerializer.cs" />
<Compile Include="Kernel\BaseKernel.cs" />
<Compile Include="Kernel\KernelContext.cs" />
<Compile Include="Logging\Logger.cs" />
<Compile Include="Logging\LogRow.cs" />
<Compile Include="Net\Handlers\BaseEmbeddedResourceHandler.cs" />
<Compile Include="Net\Handlers\BaseHandler.cs" />
<Compile Include="Net\Handlers\BaseSerializationHandler.cs" />
<Compile Include="Net\HttpServer.cs" />
<Compile Include="Net\Request.cs" />
<Compile Include="Plugins\BasePlugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Serialization\JsvSerializer.cs" />
<Compile Include="Serialization\ProtobufSerializer.cs" />
<Compile Include="Serialization\XmlSerializer.cs" />
<Compile Include="UI\BaseApplication.cs" />
<Compile Include="UI\Splash.xaml.cs">
<DependentUpon>Splash.xaml</DependentUpon>
</Compile>
<Compile Include="UI\SingleInstance.cs" />
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj">
<Project>{7eeeb4bb-f3e8-48fc-b4c5-70f0fff8329b}</Project>
<Name>MediaBrowser.Model</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Page Include="UI\Splash.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Images\mblogoblack.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Images\Icon.ico" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Images\mblogowhite.png" />
</ItemGroup>
<ItemGroup>
<Resource Include="Resources\Images\spinner.gif" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,43 @@
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
namespace MediaBrowser.Common.Mef
{
public static class MefUtils
{
/// <summary>
/// Plugins that live on both the server and UI are going to have references to assemblies from both sides.
/// But looks for Parts on one side, it will throw an exception when it seems Types from the other side that it doesn't have a reference to.
/// For example, a plugin provides a Resolver. When MEF runs in the UI, it will throw an exception when it sees the resolver because there won't be a reference to the base class.
/// This method will catch those exceptions while retining the list of Types that MEF is able to resolve.
/// </summary>
public static CompositionContainer GetSafeCompositionContainer(IEnumerable<ComposablePartCatalog> catalogs)
{
var newList = new List<ComposablePartCatalog>();
// Go through each Catalog
foreach (var catalog in catalogs)
{
try
{
// Try to have MEF find Parts
catalog.Parts.ToArray();
// If it succeeds we can use the entire catalog
newList.Add(catalog);
}
catch (ReflectionTypeLoadException ex)
{
// If it fails we can still get a list of the Types it was able to resolve and create TypeCatalogs
var typeCatalogs = ex.Types.Where(t => t != null).Select(t => new TypeCatalog(t));
newList.AddRange(typeCatalogs);
}
}
return new CompositionContainer(new AggregateCatalog(newList));
}
}
}

View File

@ -0,0 +1,23 @@
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Net.Handlers
{
public abstract class BaseEmbeddedResourceHandler : BaseHandler
{
protected BaseEmbeddedResourceHandler(string resourcePath)
: base()
{
ResourcePath = resourcePath;
}
protected string ResourcePath { get; set; }
protected override Task WriteResponseToOutputStream(Stream stream)
{
return GetEmbeddedResourceStream().CopyToAsync(stream);
}
protected abstract Stream GetEmbeddedResourceStream();
}
}

View File

@ -0,0 +1,430 @@
using MediaBrowser.Common.Logging;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Net.Handlers
{
public abstract class BaseHandler
{
public abstract bool HandlesRequest(HttpListenerRequest request);
private Stream CompressedStream { get; set; }
public virtual bool? UseChunkedEncoding
{
get
{
return null;
}
}
private bool _totalContentLengthDiscovered;
private long? _totalContentLength;
public long? TotalContentLength
{
get
{
if (!_totalContentLengthDiscovered)
{
_totalContentLength = GetTotalContentLength();
_totalContentLengthDiscovered = true;
}
return _totalContentLength;
}
}
protected virtual bool SupportsByteRangeRequests
{
get
{
return false;
}
}
/// <summary>
/// The original HttpListenerContext
/// </summary>
protected HttpListenerContext HttpListenerContext { get; set; }
/// <summary>
/// The original QueryString
/// </summary>
protected NameValueCollection QueryString
{
get
{
return HttpListenerContext.Request.QueryString;
}
}
private List<KeyValuePair<long, long?>> _requestedRanges;
protected IEnumerable<KeyValuePair<long, long?>> RequestedRanges
{
get
{
if (_requestedRanges == null)
{
_requestedRanges = new List<KeyValuePair<long, long?>>();
if (IsRangeRequest)
{
// Example: bytes=0-,32-63
string[] ranges = HttpListenerContext.Request.Headers["Range"].Split('=')[1].Split(',');
foreach (string range in ranges)
{
string[] vals = range.Split('-');
long start = 0;
long? end = null;
if (!string.IsNullOrEmpty(vals[0]))
{
start = long.Parse(vals[0]);
}
if (!string.IsNullOrEmpty(vals[1]))
{
end = long.Parse(vals[1]);
}
_requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
}
}
}
return _requestedRanges;
}
}
protected bool IsRangeRequest
{
get
{
return HttpListenerContext.Request.Headers.AllKeys.Contains("Range");
}
}
private bool ClientSupportsCompression
{
get
{
string enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
return enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 || enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1;
}
}
private string CompressionMethod
{
get
{
string enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
if (enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1)
{
return "deflate";
}
if (enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
{
return "gzip";
}
return null;
}
}
public virtual async Task ProcessRequest(HttpListenerContext ctx)
{
HttpListenerContext = ctx;
string url = ctx.Request.Url.ToString();
Logger.LogInfo("Http Server received request at: " + url);
Logger.LogInfo("Http Headers: " + string.Join(",", ctx.Request.Headers.AllKeys.Select(k => k + "=" + ctx.Request.Headers[k])));
ctx.Response.AddHeader("Access-Control-Allow-Origin", "*");
ctx.Response.KeepAlive = true;
try
{
if (SupportsByteRangeRequests && IsRangeRequest)
{
ctx.Response.Headers["Accept-Ranges"] = "bytes";
}
ResponseInfo responseInfo = await GetResponseInfo().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;
}
ctx.Response.ContentType = responseInfo.ContentType;
if (!string.IsNullOrEmpty(responseInfo.Etag))
{
ctx.Response.Headers["ETag"] = responseInfo.Etag;
}
if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
{
DateTime ifModifiedSince;
if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"], out ifModifiedSince))
{
// If the cache hasn't expired yet just return a 304
if (IsCacheValid(ifModifiedSince.ToUniversalTime(), responseInfo.CacheDuration, responseInfo.DateLastModified))
{
// ETag must also match (if supplied)
if ((responseInfo.Etag ?? string.Empty).Equals(ctx.Request.Headers["If-None-Match"] ?? string.Empty))
{
responseInfo.StatusCode = 304;
}
}
}
}
Logger.LogInfo("Responding with status code {0} for url {1}", responseInfo.StatusCode, url);
if (responseInfo.IsResponseValid)
{
await ProcessUncachedRequest(ctx, responseInfo).ConfigureAwait(false);
}
else
{
ctx.Response.StatusCode = responseInfo.StatusCode;
ctx.Response.SendChunked = false;
}
}
catch (Exception ex)
{
// It might be too late if some response data has already been transmitted, but try to set this
ctx.Response.StatusCode = 500;
Logger.LogException(ex);
}
finally
{
DisposeResponseStream();
}
}
private async Task ProcessUncachedRequest(HttpListenerContext ctx, ResponseInfo responseInfo)
{
long? totalContentLength = TotalContentLength;
// By default, use chunked encoding if we don't know the content length
bool useChunkedEncoding = UseChunkedEncoding == null ? (totalContentLength == null) : UseChunkedEncoding.Value;
// Don't force this to true. HttpListener will default it to true if supported by the client.
if (!useChunkedEncoding)
{
ctx.Response.SendChunked = false;
}
// Set the content length, if we know it
if (totalContentLength.HasValue)
{
ctx.Response.ContentLength64 = totalContentLength.Value;
}
var compressResponse = responseInfo.CompressResponse && ClientSupportsCompression;
// Add the compression header
if (compressResponse)
{
ctx.Response.AddHeader("Content-Encoding", CompressionMethod);
}
if (responseInfo.DateLastModified.HasValue)
{
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
ctx.Response.StatusCode = responseInfo.StatusCode;
if (responseInfo.IsResponseValid)
{
// Finally, write the response data
Stream outputStream = ctx.Response.OutputStream;
if (compressResponse)
{
if (CompressionMethod.Equals("deflate", StringComparison.OrdinalIgnoreCase))
{
CompressedStream = new DeflateStream(outputStream, CompressionLevel.Fastest, false);
}
else
{
CompressedStream = new GZipStream(outputStream, CompressionLevel.Fastest, false);
}
outputStream = CompressedStream;
}
await WriteResponseToOutputStream(outputStream).ConfigureAwait(false);
}
else
{
ctx.Response.SendChunked = false;
}
}
private void CacheResponse(HttpListenerResponse response, TimeSpan duration)
{
response.Headers[HttpResponseHeader.CacheControl] = "public, max-age=" + Convert.ToInt32(duration.TotalSeconds);
response.Headers[HttpResponseHeader.Expires] = DateTime.UtcNow.Add(duration).ToString("r");
}
protected abstract Task WriteResponseToOutputStream(Stream stream);
protected virtual void DisposeResponseStream()
{
if (CompressedStream != null)
{
CompressedStream.Dispose();
}
HttpListenerContext.Response.OutputStream.Dispose();
}
private bool IsCacheValid(DateTime ifModifiedSince, TimeSpan cacheDuration, DateTime? dateModified)
{
if (dateModified.HasValue)
{
DateTime lastModified = NormalizeDateForComparison(dateModified.Value);
ifModifiedSince = NormalizeDateForComparison(ifModifiedSince);
return lastModified <= ifModifiedSince;
}
DateTime cacheExpirationDate = ifModifiedSince.Add(cacheDuration);
if (DateTime.UtcNow < cacheExpirationDate)
{
return true;
}
return false;
}
/// <summary>
/// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
/// </summary>
private DateTime NormalizeDateForComparison(DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
}
protected virtual long? GetTotalContentLength()
{
return null;
}
protected abstract Task<ResponseInfo> GetResponseInfo();
private Hashtable _formValues;
/// <summary>
/// Gets a value from form POST data
/// </summary>
protected async Task<string> GetFormValue(string name)
{
if (_formValues == null)
{
_formValues = await GetFormValues(HttpListenerContext.Request).ConfigureAwait(false);
}
if (_formValues.ContainsKey(name))
{
return _formValues[name].ToString();
}
return null;
}
/// <summary>
/// Extracts form POST data from a request
/// </summary>
private async Task<Hashtable> GetFormValues(HttpListenerRequest request)
{
var formVars = new Hashtable();
if (request.HasEntityBody)
{
if (request.ContentType.IndexOf("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) != -1)
{
using (Stream requestBody = request.InputStream)
{
using (var reader = new StreamReader(requestBody, request.ContentEncoding))
{
string s = await reader.ReadToEndAsync().ConfigureAwait(false);
string[] pairs = s.Split('&');
for (int x = 0; x < pairs.Length; x++)
{
string pair = pairs[x];
int index = pair.IndexOf('=');
if (index != -1)
{
string name = pair.Substring(0, index);
string value = pair.Substring(index + 1);
formVars.Add(name, value);
}
}
}
}
}
}
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

@ -0,0 +1,90 @@
using MediaBrowser.Common.Serialization;
using System;
using System.IO;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Net.Handlers
{
public abstract class BaseSerializationHandler<T> : BaseHandler
where T : class
{
public SerializationFormat SerializationFormat
{
get
{
string format = QueryString["dataformat"];
if (string.IsNullOrEmpty(format))
{
return SerializationFormat.Json;
}
return (SerializationFormat)Enum.Parse(typeof(SerializationFormat), format, true);
}
}
protected string ContentType
{
get
{
switch (SerializationFormat)
{
case SerializationFormat.Jsv:
return "text/plain";
case SerializationFormat.Protobuf:
return "application/x-protobuf";
default:
return MimeTypes.JsonMimeType;
}
}
}
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 override Task WriteResponseToOutputStream(Stream stream)
{
return Task.Run(() =>
{
switch (SerializationFormat)
{
case SerializationFormat.Jsv:
JsvSerializer.SerializeToStream(_objectToSerialize, stream);
break;
case SerializationFormat.Protobuf:
ProtobufSerializer.SerializeToStream(_objectToSerialize, stream);
break;
default:
JsonSerializer.SerializeToStream(_objectToSerialize, stream);
break;
}
});
}
}
public enum SerializationFormat
{
Json,
Jsv,
Protobuf
}
}

View File

@ -0,0 +1,249 @@
using MediaBrowser.Common.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
namespace MediaBrowser.Common.Net.Handlers
{
public class StaticFileHandler : BaseHandler
{
public override bool HandlesRequest(HttpListenerRequest request)
{
return false;
}
private string _path;
public virtual string Path
{
get
{
if (!string.IsNullOrWhiteSpace(_path))
{
return _path;
}
return QueryString["path"];
}
set
{
_path = value;
}
}
private Stream SourceStream { get; set; }
protected override bool SupportsByteRangeRequests
{
get
{
return true;
}
}
private bool ShouldCompressResponse(string contentType)
{
// Can't compress these
if (IsRangeRequest)
{
return false;
}
// Don't compress media
if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
{
return false;
}
// It will take some work to support compression within this handler
return false;
}
protected override long? GetTotalContentLength()
{
return SourceStream.Length;
}
protected override Task<ResponseInfo> GetResponseInfo()
{
ResponseInfo info = new ResponseInfo
{
ContentType = MimeTypes.GetMimeType(Path),
};
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)
{
info.DateLastModified = File.GetLastWriteTimeUtc(Path);
}
return Task.FromResult<ResponseInfo>(info);
}
protected override Task WriteResponseToOutputStream(Stream stream)
{
if (IsRangeRequest)
{
KeyValuePair<long, long?> requestedRange = RequestedRanges.First();
// If the requested range is "0-" and we know the total length, we can optimize by avoiding having to buffer the content into memory
if (requestedRange.Value == null && TotalContentLength != null)
{
return ServeCompleteRangeRequest(requestedRange, stream);
}
if (TotalContentLength.HasValue)
{
// This will have to buffer a portion of the content into memory
return ServePartialRangeRequestWithKnownTotalContentLength(requestedRange, stream);
}
// This will have to buffer the entire content into memory
return ServePartialRangeRequestWithUnknownTotalContentLength(requestedRange, stream);
}
return SourceStream.CopyToAsync(stream);
}
protected override void DisposeResponseStream()
{
base.DisposeResponseStream();
if (SourceStream != null)
{
SourceStream.Dispose();
}
}
/// <summary>
/// Handles a range request of "bytes=0-"
/// This will serve the complete content and add the content-range header
/// </summary>
private Task ServeCompleteRangeRequest(KeyValuePair<long, long?> requestedRange, Stream responseStream)
{
long totalContentLength = TotalContentLength.Value;
long rangeStart = requestedRange.Key;
long rangeEnd = totalContentLength - 1;
long rangeLength = 1 + rangeEnd - rangeStart;
// Content-Length is the length of what we're serving, not the original content
HttpListenerContext.Response.ContentLength64 = rangeLength;
HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
if (rangeStart > 0)
{
SourceStream.Position = rangeStart;
}
return SourceStream.CopyToAsync(responseStream);
}
/// <summary>
/// Serves a partial range request where the total content length is not known
/// </summary>
private async Task ServePartialRangeRequestWithUnknownTotalContentLength(KeyValuePair<long, long?> requestedRange, Stream responseStream)
{
// Read the entire stream so that we can determine the length
byte[] bytes = await ReadBytes(SourceStream, 0, null).ConfigureAwait(false);
long totalContentLength = bytes.LongLength;
long rangeStart = requestedRange.Key;
long rangeEnd = requestedRange.Value ?? (totalContentLength - 1);
long rangeLength = 1 + rangeEnd - rangeStart;
// Content-Length is the length of what we're serving, not the original content
HttpListenerContext.Response.ContentLength64 = rangeLength;
HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
await responseStream.WriteAsync(bytes, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength)).ConfigureAwait(false);
}
/// <summary>
/// Serves a partial range request where the total content length is already known
/// </summary>
private async Task ServePartialRangeRequestWithKnownTotalContentLength(KeyValuePair<long, long?> requestedRange, Stream responseStream)
{
long totalContentLength = TotalContentLength.Value;
long rangeStart = requestedRange.Key;
long rangeEnd = requestedRange.Value ?? (totalContentLength - 1);
long rangeLength = 1 + rangeEnd - rangeStart;
// Only read the bytes we need
byte[] bytes = await ReadBytes(SourceStream, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength)).ConfigureAwait(false);
// Content-Length is the length of what we're serving, not the original content
HttpListenerContext.Response.ContentLength64 = rangeLength;
HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength);
await responseStream.WriteAsync(bytes, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false);
}
/// <summary>
/// Reads bytes from a stream
/// </summary>
/// <param name="input">The input stream</param>
/// <param name="start">The starting position</param>
/// <param name="count">The number of bytes to read, or null to read to the end.</param>
private async Task<byte[]> ReadBytes(Stream input, int start, int? count)
{
if (start > 0)
{
input.Position = start;
}
if (count == null)
{
var buffer = new byte[16 * 1024];
using (var ms = new MemoryStream())
{
int read;
while ((read = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0)
{
await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false);
}
return ms.ToArray();
}
}
else
{
var buffer = new byte[count.Value];
using (var ms = new MemoryStream())
{
int read = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false);
return ms.ToArray();
}
}
}
}
}

View File

@ -0,0 +1,40 @@
using System;
using System.Net;
using System.Reactive.Linq;
namespace MediaBrowser.Common.Net
{
public class HttpServer : IObservable<HttpListenerContext>, IDisposable
{
private readonly HttpListener _listener;
private readonly IObservable<HttpListenerContext> _stream;
public HttpServer(string url)
{
_listener = new HttpListener();
_listener.Prefixes.Add(url);
_listener.Start();
_stream = ObservableHttpContext();
}
private IObservable<HttpListenerContext> ObservableHttpContext()
{
return Observable.Create<HttpListenerContext>(obs =>
Observable.FromAsync(() => _listener.GetContextAsync())
.Subscribe(obs))
.Repeat()
.Retry()
.Publish()
.RefCount();
}
public void Dispose()
{
_listener.Stop();
}
public IDisposable Subscribe(IObserver<HttpListenerContext> observer)
{
return _stream.Subscribe(observer);
}
}
}

View File

@ -0,0 +1,160 @@
using System;
using System.IO;
namespace MediaBrowser.Common.Net
{
public static class MimeTypes
{
public static string JsonMimeType = "application/json";
public static string GetMimeType(string path)
{
var ext = Path.GetExtension(path);
// http://en.wikipedia.org/wiki/Internet_media_type
// Add more as needed
// Type video
if (ext.EndsWith("mpg", StringComparison.OrdinalIgnoreCase) || ext.EndsWith("mpeg", StringComparison.OrdinalIgnoreCase))
{
return "video/mpeg";
}
if (ext.EndsWith("mp4", StringComparison.OrdinalIgnoreCase))
{
return "video/mp4";
}
if (ext.EndsWith("ogv", StringComparison.OrdinalIgnoreCase))
{
return "video/ogg";
}
if (ext.EndsWith("mov", StringComparison.OrdinalIgnoreCase))
{
return "video/quicktime";
}
if (ext.EndsWith("webm", StringComparison.OrdinalIgnoreCase))
{
return "video/webm";
}
if (ext.EndsWith("mkv", StringComparison.OrdinalIgnoreCase))
{
return "video/x-matroska";
}
if (ext.EndsWith("wmv", StringComparison.OrdinalIgnoreCase))
{
return "video/x-ms-wmv";
}
if (ext.EndsWith("flv", StringComparison.OrdinalIgnoreCase))
{
return "video/x-flv";
}
if (ext.EndsWith("avi", StringComparison.OrdinalIgnoreCase))
{
return "video/avi";
}
if (ext.EndsWith("m4v", StringComparison.OrdinalIgnoreCase))
{
return "video/x-m4v";
}
if (ext.EndsWith("asf", StringComparison.OrdinalIgnoreCase))
{
return "video/x-ms-asf";
}
if (ext.EndsWith("3gp", StringComparison.OrdinalIgnoreCase))
{
return "video/3gpp";
}
if (ext.EndsWith("3g2", StringComparison.OrdinalIgnoreCase))
{
return "video/3gpp2";
}
if (ext.EndsWith("ts", StringComparison.OrdinalIgnoreCase))
{
return "video/mp2t";
}
// Type text
if (ext.EndsWith("css", StringComparison.OrdinalIgnoreCase))
{
return "text/css";
}
if (ext.EndsWith("csv", StringComparison.OrdinalIgnoreCase))
{
return "text/csv";
}
if (ext.EndsWith("html", StringComparison.OrdinalIgnoreCase) || ext.EndsWith("html", StringComparison.OrdinalIgnoreCase))
{
return "text/html";
}
if (ext.EndsWith("txt", StringComparison.OrdinalIgnoreCase))
{
return "text/plain";
}
// Type image
if (ext.EndsWith("gif", StringComparison.OrdinalIgnoreCase))
{
return "image/gif";
}
if (ext.EndsWith("jpg", StringComparison.OrdinalIgnoreCase) || ext.EndsWith("jpeg", StringComparison.OrdinalIgnoreCase))
{
return "image/jpeg";
}
if (ext.EndsWith("png", StringComparison.OrdinalIgnoreCase))
{
return "image/png";
}
if (ext.EndsWith("ico", StringComparison.OrdinalIgnoreCase))
{
return "image/vnd.microsoft.icon";
}
// Type audio
if (ext.EndsWith("mp3", StringComparison.OrdinalIgnoreCase))
{
return "audio/mpeg";
}
if (ext.EndsWith("m4a", StringComparison.OrdinalIgnoreCase) || ext.EndsWith("aac", StringComparison.OrdinalIgnoreCase))
{
return "audio/mp4";
}
if (ext.EndsWith("webma", StringComparison.OrdinalIgnoreCase))
{
return "audio/webm";
}
if (ext.EndsWith("wav", StringComparison.OrdinalIgnoreCase))
{
return "audio/wav";
}
if (ext.EndsWith("wma", StringComparison.OrdinalIgnoreCase))
{
return "audio/x-ms-wma";
}
if (ext.EndsWith("flac", StringComparison.OrdinalIgnoreCase))
{
return "audio/flac";
}
if (ext.EndsWith("aac", StringComparison.OrdinalIgnoreCase))
{
return "audio/x-aac";
}
if (ext.EndsWith("ogg", StringComparison.OrdinalIgnoreCase) || ext.EndsWith("oga", StringComparison.OrdinalIgnoreCase))
{
return "audio/ogg";
}
// Playlists
if (ext.EndsWith("m3u8", StringComparison.OrdinalIgnoreCase))
{
return "application/x-mpegURL";
}
// Misc
if (ext.EndsWith("dll", StringComparison.OrdinalIgnoreCase))
{
return "application/x-msdownload";
}
throw new InvalidOperationException("Argument not supported: " + path);
}
}
}

View File

@ -0,0 +1,18 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace MediaBrowser.Common.Net
{
public class Request
{
public string HttpMethod { get; set; }
public IDictionary<string, IEnumerable<string>> Headers { get; set; }
public Stream InputStream { get; set; }
public string RawUrl { get; set; }
public int ContentLength
{
get { return int.Parse(Headers["Content-Length"].First()); }
}
}
}

View File

@ -0,0 +1,247 @@
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Logging;
using MediaBrowser.Common.Serialization;
using MediaBrowser.Model.Plugins;
using System;
using System.IO;
using System.Reflection;
namespace MediaBrowser.Common.Plugins
{
/// <summary>
/// Provides a common base class for all plugins
/// </summary>
public abstract class BasePlugin : IDisposable
{
protected IKernel Kernel { get; private set; }
/// <summary>
/// Gets or sets the plugin's current context
/// </summary>
protected KernelContext Context { get { return Kernel.KernelContext; } }
/// <summary>
/// Gets the name of the plugin
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets the type of configuration this plugin uses
/// </summary>
public virtual Type ConfigurationType
{
get { return typeof (BasePluginConfiguration); }
}
/// <summary>
/// Gets the plugin version
/// </summary>
public Version Version
{
get
{
return GetType().Assembly.GetName().Version;
}
}
/// <summary>
/// Gets the name the assembly file
/// </summary>
public string AssemblyFileName
{
get
{
return GetType().Assembly.GetName().Name + ".dll";
}
}
private DateTime? _configurationDateLastModified;
public DateTime ConfigurationDateLastModified
{
get
{
if (_configurationDateLastModified == null)
{
if (File.Exists(ConfigurationFilePath))
{
_configurationDateLastModified = File.GetLastWriteTimeUtc(ConfigurationFilePath);
}
}
return _configurationDateLastModified ?? DateTime.MinValue;
}
}
/// <summary>
/// Gets the path to the assembly file
/// </summary>
public string AssemblyFilePath
{
get
{
return Path.Combine(Kernel.ApplicationPaths.PluginsPath, AssemblyFileName);
}
}
/// <summary>
/// Gets or sets the current plugin configuration
/// </summary>
public BasePluginConfiguration Configuration { get; protected set; }
/// <summary>
/// Gets the name of the configuration file. Subclasses should override
/// </summary>
public virtual string ConfigurationFileName
{
get
{
return Name.Replace(" ", string.Empty) + ".xml";
}
}
/// <summary>
/// Gets the full path to the configuration file
/// </summary>
public string ConfigurationFilePath
{
get
{
return Path.Combine(Kernel.ApplicationPaths.PluginConfigurationsPath, ConfigurationFileName);
}
}
private string _dataFolderPath;
/// <summary>
/// Gets the full path to the data folder, where the plugin can store any miscellaneous files needed
/// </summary>
public string DataFolderPath
{
get
{
if (_dataFolderPath == null)
{
// Give the folder name the same name as the config file name
// We can always make this configurable if/when needed
_dataFolderPath = Path.Combine(Kernel.ApplicationPaths.PluginsPath, Path.GetFileNameWithoutExtension(ConfigurationFileName));
if (!Directory.Exists(_dataFolderPath))
{
Directory.CreateDirectory(_dataFolderPath);
}
}
return _dataFolderPath;
}
}
public bool Enabled
{
get
{
return Configuration.Enabled;
}
}
/// <summary>
/// Returns true or false indicating if the plugin should be downloaded and run within the Ui.
/// </summary>
public virtual bool DownloadToUi
{
get
{
return false;
}
}
public void Initialize(IKernel kernel)
{
Initialize(kernel, true);
}
/// <summary>
/// Starts the plugin.
/// </summary>
public void Initialize(IKernel kernel, bool loadFeatures)
{
Kernel = kernel;
if (loadFeatures)
{
ReloadConfiguration();
if (Enabled)
{
if (kernel.KernelContext == KernelContext.Server)
{
InitializeOnServer();
}
else if (kernel.KernelContext == KernelContext.Ui)
{
InitializeInUi();
}
}
}
}
/// <summary>
/// Starts the plugin on the server
/// </summary>
protected virtual void InitializeOnServer()
{
}
/// <summary>
/// Starts the plugin in the Ui
/// </summary>
protected virtual void InitializeInUi()
{
}
/// <summary>
/// Disposes the plugins. Undos all actions performed during Init.
/// </summary>
public void Dispose()
{
Logger.LogInfo("Disposing {0} Plugin", Name);
if (Context == KernelContext.Server)
{
DisposeOnServer();
}
else if (Context == KernelContext.Ui)
{
InitializeInUi();
}
}
/// <summary>
/// Disposes the plugin on the server
/// </summary>
protected virtual void DisposeOnServer()
{
}
/// <summary>
/// Disposes the plugin in the Ui
/// </summary>
protected virtual void DisposeInUi()
{
}
public void ReloadConfiguration()
{
if (!File.Exists(ConfigurationFilePath))
{
Configuration = Activator.CreateInstance(ConfigurationType) as BasePluginConfiguration;
XmlSerializer.SerializeToFile(Configuration, ConfigurationFilePath);
}
else
{
Configuration = XmlSerializer.DeserializeFromFile(ConfigurationType, ConfigurationFilePath) as BasePluginConfiguration;
}
// Reset this so it will be loaded again next time it's accessed
_configurationDateLastModified = null;
}
}
}

View File

@ -0,0 +1,78 @@
using MediaBrowser.Common.Mef;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Windows;
using System.Windows.Controls;
namespace MediaBrowser.Common.Plugins
{
public abstract class BaseTheme : BasePlugin
{
public sealed override bool DownloadToUi
{
get
{
return true;
}
}
/// <summary>
/// Gets the MEF CompositionContainer
/// </summary>
private CompositionContainer CompositionContainer { get; set; }
/// <summary>
/// Gets the list of global resources
/// </summary>
[ImportMany(typeof(ResourceDictionary))]
public IEnumerable<ResourceDictionary> GlobalResources { get; private set; }
/// <summary>
/// Gets the list of pages
/// </summary>
[ImportMany(typeof(Page))]
public IEnumerable<Page> Pages { get; private set; }
/// <summary>
/// Gets the pack Uri of the Login page
/// </summary>
public abstract Uri LoginPageUri { get; }
protected override void InitializeInUi()
{
base.InitializeInUi();
ComposeParts();
}
private void ComposeParts()
{
var catalog = new AssemblyCatalog(GetType().Assembly);
CompositionContainer = MefUtils.GetSafeCompositionContainer(new ComposablePartCatalog[] { catalog });
CompositionContainer.ComposeParts(this);
CompositionContainer.Catalog.Dispose();
}
protected override void DisposeInUi()
{
base.DisposeInUi();
CompositionContainer.Dispose();
}
protected Uri GeneratePackUri(string relativePath)
{
string assemblyName = GetType().Assembly.GetName().Name;
string uri = string.Format("pack://application:,,,/{0};component/{1}", assemblyName, relativePath);
return new Uri(uri, UriKind.Absolute);
}
}
}

View File

@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MediaBrowser.Common")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MediaBrowser.Common")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("cdec1bb7-6ffd-409f-b41f-0524a73df9be")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.17929
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MediaBrowser.Common.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MediaBrowser.Common.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View File

@ -0,0 +1,74 @@
using System;
using System.IO;
namespace MediaBrowser.Common.Serialization
{
/// <summary>
/// Provides a wrapper around third party json serialization.
/// </summary>
public class JsonSerializer
{
public static void SerializeToStream<T>(T obj, Stream stream)
{
Configure();
ServiceStack.Text.JsonSerializer.SerializeToStream(obj, stream);
}
public static void SerializeToFile<T>(T obj, string file)
{
Configure();
using (Stream stream = File.Open(file, FileMode.Create))
{
ServiceStack.Text.JsonSerializer.SerializeToStream(obj, stream);
}
}
public static object DeserializeFromFile(Type type, string file)
{
Configure();
using (Stream stream = File.OpenRead(file))
{
return ServiceStack.Text.JsonSerializer.DeserializeFromStream(type, stream);
}
}
public static T DeserializeFromFile<T>(string file)
{
Configure();
using (Stream stream = File.OpenRead(file))
{
return ServiceStack.Text.JsonSerializer.DeserializeFromStream<T>(stream);
}
}
public static T DeserializeFromStream<T>(Stream stream)
{
Configure();
return ServiceStack.Text.JsonSerializer.DeserializeFromStream<T>(stream);
}
public static object DeserializeFromStream(Stream stream, Type type)
{
Configure();
return ServiceStack.Text.JsonSerializer.DeserializeFromStream(type, stream);
}
private static bool _isConfigured;
private static void Configure()
{
if (!_isConfigured)
{
ServiceStack.Text.JsConfig.DateHandler = ServiceStack.Text.JsonDateHandler.ISO8601;
ServiceStack.Text.JsConfig.ExcludeTypeInfo = true;
ServiceStack.Text.JsConfig.IncludeNullValues = false;
_isConfigured = true;
}
}
}
}

View File

@ -0,0 +1,44 @@
using System;
using System.IO;
namespace MediaBrowser.Common.Serialization
{
/// <summary>
/// This adds support for ServiceStack's proprietary JSV output format.
/// It's a hybrid of Json and Csv but the serializer performs about 25% faster and output runs about 10% smaller
/// http://www.servicestack.net/benchmarks/NorthwindDatabaseRowsSerialization.100000-times.2010-08-17.html
/// </summary>
public static class JsvSerializer
{
public static void SerializeToStream<T>(T obj, Stream stream)
{
ServiceStack.Text.TypeSerializer.SerializeToStream(obj, stream);
}
public static T DeserializeFromStream<T>(Stream stream)
{
return ServiceStack.Text.TypeSerializer.DeserializeFromStream<T>(stream);
}
public static object DeserializeFromStream(Stream stream, Type type)
{
return ServiceStack.Text.TypeSerializer.DeserializeFromStream(type, stream);
}
public static void SerializeToFile<T>(T obj, string file)
{
using (Stream stream = File.Open(file, FileMode.Create))
{
SerializeToStream(obj, stream);
}
}
public static T DeserializeFromFile<T>(string file)
{
using (Stream stream = File.OpenRead(file))
{
return DeserializeFromStream<T>(stream);
}
}
}
}

View File

@ -0,0 +1,53 @@
using System;
using System.IO;
namespace MediaBrowser.Common.Serialization
{
/// <summary>
/// Protocol buffers is google's binary serialization format. This is a .NET implementation of it.
/// You have to tag your classes with some annoying attributes, but in return you get the fastest serialization around with the smallest possible output.
/// </summary>
public static class ProtobufSerializer
{
/// <summary>
/// This is an auto-generated Protobuf Serialization assembly for best performance.
/// It is created during the Model project's post-build event.
/// This means that this class can currently only handle types within the Model project.
/// If we need to, we can always add a param indicating whether or not the model serializer should be used.
/// </summary>
private static readonly ProtobufModelSerializer ProtobufModelSerializer = new ProtobufModelSerializer();
public static void SerializeToStream<T>(T obj, Stream stream)
{
ProtobufModelSerializer.Serialize(stream, obj);
}
public static T DeserializeFromStream<T>(Stream stream)
where T : class
{
return ProtobufModelSerializer.Deserialize(stream, null, typeof(T)) as T;
}
public static object DeserializeFromStream(Stream stream, Type type)
{
return ProtobufModelSerializer.Deserialize(stream, null, type);
}
public static void SerializeToFile<T>(T obj, string file)
{
using (Stream stream = File.Open(file, FileMode.Create))
{
SerializeToStream(obj, stream);
}
}
public static T DeserializeFromFile<T>(string file)
where T : class
{
using (Stream stream = File.OpenRead(file))
{
return DeserializeFromStream<T>(stream);
}
}
}
}

View File

@ -0,0 +1,58 @@
using System;
using System.IO;
namespace MediaBrowser.Common.Serialization
{
/// <summary>
/// Provides a wrapper around third party xml serialization.
/// </summary>
public class XmlSerializer
{
public static void SerializeToStream<T>(T obj, Stream stream)
{
ServiceStack.Text.XmlSerializer.SerializeToStream(obj, stream);
}
public static T DeserializeFromStream<T>(Stream stream)
{
return ServiceStack.Text.XmlSerializer.DeserializeFromStream<T>(stream);
}
public static object DeserializeFromStream(Type type, Stream stream)
{
return ServiceStack.Text.XmlSerializer.DeserializeFromStream(type, stream);
}
public static void SerializeToFile<T>(T obj, string file)
{
using (var stream = new FileStream(file, FileMode.Create))
{
SerializeToStream(obj, stream);
}
}
public static T DeserializeFromFile<T>(string file)
{
using (Stream stream = File.OpenRead(file))
{
return DeserializeFromStream<T>(stream);
}
}
public static void SerializeToFile(object obj, string file)
{
using (var stream = new FileStream(file, FileMode.Create))
{
ServiceStack.Text.XmlSerializer.SerializeToStream(obj, stream);
}
}
public static object DeserializeFromFile(Type type, string file)
{
using (Stream stream = File.OpenRead(file))
{
return DeserializeFromStream(type, stream);
}
}
}
}

View File

@ -0,0 +1,123 @@
using MediaBrowser.Common.Kernel;
using MediaBrowser.Common.Logging;
using MediaBrowser.Model.Progress;
using Microsoft.Shell;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace MediaBrowser.Common.UI
{
/// <summary>
/// Serves as a base Application class for both the UI and Server apps.
/// </summary>
public abstract class BaseApplication : Application, INotifyPropertyChanged, ISingleInstanceApp
{
private IKernel Kernel { get; set; }
protected abstract IKernel InstantiateKernel();
protected abstract Window InstantiateMainWindow();
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
protected override void OnStartup(StartupEventArgs e)
{
// Without this the app will shutdown after the splash screen closes
ShutdownMode = ShutdownMode.OnExplicitShutdown;
LoadKernel();
}
private async void LoadKernel()
{
Kernel = InstantiateKernel();
var progress = new Progress<TaskProgress>();
var splash = new Splash(progress);
splash.Show();
try
{
DateTime now = DateTime.UtcNow;
await Kernel.Init(progress);
Logger.LogInfo("Kernel.Init completed in {0} seconds.", (DateTime.UtcNow - now).TotalSeconds);
splash.Close();
ShutdownMode = System.Windows.ShutdownMode.OnLastWindowClose;
OnKernelLoaded();
InstantiateMainWindow().Show();
}
catch (Exception ex)
{
Logger.LogException(ex);
MessageBox.Show("There was an error launching Media Browser: " + ex.Message);
splash.Close();
// Shutdown the app with an error code
Shutdown(1);
}
}
protected virtual void OnKernelLoaded()
{
}
protected override void OnExit(ExitEventArgs e)
{
base.OnExit(e);
Kernel.Dispose();
}
public bool SignalExternalCommandLineArgs(IList<string> args)
{
OnSecondInstanceLaunched(args);
return true;
}
protected virtual void OnSecondInstanceLaunched(IList<string> args)
{
if (this.MainWindow.WindowState == WindowState.Minimized)
{
this.MainWindow.WindowState = WindowState.Maximized;
}
}
public static void RunApplication<TApplicationType>(string uniqueKey)
where TApplicationType : BaseApplication, IApplication, new()
{
if (SingleInstance<TApplicationType>.InitializeAsFirstInstance(uniqueKey))
{
var application = new TApplicationType();
application.InitializeComponent();
application.Run();
// Allow single instance code to perform cleanup operations
SingleInstance<TApplicationType>.Cleanup();
}
}
}
public interface IApplication
{
void InitializeComponent();
}
}

View File

@ -0,0 +1,484 @@
//-----------------------------------------------------------------------
// <copyright file="SingleInstance.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <summary>
// This class checks to make sure that only one instance of
// this application is running at a time.
// </summary>
//-----------------------------------------------------------------------
namespace Microsoft.Shell
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;
using System.Security;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
internal enum WM
{
NULL = 0x0000,
CREATE = 0x0001,
DESTROY = 0x0002,
MOVE = 0x0003,
SIZE = 0x0005,
ACTIVATE = 0x0006,
SETFOCUS = 0x0007,
KILLFOCUS = 0x0008,
ENABLE = 0x000A,
SETREDRAW = 0x000B,
SETTEXT = 0x000C,
GETTEXT = 0x000D,
GETTEXTLENGTH = 0x000E,
PAINT = 0x000F,
CLOSE = 0x0010,
QUERYENDSESSION = 0x0011,
QUIT = 0x0012,
QUERYOPEN = 0x0013,
ERASEBKGND = 0x0014,
SYSCOLORCHANGE = 0x0015,
SHOWWINDOW = 0x0018,
ACTIVATEAPP = 0x001C,
SETCURSOR = 0x0020,
MOUSEACTIVATE = 0x0021,
CHILDACTIVATE = 0x0022,
QUEUESYNC = 0x0023,
GETMINMAXINFO = 0x0024,
WINDOWPOSCHANGING = 0x0046,
WINDOWPOSCHANGED = 0x0047,
CONTEXTMENU = 0x007B,
STYLECHANGING = 0x007C,
STYLECHANGED = 0x007D,
DISPLAYCHANGE = 0x007E,
GETICON = 0x007F,
SETICON = 0x0080,
NCCREATE = 0x0081,
NCDESTROY = 0x0082,
NCCALCSIZE = 0x0083,
NCHITTEST = 0x0084,
NCPAINT = 0x0085,
NCACTIVATE = 0x0086,
GETDLGCODE = 0x0087,
SYNCPAINT = 0x0088,
NCMOUSEMOVE = 0x00A0,
NCLBUTTONDOWN = 0x00A1,
NCLBUTTONUP = 0x00A2,
NCLBUTTONDBLCLK = 0x00A3,
NCRBUTTONDOWN = 0x00A4,
NCRBUTTONUP = 0x00A5,
NCRBUTTONDBLCLK = 0x00A6,
NCMBUTTONDOWN = 0x00A7,
NCMBUTTONUP = 0x00A8,
NCMBUTTONDBLCLK = 0x00A9,
SYSKEYDOWN = 0x0104,
SYSKEYUP = 0x0105,
SYSCHAR = 0x0106,
SYSDEADCHAR = 0x0107,
COMMAND = 0x0111,
SYSCOMMAND = 0x0112,
MOUSEMOVE = 0x0200,
LBUTTONDOWN = 0x0201,
LBUTTONUP = 0x0202,
LBUTTONDBLCLK = 0x0203,
RBUTTONDOWN = 0x0204,
RBUTTONUP = 0x0205,
RBUTTONDBLCLK = 0x0206,
MBUTTONDOWN = 0x0207,
MBUTTONUP = 0x0208,
MBUTTONDBLCLK = 0x0209,
MOUSEWHEEL = 0x020A,
XBUTTONDOWN = 0x020B,
XBUTTONUP = 0x020C,
XBUTTONDBLCLK = 0x020D,
MOUSEHWHEEL = 0x020E,
CAPTURECHANGED = 0x0215,
ENTERSIZEMOVE = 0x0231,
EXITSIZEMOVE = 0x0232,
IME_SETCONTEXT = 0x0281,
IME_NOTIFY = 0x0282,
IME_CONTROL = 0x0283,
IME_COMPOSITIONFULL = 0x0284,
IME_SELECT = 0x0285,
IME_CHAR = 0x0286,
IME_REQUEST = 0x0288,
IME_KEYDOWN = 0x0290,
IME_KEYUP = 0x0291,
NCMOUSELEAVE = 0x02A2,
DWMCOMPOSITIONCHANGED = 0x031E,
DWMNCRENDERINGCHANGED = 0x031F,
DWMCOLORIZATIONCOLORCHANGED = 0x0320,
DWMWINDOWMAXIMIZEDCHANGE = 0x0321,
#region Windows 7
DWMSENDICONICTHUMBNAIL = 0x0323,
DWMSENDICONICLIVEPREVIEWBITMAP = 0x0326,
#endregion
USER = 0x0400,
// This is the hard-coded message value used by WinForms for Shell_NotifyIcon.
// It's relatively safe to reuse.
TRAYMOUSEMESSAGE = 0x800, //WM_USER + 1024
APP = 0x8000,
}
[SuppressUnmanagedCodeSecurity]
internal static class NativeMethods
{
/// <summary>
/// Delegate declaration that matches WndProc signatures.
/// </summary>
public delegate IntPtr MessageHandler(WM uMsg, IntPtr wParam, IntPtr lParam, out bool handled);
[DllImport("shell32.dll", EntryPoint = "CommandLineToArgvW", CharSet = CharSet.Unicode)]
private static extern IntPtr _CommandLineToArgvW([MarshalAs(UnmanagedType.LPWStr)] string cmdLine, out int numArgs);
[DllImport("kernel32.dll", EntryPoint = "LocalFree", SetLastError = true)]
private static extern IntPtr _LocalFree(IntPtr hMem);
public static string[] CommandLineToArgvW(string cmdLine)
{
IntPtr argv = IntPtr.Zero;
try
{
int numArgs = 0;
argv = _CommandLineToArgvW(cmdLine, out numArgs);
if (argv == IntPtr.Zero)
{
throw new Win32Exception();
}
var result = new string[numArgs];
for (int i = 0; i < numArgs; i++)
{
IntPtr currArg = Marshal.ReadIntPtr(argv, i * Marshal.SizeOf(typeof(IntPtr)));
result[i] = Marshal.PtrToStringUni(currArg);
}
return result;
}
finally
{
_LocalFree(argv);
// Otherwise LocalFree failed.
// Assert.AreEqual(IntPtr.Zero, p);
}
}
}
public interface ISingleInstanceApp
{
bool SignalExternalCommandLineArgs(IList<string> args);
}
/// <summary>
/// This class checks to make sure that only one instance of
/// this application is running at a time.
/// </summary>
/// <remarks>
/// Note: this class should be used with some caution, because it does no
/// security checking. For example, if one instance of an app that uses this class
/// is running as Administrator, any other instance, even if it is not
/// running as Administrator, can activate it with command line arguments.
/// For most apps, this will not be much of an issue.
/// </remarks>
public static class SingleInstance<TApplication>
where TApplication : Application, ISingleInstanceApp
{
#region Private Fields
/// <summary>
/// String delimiter used in channel names.
/// </summary>
private const string Delimiter = ":";
/// <summary>
/// Suffix to the channel name.
/// </summary>
private const string ChannelNameSuffix = "SingeInstanceIPCChannel";
/// <summary>
/// Remote service name.
/// </summary>
private const string RemoteServiceName = "SingleInstanceApplicationService";
/// <summary>
/// IPC protocol used (string).
/// </summary>
private const string IpcProtocol = "ipc://";
/// <summary>
/// Application mutex.
/// </summary>
private static Mutex singleInstanceMutex;
/// <summary>
/// IPC channel for communications.
/// </summary>
private static IpcServerChannel channel;
/// <summary>
/// List of command line arguments for the application.
/// </summary>
private static IList<string> commandLineArgs;
#endregion
#region Public Properties
/// <summary>
/// Gets list of command line arguments for the application.
/// </summary>
public static IList<string> CommandLineArgs
{
get { return commandLineArgs; }
}
#endregion
#region Public Methods
/// <summary>
/// Checks if the instance of the application attempting to start is the first instance.
/// If not, activates the first instance.
/// </summary>
/// <returns>True if this is the first instance of the application.</returns>
public static bool InitializeAsFirstInstance(string uniqueName)
{
commandLineArgs = GetCommandLineArgs(uniqueName);
// Build unique application Id and the IPC channel name.
string applicationIdentifier = uniqueName + Environment.UserName;
string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix);
// Create mutex based on unique application Id to check if this is the first instance of the application.
bool firstInstance;
singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance);
if (firstInstance)
{
CreateRemoteService(channelName);
}
else
{
SignalFirstInstance(channelName, commandLineArgs);
}
return firstInstance;
}
/// <summary>
/// Cleans up single-instance code, clearing shared resources, mutexes, etc.
/// </summary>
public static void Cleanup()
{
if (singleInstanceMutex != null)
{
singleInstanceMutex.Close();
singleInstanceMutex = null;
}
if (channel != null)
{
ChannelServices.UnregisterChannel(channel);
channel = null;
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets command line args - for ClickOnce deployed applications, command line args may not be passed directly, they have to be retrieved.
/// </summary>
/// <returns>List of command line arg strings.</returns>
private static IList<string> GetCommandLineArgs(string uniqueApplicationName)
{
string[] args = null;
if (AppDomain.CurrentDomain.ActivationContext == null)
{
// The application was not clickonce deployed, get args from standard API's
args = Environment.GetCommandLineArgs();
}
else
{
// The application was clickonce deployed
// Clickonce deployed apps cannot recieve traditional commandline arguments
// As a workaround commandline arguments can be written to a shared location before
// the app is launched and the app can obtain its commandline arguments from the
// shared location
string appFolderPath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), uniqueApplicationName);
string cmdLinePath = Path.Combine(appFolderPath, "cmdline.txt");
if (File.Exists(cmdLinePath))
{
try
{
using (TextReader reader = new StreamReader(cmdLinePath, System.Text.Encoding.Unicode))
{
args = NativeMethods.CommandLineToArgvW(reader.ReadToEnd());
}
File.Delete(cmdLinePath);
}
catch (IOException)
{
}
}
}
if (args == null)
{
args = new string[] { };
}
return new List<string>(args);
}
/// <summary>
/// Creates a remote service for communication.
/// </summary>
/// <param name="channelName">Application's IPC channel name.</param>
private static void CreateRemoteService(string channelName)
{
var serverProvider = new BinaryServerFormatterSinkProvider { };
serverProvider.TypeFilterLevel = TypeFilterLevel.Full;
IDictionary props = new Dictionary<string, string>();
props["name"] = channelName;
props["portName"] = channelName;
props["exclusiveAddressUse"] = "false";
// Create the IPC Server channel with the channel properties
channel = new IpcServerChannel(props, serverProvider);
// Register the channel with the channel services
ChannelServices.RegisterChannel(channel, true);
// Expose the remote service with the REMOTE_SERVICE_NAME
var remoteService = new IPCRemoteService();
RemotingServices.Marshal(remoteService, RemoteServiceName);
}
/// <summary>
/// Creates a client channel and obtains a reference to the remoting service exposed by the server -
/// in this case, the remoting service exposed by the first instance. Calls a function of the remoting service
/// class to pass on command line arguments from the second instance to the first and cause it to activate itself.
/// </summary>
/// <param name="channelName">Application's IPC channel name.</param>
/// <param name="args">
/// Command line arguments for the second instance, passed to the first instance to take appropriate action.
/// </param>
private static void SignalFirstInstance(string channelName, IList<string> args)
{
var secondInstanceChannel = new IpcClientChannel();
ChannelServices.RegisterChannel(secondInstanceChannel, true);
string remotingServiceUrl = IpcProtocol + channelName + "/" + RemoteServiceName;
// Obtain a reference to the remoting service exposed by the server i.e the first instance of the application
var firstInstanceRemoteServiceReference = (IPCRemoteService)RemotingServices.Connect(typeof(IPCRemoteService), remotingServiceUrl);
// Check that the remote service exists, in some cases the first instance may not yet have created one, in which case
// the second instance should just exit
if (firstInstanceRemoteServiceReference != null)
{
// Invoke a method of the remote service exposed by the first instance passing on the command line
// arguments and causing the first instance to activate itself
firstInstanceRemoteServiceReference.InvokeFirstInstance(args);
}
}
/// <summary>
/// Callback for activating first instance of the application.
/// </summary>
/// <param name="arg">Callback argument.</param>
/// <returns>Always null.</returns>
private static object ActivateFirstInstanceCallback(object arg)
{
// Get command line args to be passed to first instance
var args = arg as IList<string>;
ActivateFirstInstance(args);
return null;
}
/// <summary>
/// Activates the first instance of the application with arguments from a second instance.
/// </summary>
/// <param name="args">List of arguments to supply the first instance of the application.</param>
private static void ActivateFirstInstance(IList<string> args)
{
// Set main window state and process command line args
if (Application.Current == null)
{
return;
}
((TApplication)Application.Current).SignalExternalCommandLineArgs(args);
}
#endregion
#region Private Classes
/// <summary>
/// Remoting service class which is exposed by the server i.e the first instance and called by the second instance
/// to pass on the command line arguments to the first instance and cause it to activate itself.
/// </summary>
private class IPCRemoteService : MarshalByRefObject
{
/// <summary>
/// Activates the first instance of the application.
/// </summary>
/// <param name="args">List of arguments to pass to the first instance.</param>
public void InvokeFirstInstance(IList<string> args)
{
if (Application.Current != null)
{
// Do an asynchronous call to ActivateFirstInstance function
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new DispatcherOperationCallback(SingleInstance<TApplication>.ActivateFirstInstanceCallback), args);
}
}
/// <summary>
/// Remoting Object's ease expires after every 5 minutes by default. We need to override the InitializeLifetimeService class
/// to ensure that lease never expires.
/// </summary>
/// <returns>Always null.</returns>
public override object InitializeLifetimeService()
{
return null;
}
}
#endregion
}
}

View File

@ -0,0 +1,33 @@
<Controls:MetroWindow x:Class="MediaBrowser.Common.UI.Splash"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="MediaBrowser"
Height="230"
Width="520"
ShowInTaskbar="True"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
WindowState="Normal"
FontSize="14">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type Controls:WindowCommands}">
<Setter Property="Visibility" Value="Hidden" />
</Style>
</ResourceDictionary>
</Window.Resources>
<Window.Background>
<RadialGradientBrush RadiusX=".75" RadiusY=".75">
<GradientStop Color="White" Offset="0.0"/>
<GradientStop Color="WhiteSmoke" Offset="0.65"/>
<GradientStop Color="#cfcfcf" Offset="1.0"/>
</RadialGradientBrush>
</Window.Background>
<Grid Name="splashGrid">
<Image x:Name="imgLogo" HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="Uniform" Grid.Row="0" Margin="10 10 10 10" Source="../Resources/Images/mblogoblack.png"/>
<StackPanel Margin="0,130,10,0" VerticalAlignment="Center" HorizontalAlignment="Center" Grid.Row="2" Orientation="Horizontal">
<TextBlock Name="lblProgress" FontSize="18" Foreground="Black" Text="Label"></TextBlock>
</StackPanel>
</Grid>
</Controls:MetroWindow>

View File

@ -0,0 +1,32 @@
using MahApps.Metro.Controls;
using MediaBrowser.Model.Progress;
using System;
using System.Windows;
namespace MediaBrowser.Common.UI
{
/// <summary>
/// Interaction logic for Splash.xaml
/// </summary>
public partial class Splash : MetroWindow
{
public Splash(Progress<TaskProgress> progress)
{
InitializeComponent();
progress.ProgressChanged += ProgressChanged;
Loaded+=SplashLoaded;
}
void ProgressChanged(object sender, TaskProgress e)
{
lblProgress.Text = e.Description + "...";
}
private void SplashLoaded(object sender, RoutedEventArgs e)
{
// Setting this in markup throws an exception at runtime
ShowTitleBar = false;
}
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Reactive.Core" publicKeyToken="f300afd708cefcd3" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.20823.0" newVersion="2.0.20823.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reactive.Interfaces" publicKeyToken="f300afd708cefcd3" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.20823.0" newVersion="2.0.20823.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MahApps.Metro" version="0.9.0.0" targetFramework="net45" />
<package id="Rx-Core" version="2.0.20823" targetFramework="net45" />
<package id="Rx-Interfaces" version="2.0.20823" targetFramework="net45" />
<package id="Rx-Linq" version="2.0.20823" targetFramework="net45" />
<package id="ServiceStack.Text" version="3.9.9" targetFramework="net45" />
</packages>

View File

@ -0,0 +1,81 @@
using System;
using System.Drawing;
namespace MediaBrowser.Controller.Drawing
{
public static class DrawingUtils
{
/// <summary>
/// Resizes a set of dimensions
/// </summary>
public static Size Resize(int currentWidth, int currentHeight, int? width, int? height, int? maxWidth, int? maxHeight)
{
return Resize(new Size(currentWidth, currentHeight), width, height, maxWidth, maxHeight);
}
/// <summary>
/// Resizes a set of dimensions
/// </summary>
/// <param name="size">The original size object</param>
/// <param name="width">A new fixed width, if desired</param>
/// <param name="height">A new fixed neight, if desired</param>
/// <param name="maxWidth">A max fixed width, if desired</param>
/// <param name="maxHeight">A max fixed height, if desired</param>
/// <returns>A new size object</returns>
public static Size Resize(Size size, int? width, int? height, int? maxWidth, int? maxHeight)
{
decimal newWidth = size.Width;
decimal newHeight = size.Height;
if (width.HasValue && height.HasValue)
{
newWidth = width.Value;
newHeight = height.Value;
}
else if (height.HasValue)
{
newWidth = GetNewWidth(newHeight, newWidth, height.Value);
newHeight = height.Value;
}
else if (width.HasValue)
{
newHeight = GetNewHeight(newHeight, newWidth, width.Value);
newWidth = width.Value;
}
if (maxHeight.HasValue && maxHeight < newHeight)
{
newWidth = GetNewWidth(newHeight, newWidth, maxHeight.Value);
newHeight = maxHeight.Value;
}
if (maxWidth.HasValue && maxWidth < newWidth)
{
newHeight = GetNewHeight(newHeight, newWidth, maxWidth.Value);
newWidth = maxWidth.Value;
}
return new Size(Convert.ToInt32(newWidth), Convert.ToInt32(newHeight));
}
private static decimal GetNewWidth(decimal currentHeight, decimal currentWidth, int newHeight)
{
decimal scaleFactor = newHeight;
scaleFactor /= currentHeight;
scaleFactor *= currentWidth;
return scaleFactor;
}
private static decimal GetNewHeight(decimal currentHeight, decimal currentWidth, int newWidth)
{
decimal scaleFactor = newWidth;
scaleFactor /= currentWidth;
scaleFactor *= currentHeight;
return scaleFactor;
}
}
}

View File

@ -0,0 +1,148 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Entities;
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
namespace MediaBrowser.Controller.Drawing
{
public static class ImageProcessor
{
/// <summary>
/// Processes an image by resizing to target dimensions
/// </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="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="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>
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.FromFile(GetImagePath(entity, imageType, imageIndex));
// Determine the output size based on incoming parameters
Size newSize = DrawingUtils.Resize(originalImage.Size, width, height, maxWidth, maxHeight);
Bitmap thumbnail;
// Graphics.FromImage will throw an exception if the PixelFormat is Indexed, so we need to handle that here
if (originalImage.PixelFormat.HasFlag(PixelFormat.Indexed))
{
thumbnail = new Bitmap(originalImage, newSize.Width, newSize.Height);
}
else
{
thumbnail = new Bitmap(newSize.Width, newSize.Height, originalImage.PixelFormat);
}
thumbnail.MakeTransparent();
// Preserve the original resolution
thumbnail.SetResolution(originalImage.HorizontalResolution, originalImage.VerticalResolution);
Graphics thumbnailGraph = Graphics.FromImage(thumbnail);
thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
thumbnailGraph.PixelOffsetMode = PixelOffsetMode.HighQuality;
thumbnailGraph.CompositingMode = CompositingMode.SourceOver;
thumbnailGraph.DrawImage(originalImage, 0, 0, newSize.Width, newSize.Height);
ImageFormat outputFormat = originalImage.RawFormat;
// Write to the output stream
SaveImage(outputFormat, thumbnail, toStream, quality);
thumbnailGraph.Dispose();
thumbnail.Dispose();
originalImage.Dispose();
}
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
// All other formats use the generic Image.Save
if (ImageFormat.Jpeg.Equals(outputFormat))
{
SaveJpeg(newImage, toStream, quality);
}
else if (ImageFormat.Png.Equals(outputFormat))
{
newImage.Save(toStream, ImageFormat.Png);
}
else
{
newImage.Save(toStream, outputFormat);
}
}
public static void SaveJpeg(Image image, Stream target, int? quality)
{
if (!quality.HasValue)
{
quality = 90;
}
using (var encoderParameters = new EncoderParameters(1))
{
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value);
image.Save(target, GetImageCodecInfo("image/jpeg"), encoderParameters);
}
}
public static ImageCodecInfo GetImageCodecInfo(string mimeType)
{
ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders();
for (int i = 0; i < info.Length; i++)
{
ImageCodecInfo ici = info[i];
if (ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase))
{
return ici;
}
}
return info[1];
}
}
}

View File

@ -0,0 +1,14 @@

namespace MediaBrowser.Controller.Entities
{
public class Audio : BaseItem
{
public int BitRate { get; set; }
public int Channels { get; set; }
public int SampleRate { get; set; }
public string Artist { get; set; }
public string Album { get; set; }
public string AlbumArtist { get; set; }
}
}

View File

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Providers;
namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// Provides a base entity for all of our types
/// </summary>
public abstract class BaseEntity
{
public string Name { get; set; }
public Guid Id { get; set; }
public string Path { get; set; }
public Folder Parent { get; set; }
public string PrimaryImagePath { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
public override string ToString()
{
return Name;
}
protected Dictionary<Guid, BaseProviderInfo> _providerData;
/// <summary>
/// Holds persistent data for providers like last refresh date.
/// Providers can use this to determine if they need to refresh.
/// The BaseProviderInfo class can be extended to hold anything a provider may need.
///
/// Keyed by a unique provider ID.
/// </summary>
public Dictionary<Guid, BaseProviderInfo> ProviderData
{
get
{
if (_providerData == null) _providerData = new Dictionary<Guid, BaseProviderInfo>();
return _providerData;
}
set
{
_providerData = value;
}
}
protected ItemResolveEventArgs _resolveArgs;
/// <summary>
/// We attach these to the item so that we only ever have to hit the file system once
/// (this includes the children of the containing folder)
/// Use ResolveArgs.FileSystemChildren to check for the existence of files instead of File.Exists
/// </summary>
public ItemResolveEventArgs ResolveArgs
{
get
{
if (_resolveArgs == null)
{
_resolveArgs = new ItemResolveEventArgs()
{
FileInfo = FileData.GetFileData(this.Path),
Parent = this.Parent,
Cancel = false,
Path = this.Path
};
_resolveArgs = FileSystemHelper.FilterChildFileSystemEntries(_resolveArgs, (this.Parent != null && this.Parent.IsRoot));
}
return _resolveArgs;
}
set
{
_resolveArgs = value;
}
}
/// <summary>
/// Refresh metadata on us by execution our provider chain
/// </summary>
/// <returns>true if a provider reports we changed</returns>
public bool RefreshMetadata()
{
Kernel.Instance.ExecuteMetadataProviders(this).ConfigureAwait(false);
return true;
}
}
}

View File

@ -0,0 +1,202 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.IO;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities
{
public abstract class BaseItem : BaseEntity, IHasProviderIds
{
public IEnumerable<string> PhysicalLocations
{
get
{
return _resolveArgs.PhysicalLocations;
}
}
public string SortName { get; set; }
/// <summary>
/// When the item first debuted. For movies this could be premiere date, episodes would be first aired
/// </summary>
public DateTime? PremiereDate { get; set; }
public string LogoImagePath { get; set; }
public string ArtImagePath { get; set; }
public string ThumbnailImagePath { get; set; }
public string BannerImagePath { get; set; }
public IEnumerable<string> BackdropImagePaths { get; set; }
public string OfficialRating { get; set; }
public string CustomRating { get; set; }
public string CustomPin { get; set; }
public string Language { get; set; }
public string Overview { get; set; }
public List<string> Taglines { get; set; }
/// <summary>
/// Using a Dictionary to prevent duplicates
/// </summary>
public Dictionary<string,PersonInfo> People { get; set; }
public List<string> Studios { get; set; }
public List<string> Genres { get; set; }
public string DisplayMediaType { get; set; }
public float? CommunityRating { get; set; }
public long? RunTimeTicks { get; set; }
public string AspectRatio { get; set; }
public int? ProductionYear { get; set; }
/// <summary>
/// If the item is part of a series, this is it's number in the series.
/// This could be episode number, album track number, etc.
/// </summary>
public int? IndexNumber { get; set; }
/// <summary>
/// For an episode this could be the season number, or for a song this could be the disc number.
/// </summary>
public int? ParentIndexNumber { get; set; }
public IEnumerable<Video> LocalTrailers { get; set; }
public string TrailerUrl { get; set; }
public Dictionary<string, string> ProviderIds { get; set; }
public Dictionary<Guid, UserItemData> UserData { get; set; }
public UserItemData GetUserData(User user, bool createIfNull)
{
if (UserData == null || !UserData.ContainsKey(user.Id))
{
if (createIfNull)
{
AddUserData(user, new UserItemData());
}
else
{
return null;
}
}
return UserData[user.Id];
}
private void AddUserData(User user, UserItemData data)
{
if (UserData == null)
{
UserData = new Dictionary<Guid, UserItemData>();
}
UserData[user.Id] = data;
}
/// <summary>
/// Determines if a given user has access to this item
/// </summary>
internal bool IsParentalAllowed(User user)
{
return true;
}
/// <summary>
/// Finds an item by ID, recursively
/// </summary>
public virtual BaseItem FindItemById(Guid id)
{
if (Id == id)
{
return this;
}
if (LocalTrailers != null)
{
return LocalTrailers.FirstOrDefault(i => i.Id == id);
}
return null;
}
public virtual bool IsFolder
{
get
{
return false;
}
}
/// <summary>
/// Determine if we have changed vs the passed in copy
/// </summary>
/// <param name="copy"></param>
/// <returns></returns>
public virtual bool IsChanged(BaseItem copy)
{
bool changed = copy.DateModified != this.DateModified;
if (changed) MediaBrowser.Common.Logging.Logger.LogDebugInfo(this.Name + " changed - original creation: " + this.DateCreated + " new creation: " + copy.DateCreated + " original modified: " + this.DateModified + " new modified: " + copy.DateModified);
return changed;
}
/// <summary>
/// Determines if the item is considered new based on user settings
/// </summary>
public bool IsRecentlyAdded(User user)
{
return (DateTime.UtcNow - DateCreated).TotalDays < user.RecentItemDays;
}
public void AddPerson(PersonInfo person)
{
if (People == null)
{
People = new Dictionary<string, PersonInfo>(StringComparer.OrdinalIgnoreCase);
}
People[person.Name] = person;
}
/// <summary>
/// Marks the item as either played or unplayed
/// </summary>
public virtual void SetPlayedStatus(User user, bool wasPlayed)
{
UserItemData data = GetUserData(user, true);
if (wasPlayed)
{
data.PlayCount = Math.Max(data.PlayCount, 1);
}
else
{
data.PlayCount = 0;
data.PlaybackPositionTicks = 0;
}
}
/// <summary>
/// Do whatever refreshing is necessary when the filesystem pertaining to this item has changed.
/// </summary>
/// <returns></returns>
public virtual Task ChangedExternally()
{
return Task.Run(() => RefreshMetadata());
}
}
}

View File

@ -0,0 +1,619 @@
using MediaBrowser.Model.Entities;
using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Common.Logging;
using MediaBrowser.Controller.Resolvers;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities
{
public class Folder : BaseItem
{
#region Events
/// <summary>
/// Fires whenever a validation routine updates our children. The added and removed children are properties of the args.
/// *** Will fire asynchronously. ***
/// </summary>
public event EventHandler<ChildrenChangedEventArgs> ChildrenChanged;
protected void OnChildrenChanged(ChildrenChangedEventArgs args)
{
if (ChildrenChanged != null)
{
Task.Run( () =>
{
ChildrenChanged(this, args);
Kernel.Instance.OnLibraryChanged(args);
});
}
}
#endregion
public override bool IsFolder
{
get
{
return true;
}
}
public bool IsRoot { get; set; }
public bool IsVirtualFolder
{
get
{
return Parent != null && Parent.IsRoot;
}
}
protected object childLock = new object();
protected List<BaseItem> children;
protected virtual List<BaseItem> ActualChildren
{
get
{
if (children == null)
{
LoadChildren();
}
return children;
}
set
{
children = value;
}
}
/// <summary>
/// thread-safe access to the actual children of this folder - without regard to user
/// </summary>
public IEnumerable<BaseItem> Children
{
get
{
lock (childLock)
return ActualChildren.ToList();
}
}
/// <summary>
/// thread-safe access to all recursive children of this folder - without regard to user
/// </summary>
public IEnumerable<BaseItem> RecursiveChildren
{
get
{
foreach (var item in Children)
{
yield return item;
var subFolder = item as Folder;
if (subFolder != null)
{
foreach (var subitem in subFolder.RecursiveChildren)
{
yield return subitem;
}
}
}
}
}
/// <summary>
/// Loads and validates our children
/// </summary>
protected virtual void LoadChildren()
{
//first - load our children from the repo
lock (childLock)
children = GetCachedChildren();
//then kick off a validation against the actual file system
Task.Run(() => ValidateChildren());
}
protected bool ChildrenValidating = false;
/// <summary>
/// Compare our current children (presumably just read from the repo) with the current state of the file system and adjust for any changes
/// ***Currently does not contain logic to maintain items that are unavailable in the file system***
/// </summary>
/// <returns></returns>
protected async virtual void ValidateChildren()
{
if (ChildrenValidating) return; //only ever want one of these going at once and don't want them to fire off in sequence so don't use lock
ChildrenValidating = true;
bool changed = false; //this will save us a little time at the end if nothing changes
var changedArgs = new ChildrenChangedEventArgs(this);
//get the current valid children from filesystem (or wherever)
var nonCachedChildren = await GetNonCachedChildren();
if (nonCachedChildren == null) return; //nothing to validate
//build a dictionary of the current children we have now by Id so we can compare quickly and easily
Dictionary<Guid, BaseItem> currentChildren;
lock (childLock)
currentChildren = ActualChildren.ToDictionary(i => i.Id);
//create a list for our validated children
var validChildren = new List<BaseItem>();
//now traverse the valid children and find any changed or new items
foreach (var child in nonCachedChildren)
{
BaseItem currentChild;
currentChildren.TryGetValue(child.Id, out currentChild);
if (currentChild == null)
{
//brand new item - needs to be added
changed = true;
changedArgs.ItemsAdded.Add(child);
//refresh it
child.RefreshMetadata();
Logger.LogInfo("New Item Added to Library: ("+child.GetType().Name+") "+ child.Name + " (" + child.Path + ")");
//save it in repo...
//and add it to our valid children
validChildren.Add(child);
//fire an added event...?
//if it is a folder we need to validate its children as well
Folder folder = child as Folder;
if (folder != null)
{
folder.ValidateChildren();
//probably need to refresh too...
}
}
else
{
//existing item - check if it has changed
if (currentChild.IsChanged(child))
{
changed = true;
//update resolve args and refresh meta
// Note - we are refreshing the existing child instead of the newly found one so the "Except" operation below
// will identify this item as the same one
currentChild.ResolveArgs = child.ResolveArgs;
currentChild.RefreshMetadata();
Logger.LogInfo("Item Changed: ("+currentChild.GetType().Name+") "+ currentChild.Name + " (" + currentChild.Path + ")");
//save it in repo...
validChildren.Add(currentChild);
}
else
{
//current child that didn't change - just put it in the valid children
validChildren.Add(currentChild);
}
}
}
//that's all the new and changed ones - now see if there are any that are missing
changedArgs.ItemsRemoved = currentChildren.Values.Except(validChildren);
changed |= changedArgs.ItemsRemoved != null;
//now, if anything changed - replace our children
if (changed)
{
if (changedArgs.ItemsRemoved != null) foreach (var item in changedArgs.ItemsRemoved) Logger.LogDebugInfo("** " + item.Name + " Removed from library.");
lock (childLock)
ActualChildren = validChildren;
//and save children in repo...
//and fire event
this.OnChildrenChanged(changedArgs);
}
ChildrenValidating = false;
}
/// <summary>
/// Get the children of this folder from the actual file system
/// </summary>
/// <returns></returns>
protected async virtual Task<IEnumerable<BaseItem>> GetNonCachedChildren()
{
ItemResolveEventArgs args = new ItemResolveEventArgs()
{
FileInfo = FileData.GetFileData(this.Path),
Parent = this.Parent,
Cancel = false,
Path = this.Path
};
// Gather child folder and files
if (args.IsDirectory)
{
args.FileSystemChildren = FileData.GetFileSystemEntries(this.Path, "*").ToArray();
bool isVirtualFolder = Parent != null && Parent.IsRoot;
args = FileSystemHelper.FilterChildFileSystemEntries(args, isVirtualFolder);
}
else
{
Logger.LogError("Folder has a path that is not a directory: " + this.Path);
return null;
}
if (!EntityResolutionHelper.ShouldResolvePathContents(args))
{
return null;
}
return (await Task.WhenAll<BaseItem>(GetChildren(args.FileSystemChildren)).ConfigureAwait(false))
.Where(i => i != null).OrderBy(f =>
{
return string.IsNullOrEmpty(f.SortName) ? f.Name : f.SortName;
});
}
/// <summary>
/// Resolves a path into a BaseItem
/// </summary>
protected async Task<BaseItem> GetChild(string path, WIN32_FIND_DATA? fileInfo = null)
{
ItemResolveEventArgs args = new ItemResolveEventArgs()
{
FileInfo = fileInfo ?? FileData.GetFileData(path),
Parent = this,
Cancel = false,
Path = path
};
args.FileSystemChildren = FileData.GetFileSystemEntries(path, "*").ToArray();
args = FileSystemHelper.FilterChildFileSystemEntries(args, false);
return Kernel.Instance.ResolveItem(args);
}
/// <summary>
/// Finds child BaseItems for us
/// </summary>
protected Task<BaseItem>[] GetChildren(WIN32_FIND_DATA[] fileSystemChildren)
{
Task<BaseItem>[] tasks = new Task<BaseItem>[fileSystemChildren.Length];
for (int i = 0; i < fileSystemChildren.Length; i++)
{
var child = fileSystemChildren[i];
tasks[i] = GetChild(child.Path, child);
}
return tasks;
}
/// <summary>
/// Get our children from the repo - stubbed for now
/// </summary>
/// <returns></returns>
protected virtual List<BaseItem> GetCachedChildren()
{
return new List<BaseItem>();
}
/// <summary>
/// Gets allowed children of an item
/// </summary>
public IEnumerable<BaseItem> GetChildren(User user)
{
lock(childLock)
return ActualChildren.Where(c => c.IsParentalAllowed(user));
}
/// <summary>
/// Gets allowed recursive children of an item
/// </summary>
public IEnumerable<BaseItem> GetRecursiveChildren(User user)
{
foreach (var item in GetChildren(user))
{
yield return item;
var subFolder = item as Folder;
if (subFolder != null)
{
foreach (var subitem in subFolder.GetRecursiveChildren(user))
{
yield return subitem;
}
}
}
}
/// <summary>
/// Folders need to validate and refresh
/// </summary>
/// <returns></returns>
public override Task ChangedExternally()
{
return Task.Run(() =>
{
if (this.IsRoot)
{
Kernel.Instance.ReloadRoot().ConfigureAwait(false);
}
else
{
RefreshMetadata();
ValidateChildren();
}
});
}
/// <summary>
/// Since it can be slow to make all of these calculations at once, this method will provide a way to get them all back together
/// </summary>
public ItemSpecialCounts GetSpecialCounts(User user)
{
var counts = new ItemSpecialCounts();
IEnumerable<BaseItem> recursiveChildren = GetRecursiveChildren(user);
var recentlyAddedItems = GetRecentlyAddedItems(recursiveChildren, user);
counts.RecentlyAddedItemCount = recentlyAddedItems.Count;
counts.RecentlyAddedUnPlayedItemCount = GetRecentlyAddedUnplayedItems(recentlyAddedItems, user).Count;
counts.InProgressItemCount = GetInProgressItems(recursiveChildren, user).Count;
counts.PlayedPercentage = GetPlayedPercentage(recursiveChildren, user);
return counts;
}
/// <summary>
/// Finds all recursive items within a top-level parent that contain the given genre and are allowed for the current user
/// </summary>
public IEnumerable<BaseItem> GetItemsWithGenre(string genre, User user)
{
return GetRecursiveChildren(user).Where(f => f.Genres != null && f.Genres.Any(s => s.Equals(genre, StringComparison.OrdinalIgnoreCase)));
}
/// <summary>
/// Finds all recursive items within a top-level parent that contain the given year and are allowed for the current user
/// </summary>
public IEnumerable<BaseItem> GetItemsWithYear(int year, User user)
{
return GetRecursiveChildren(user).Where(f => f.ProductionYear.HasValue && f.ProductionYear == year);
}
/// <summary>
/// Finds all recursive items within a top-level parent that contain the given studio and are allowed for the current user
/// </summary>
public IEnumerable<BaseItem> GetItemsWithStudio(string studio, User user)
{
return GetRecursiveChildren(user).Where(f => f.Studios != null && f.Studios.Any(s => s.Equals(studio, StringComparison.OrdinalIgnoreCase)));
}
/// <summary>
/// Finds all recursive items within a top-level parent that the user has marked as a favorite
/// </summary>
public IEnumerable<BaseItem> GetFavoriteItems(User user)
{
return GetRecursiveChildren(user).Where(c =>
{
UserItemData data = c.GetUserData(user, false);
if (data != null)
{
return data.IsFavorite;
}
return false;
});
}
/// <summary>
/// Finds all recursive items within a top-level parent that contain the given person and are allowed for the current user
/// </summary>
public IEnumerable<BaseItem> GetItemsWithPerson(string person, User user)
{
return GetRecursiveChildren(user).Where(c =>
{
if (c.People != null)
{
return c.People.ContainsKey(person);
}
return false;
});
}
/// <summary>
/// Finds all recursive items within a top-level parent that contain the given person and are allowed for the current user
/// </summary>
/// <param name="personType">Specify this to limit results to a specific PersonType</param>
public IEnumerable<BaseItem> GetItemsWithPerson(string person, string personType, User user)
{
return GetRecursiveChildren(user).Where(c =>
{
if (c.People != null)
{
return c.People.ContainsKey(person) && c.People[person].Type.Equals(personType, StringComparison.OrdinalIgnoreCase);
}
return false;
});
}
/// <summary>
/// Gets all recently added items (recursive) within a folder, based on configuration and parental settings
/// </summary>
public List<BaseItem> GetRecentlyAddedItems(User user)
{
return GetRecentlyAddedItems(GetRecursiveChildren(user), user);
}
/// <summary>
/// Gets all recently added unplayed items (recursive) within a folder, based on configuration and parental settings
/// </summary>
public List<BaseItem> GetRecentlyAddedUnplayedItems(User user)
{
return GetRecentlyAddedUnplayedItems(GetRecursiveChildren(user), user);
}
/// <summary>
/// Gets all in-progress items (recursive) within a folder
/// </summary>
public List<BaseItem> GetInProgressItems(User user)
{
return GetInProgressItems(GetRecursiveChildren(user), user);
}
/// <summary>
/// Takes a list of items and returns the ones that are recently added
/// </summary>
private static List<BaseItem> GetRecentlyAddedItems(IEnumerable<BaseItem> itemSet, User user)
{
var list = new List<BaseItem>();
foreach (var item in itemSet)
{
if (!item.IsFolder && item.IsRecentlyAdded(user))
{
list.Add(item);
}
}
return list;
}
/// <summary>
/// Takes a list of items and returns the ones that are recently added and unplayed
/// </summary>
private static List<BaseItem> GetRecentlyAddedUnplayedItems(IEnumerable<BaseItem> itemSet, User user)
{
var list = new List<BaseItem>();
foreach (var item in itemSet)
{
if (!item.IsFolder && item.IsRecentlyAdded(user))
{
var userdata = item.GetUserData(user, false);
if (userdata == null || userdata.PlayCount == 0)
{
list.Add(item);
}
}
}
return list;
}
/// <summary>
/// Takes a list of items and returns the ones that are in progress
/// </summary>
private static List<BaseItem> GetInProgressItems(IEnumerable<BaseItem> itemSet, User user)
{
var list = new List<BaseItem>();
foreach (var item in itemSet)
{
if (!item.IsFolder)
{
var userdata = item.GetUserData(user, false);
if (userdata != null && userdata.PlaybackPositionTicks > 0)
{
list.Add(item);
}
}
}
return list;
}
/// <summary>
/// Gets the total played percentage for a set of items
/// </summary>
private static decimal GetPlayedPercentage(IEnumerable<BaseItem> itemSet, User user)
{
itemSet = itemSet.Where(i => !(i.IsFolder));
decimal totalPercent = 0;
int count = 0;
foreach (BaseItem item in itemSet)
{
count++;
UserItemData data = item.GetUserData(user, false);
if (data == null)
{
continue;
}
if (data.PlayCount > 0)
{
totalPercent += 100;
}
else if (data.PlaybackPositionTicks > 0 && item.RunTimeTicks.HasValue)
{
decimal itemPercent = data.PlaybackPositionTicks;
itemPercent /= item.RunTimeTicks.Value;
totalPercent += itemPercent;
}
}
if (count == 0)
{
return 0;
}
return totalPercent / count;
}
/// <summary>
/// Marks the item as either played or unplayed
/// </summary>
public override void SetPlayedStatus(User user, bool wasPlayed)
{
base.SetPlayedStatus(user, wasPlayed);
// Now sweep through recursively and update status
foreach (BaseItem item in GetChildren(user))
{
item.SetPlayedStatus(user, wasPlayed);
}
}
/// <summary>
/// Finds an item by ID, recursively
/// </summary>
public override BaseItem FindItemById(Guid id)
{
var result = base.FindItemById(id);
if (result != null)
{
return result;
}
//this should be functionally equivilent to what was here since it is IEnum and works on a thread-safe copy
return RecursiveChildren.FirstOrDefault(i => i.Id == id);
}
/// <summary>
/// Finds an item by path, recursively
/// </summary>
public BaseItem FindByPath(string path)
{
if (PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
{
return this;
}
//this should be functionally equivilent to what was here since it is IEnum and works on a thread-safe copy
return RecursiveChildren.FirstOrDefault(i => i.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase));
}
}
}

View File

@ -0,0 +1,7 @@

namespace MediaBrowser.Controller.Entities
{
public class Genre : BaseEntity
{
}
}

View File

@ -0,0 +1,7 @@

namespace MediaBrowser.Controller.Entities.Movies
{
public class BoxSet : Folder
{
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MediaBrowser.Controller.Entities.Movies
{
public class Movie : Video
{
public IEnumerable<Video> SpecialFeatures { get; set; }
/// <summary>
/// Finds an item by ID, recursively
/// </summary>
public override BaseItem FindItemById(Guid id)
{
var item = base.FindItemById(id);
if (item != null)
{
return item;
}
if (SpecialFeatures != null)
{
return SpecialFeatures.FirstOrDefault(i => i.Id == id);
}
return null;
}
}
}

View File

@ -0,0 +1,25 @@

namespace MediaBrowser.Controller.Entities
{
/// <summary>
/// This is the full Person object that can be retrieved with all of it's data.
/// </summary>
public class Person : BaseEntity
{
}
/// <summary>
/// This is the small Person stub that is attached to BaseItems
/// </summary>
public class PersonInfo
{
public string Name { get; set; }
public string Overview { get; set; }
public string Type { get; set; }
public override string ToString()
{
return Name;
}
}
}

View File

@ -0,0 +1,7 @@

namespace MediaBrowser.Controller.Entities
{
public class Studio : BaseEntity
{
}
}

View File

@ -0,0 +1,7 @@

namespace MediaBrowser.Controller.Entities.TV
{
public class Episode : Video
{
}
}

View File

@ -0,0 +1,34 @@
using System;
namespace MediaBrowser.Controller.Entities.TV
{
public class Season : Folder
{
/// <summary>
/// Store these to reduce disk access in Episode Resolver
/// </summary>
public string[] MetadataFiles
{
get
{
return ResolveArgs.MetadataFiles ?? new string[] { };
}
}
/// <summary>
/// Determines if the metafolder contains a given file
/// </summary>
public bool ContainsMetadataFile(string file)
{
for (int i = 0; i < MetadataFiles.Length; i++)
{
if (MetadataFiles[i].Equals(file, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
namespace MediaBrowser.Controller.Entities.TV
{
public class Series : Folder
{
public string Status { get; set; }
public IEnumerable<DayOfWeek> AirDays { get; set; }
public string AirTime { get; set; }
}
}

View File

@ -0,0 +1,21 @@
using System;
namespace MediaBrowser.Controller.Entities
{
public class User : BaseEntity
{
public string Password { get; set; }
public string MaxParentalRating { get; set; }
public int RecentItemDays { get; set; }
public User()
{
RecentItemDays = 14;
}
public DateTime? LastLoginDate { get; set; }
public DateTime? LastActivityDate { get; set; }
}
}

View File

@ -0,0 +1,67 @@
using System;
using System.Runtime.Serialization;
namespace MediaBrowser.Controller.Entities
{
public class UserItemData
{
private float? _rating;
/// <summary>
/// Gets or sets the users 0-10 rating
/// </summary>
public float? Rating
{
get
{
return _rating;
}
set
{
if (value.HasValue)
{
if (value.Value < 0 || value.Value > 10)
{
throw new InvalidOperationException("A 0-10 rating is required for UserItemData.");
}
}
_rating = value;
}
}
public long PlaybackPositionTicks { get; set; }
public int PlayCount { get; set; }
public bool IsFavorite { get; set; }
/// <summary>
/// This is an interpreted property to indicate likes or dislikes
/// This should never be serialized.
/// </summary>
[IgnoreDataMember]
public bool? Likes
{
get
{
if (Rating != null)
{
return Rating >= 6.5;
}
return null;
}
set
{
if (value.HasValue)
{
Rating = value.Value ? 10 : 1;
}
else
{
Rating = null;
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More