commit
845554722e
43
.hgignore
Normal file
43
.hgignore
Normal 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
|
438
MediaBrowser.Api/ApiService.cs
Normal file
438
MediaBrowser.Api/ApiService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
81
MediaBrowser.Api/Drawing/DrawingUtils.cs
Normal file
81
MediaBrowser.Api/Drawing/DrawingUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
148
MediaBrowser.Api/Drawing/ImageProcessor.cs
Normal file
148
MediaBrowser.Api/Drawing/ImageProcessor.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
119
MediaBrowser.Api/HttpHandlers/AudioHandler.cs
Normal file
119
MediaBrowser.Api/HttpHandlers/AudioHandler.cs
Normal 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()) + " -";
|
||||
}
|
||||
}
|
||||
}
|
255
MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs
Normal file
255
MediaBrowser.Api/HttpHandlers/BaseMediaHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
38
MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs
Normal file
38
MediaBrowser.Api/HttpHandlers/FavoriteStatusHandler.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
57
MediaBrowser.Api/HttpHandlers/GenreHandler.cs
Normal file
57
MediaBrowser.Api/HttpHandlers/GenreHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
78
MediaBrowser.Api/HttpHandlers/GenresHandler.cs
Normal file
78
MediaBrowser.Api/HttpHandlers/GenresHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
224
MediaBrowser.Api/HttpHandlers/ImageHandler.cs
Normal file
224
MediaBrowser.Api/HttpHandlers/ImageHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
35
MediaBrowser.Api/HttpHandlers/ItemHandler.cs
Normal file
35
MediaBrowser.Api/HttpHandlers/ItemHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
84
MediaBrowser.Api/HttpHandlers/ItemListHandler.cs
Normal file
84
MediaBrowser.Api/HttpHandlers/ItemListHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
MediaBrowser.Api/HttpHandlers/MovieSpecialFeaturesHandler.cs
Normal file
46
MediaBrowser.Api/HttpHandlers/MovieSpecialFeaturesHandler.cs
Normal 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"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
MediaBrowser.Api/HttpHandlers/PersonHandler.cs
Normal file
55
MediaBrowser.Api/HttpHandlers/PersonHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
38
MediaBrowser.Api/HttpHandlers/PlayedStatusHandler.cs
Normal file
38
MediaBrowser.Api/HttpHandlers/PlayedStatusHandler.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
38
MediaBrowser.Api/HttpHandlers/PluginAssemblyHandler.cs
Normal file
38
MediaBrowser.Api/HttpHandlers/PluginAssemblyHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
53
MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs
Normal file
53
MediaBrowser.Api/HttpHandlers/PluginConfigurationHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
38
MediaBrowser.Api/HttpHandlers/PluginsHandler.cs
Normal file
38
MediaBrowser.Api/HttpHandlers/PluginsHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
37
MediaBrowser.Api/HttpHandlers/ServerConfigurationHandler.cs
Normal file
37
MediaBrowser.Api/HttpHandlers/ServerConfigurationHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
57
MediaBrowser.Api/HttpHandlers/StudioHandler.cs
Normal file
57
MediaBrowser.Api/HttpHandlers/StudioHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
78
MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
Normal file
78
MediaBrowser.Api/HttpHandlers/StudiosHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
29
MediaBrowser.Api/HttpHandlers/UserAuthenticationHandler.cs
Normal file
29
MediaBrowser.Api/HttpHandlers/UserAuthenticationHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
29
MediaBrowser.Api/HttpHandlers/UserHandler.cs
Normal file
29
MediaBrowser.Api/HttpHandlers/UserHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
46
MediaBrowser.Api/HttpHandlers/UserItemRatingHandler.cs
Normal file
46
MediaBrowser.Api/HttpHandlers/UserItemRatingHandler.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
25
MediaBrowser.Api/HttpHandlers/UsersHandler.cs
Normal file
25
MediaBrowser.Api/HttpHandlers/UsersHandler.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
424
MediaBrowser.Api/HttpHandlers/VideoHandler.cs
Normal file
424
MediaBrowser.Api/HttpHandlers/VideoHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
43
MediaBrowser.Api/HttpHandlers/WeatherHandler.cs
Normal file
43
MediaBrowser.Api/HttpHandlers/WeatherHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
55
MediaBrowser.Api/HttpHandlers/YearHandler.cs
Normal file
55
MediaBrowser.Api/HttpHandlers/YearHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
75
MediaBrowser.Api/HttpHandlers/YearsHandler.cs
Normal file
75
MediaBrowser.Api/HttpHandlers/YearsHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
117
MediaBrowser.Api/MediaBrowser.Api.csproj
Normal file
117
MediaBrowser.Api/MediaBrowser.Api.csproj
Normal 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>
|
14
MediaBrowser.Api/Plugin.cs
Normal file
14
MediaBrowser.Api/Plugin.cs
Normal 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"; }
|
||||
}
|
||||
}
|
||||
}
|
35
MediaBrowser.Api/Properties/AssemblyInfo.cs
Normal file
35
MediaBrowser.Api/Properties/AssemblyInfo.cs
Normal 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")]
|
6
MediaBrowser.Api/packages.config
Normal file
6
MediaBrowser.Api/packages.config
Normal 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>
|
12
MediaBrowser.ApiInteraction.Metro/ApiClient.cs
Normal file
12
MediaBrowser.ApiInteraction.Metro/ApiClient.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using System.Net.Http;
|
||||
|
||||
namespace MediaBrowser.ApiInteraction
|
||||
{
|
||||
public class ApiClient : BaseHttpApiClient
|
||||
{
|
||||
public ApiClient(HttpClientHandler handler)
|
||||
: base(handler)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
78
MediaBrowser.ApiInteraction.Metro/DataSerializer.cs
Normal file
78
MediaBrowser.ApiInteraction.Metro/DataSerializer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
30
MediaBrowser.ApiInteraction.Metro/Properties/AssemblyInfo.cs
Normal file
30
MediaBrowser.ApiInteraction.Metro/Properties/AssemblyInfo.cs
Normal 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")]
|
37
MediaBrowser.ApiInteraction.sln
Normal file
37
MediaBrowser.ApiInteraction.sln
Normal 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
|
18
MediaBrowser.ApiInteraction/ApiClient.cs
Normal file
18
MediaBrowser.ApiInteraction/ApiClient.cs
Normal 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) })
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
446
MediaBrowser.ApiInteraction/BaseApiClient.cs
Normal file
446
MediaBrowser.ApiInteraction/BaseApiClient.cs
Normal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
611
MediaBrowser.ApiInteraction/BaseHttpApiClient.cs
Normal file
611
MediaBrowser.ApiInteraction/BaseHttpApiClient.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
77
MediaBrowser.ApiInteraction/DataSerializer.cs
Normal file
77
MediaBrowser.ApiInteraction/DataSerializer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
35
MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs
Normal file
35
MediaBrowser.ApiInteraction/Properties/AssemblyInfo.cs
Normal 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")]
|
10
MediaBrowser.ApiInteraction/SerializationFormats.cs
Normal file
10
MediaBrowser.ApiInteraction/SerializationFormats.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
namespace MediaBrowser.ApiInteraction
|
||||
{
|
||||
public enum SerializationFormats
|
||||
{
|
||||
Json,
|
||||
Jsv,
|
||||
Protobuf
|
||||
}
|
||||
}
|
4
MediaBrowser.ApiInteraction/packages.config
Normal file
4
MediaBrowser.ApiInteraction/packages.config
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="ServiceStack.Text" version="3.9.9" targetFramework="net45" />
|
||||
</packages>
|
12
MediaBrowser.Common/Events/GenericEventArgs.cs
Normal file
12
MediaBrowser.Common/Events/GenericEventArgs.cs
Normal 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; }
|
||||
}
|
||||
}
|
63
MediaBrowser.Common/Extensions/BaseExtensions.cs
Normal file
63
MediaBrowser.Common/Extensions/BaseExtensions.cs
Normal 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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
154
MediaBrowser.Common/Kernel/BaseApplicationPaths.cs
Normal file
154
MediaBrowser.Common/Kernel/BaseApplicationPaths.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
345
MediaBrowser.Common/Kernel/BaseKernel.cs
Normal file
345
MediaBrowser.Common/Kernel/BaseKernel.cs
Normal 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();
|
||||
}
|
||||
}
|
9
MediaBrowser.Common/Kernel/KernelContext.cs
Normal file
9
MediaBrowser.Common/Kernel/KernelContext.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
|
||||
namespace MediaBrowser.Common.Kernel
|
||||
{
|
||||
public enum KernelContext
|
||||
{
|
||||
Server,
|
||||
Ui
|
||||
}
|
||||
}
|
16
MediaBrowser.Common/Logging/BaseLogger.cs
Normal file
16
MediaBrowser.Common/Logging/BaseLogger.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
44
MediaBrowser.Common/Logging/LogRow.cs
Normal file
44
MediaBrowser.Common/Logging/LogRow.cs
Normal 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] ");
|
||||
}
|
||||
}
|
||||
}
|
14
MediaBrowser.Common/Logging/LogSeverity.cs
Normal file
14
MediaBrowser.Common/Logging/LogSeverity.cs
Normal 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
|
||||
}
|
||||
}
|
93
MediaBrowser.Common/Logging/Logger.cs
Normal file
93
MediaBrowser.Common/Logging/Logger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
38
MediaBrowser.Common/Logging/TraceFileLogger.cs
Normal file
38
MediaBrowser.Common/Logging/TraceFileLogger.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
164
MediaBrowser.Common/MediaBrowser.Common.csproj
Normal file
164
MediaBrowser.Common/MediaBrowser.Common.csproj
Normal 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>
|
43
MediaBrowser.Common/Mef/MefUtils.cs
Normal file
43
MediaBrowser.Common/Mef/MefUtils.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
430
MediaBrowser.Common/Net/Handlers/BaseHandler.cs
Normal file
430
MediaBrowser.Common/Net/Handlers/BaseHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
90
MediaBrowser.Common/Net/Handlers/BaseSerializationHandler.cs
Normal file
90
MediaBrowser.Common/Net/Handlers/BaseSerializationHandler.cs
Normal 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
|
||||
}
|
||||
|
||||
}
|
249
MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs
Normal file
249
MediaBrowser.Common/Net/Handlers/StaticFileHandler.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
40
MediaBrowser.Common/Net/HttpServer.cs
Normal file
40
MediaBrowser.Common/Net/HttpServer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
160
MediaBrowser.Common/Net/MimeTypes.cs
Normal file
160
MediaBrowser.Common/Net/MimeTypes.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
18
MediaBrowser.Common/Net/Request.cs
Normal file
18
MediaBrowser.Common/Net/Request.cs
Normal 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()); }
|
||||
}
|
||||
}
|
||||
}
|
247
MediaBrowser.Common/Plugins/BasePlugin.cs
Normal file
247
MediaBrowser.Common/Plugins/BasePlugin.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
78
MediaBrowser.Common/Plugins/BaseTheme.cs
Normal file
78
MediaBrowser.Common/Plugins/BaseTheme.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
35
MediaBrowser.Common/Properties/AssemblyInfo.cs
Normal file
35
MediaBrowser.Common/Properties/AssemblyInfo.cs
Normal 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")]
|
63
MediaBrowser.Common/Properties/Resources.Designer.cs
generated
Normal file
63
MediaBrowser.Common/Properties/Resources.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
121
MediaBrowser.Common/Properties/Resources.resx
Normal file
121
MediaBrowser.Common/Properties/Resources.resx
Normal 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>
|
BIN
MediaBrowser.Common/Resources/Images/Icon.ico
Normal file
BIN
MediaBrowser.Common/Resources/Images/Icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 143 KiB |
BIN
MediaBrowser.Common/Resources/Images/mblogoblack.png
Normal file
BIN
MediaBrowser.Common/Resources/Images/mblogoblack.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
BIN
MediaBrowser.Common/Resources/Images/mblogowhite.png
Normal file
BIN
MediaBrowser.Common/Resources/Images/mblogowhite.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
MediaBrowser.Common/Resources/Images/spinner.gif
Normal file
BIN
MediaBrowser.Common/Resources/Images/spinner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 673 B |
74
MediaBrowser.Common/Serialization/JsonSerializer.cs
Normal file
74
MediaBrowser.Common/Serialization/JsonSerializer.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
MediaBrowser.Common/Serialization/JsvSerializer.cs
Normal file
44
MediaBrowser.Common/Serialization/JsvSerializer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
MediaBrowser.Common/Serialization/ProtobufSerializer.cs
Normal file
53
MediaBrowser.Common/Serialization/ProtobufSerializer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
58
MediaBrowser.Common/Serialization/XmlSerializer.cs
Normal file
58
MediaBrowser.Common/Serialization/XmlSerializer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
123
MediaBrowser.Common/UI/BaseApplication.cs
Normal file
123
MediaBrowser.Common/UI/BaseApplication.cs
Normal 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();
|
||||
}
|
||||
}
|
484
MediaBrowser.Common/UI/SingleInstance.cs
Normal file
484
MediaBrowser.Common/UI/SingleInstance.cs
Normal 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
|
||||
}
|
||||
}
|
33
MediaBrowser.Common/UI/Splash.xaml
Normal file
33
MediaBrowser.Common/UI/Splash.xaml
Normal 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>
|
32
MediaBrowser.Common/UI/Splash.xaml.cs
Normal file
32
MediaBrowser.Common/UI/Splash.xaml.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
15
MediaBrowser.Common/app.config
Normal file
15
MediaBrowser.Common/app.config
Normal 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>
|
8
MediaBrowser.Common/packages.config
Normal file
8
MediaBrowser.Common/packages.config
Normal 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>
|
81
MediaBrowser.Controller/Drawing/DrawingUtils.cs
Normal file
81
MediaBrowser.Controller/Drawing/DrawingUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
148
MediaBrowser.Controller/Drawing/ImageProcessor.cs
Normal file
148
MediaBrowser.Controller/Drawing/ImageProcessor.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
14
MediaBrowser.Controller/Entities/Audio.cs
Normal file
14
MediaBrowser.Controller/Entities/Audio.cs
Normal 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; }
|
||||
}
|
||||
}
|
94
MediaBrowser.Controller/Entities/BaseEntity.cs
Normal file
94
MediaBrowser.Controller/Entities/BaseEntity.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
202
MediaBrowser.Controller/Entities/BaseItem.cs
Normal file
202
MediaBrowser.Controller/Entities/BaseItem.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
619
MediaBrowser.Controller/Entities/Folder.cs
Normal file
619
MediaBrowser.Controller/Entities/Folder.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
7
MediaBrowser.Controller/Entities/Genre.cs
Normal file
7
MediaBrowser.Controller/Entities/Genre.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
public class Genre : BaseEntity
|
||||
{
|
||||
}
|
||||
}
|
7
MediaBrowser.Controller/Entities/Movies/BoxSet.cs
Normal file
7
MediaBrowser.Controller/Entities/Movies/BoxSet.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
namespace MediaBrowser.Controller.Entities.Movies
|
||||
{
|
||||
public class BoxSet : Folder
|
||||
{
|
||||
}
|
||||
}
|
31
MediaBrowser.Controller/Entities/Movies/Movie.cs
Normal file
31
MediaBrowser.Controller/Entities/Movies/Movie.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
25
MediaBrowser.Controller/Entities/Person.cs
Normal file
25
MediaBrowser.Controller/Entities/Person.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
7
MediaBrowser.Controller/Entities/Studio.cs
Normal file
7
MediaBrowser.Controller/Entities/Studio.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
namespace MediaBrowser.Controller.Entities
|
||||
{
|
||||
public class Studio : BaseEntity
|
||||
{
|
||||
}
|
||||
}
|
7
MediaBrowser.Controller/Entities/TV/Episode.cs
Normal file
7
MediaBrowser.Controller/Entities/TV/Episode.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
|
||||
namespace MediaBrowser.Controller.Entities.TV
|
||||
{
|
||||
public class Episode : Video
|
||||
{
|
||||
}
|
||||
}
|
34
MediaBrowser.Controller/Entities/TV/Season.cs
Normal file
34
MediaBrowser.Controller/Entities/TV/Season.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
12
MediaBrowser.Controller/Entities/TV/Series.cs
Normal file
12
MediaBrowser.Controller/Entities/TV/Series.cs
Normal 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; }
|
||||
}
|
||||
}
|
21
MediaBrowser.Controller/Entities/User.cs
Normal file
21
MediaBrowser.Controller/Entities/User.cs
Normal 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; }
|
||||
}
|
||||
}
|
67
MediaBrowser.Controller/Entities/UserItemData.cs
Normal file
67
MediaBrowser.Controller/Entities/UserItemData.cs
Normal 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
Loading…
Reference in New Issue
Block a user