Преглед на файлове

Merge pull request #1086 from t-andre/dev

Report manager implementation
Luke преди 10 години
родител
ревизия
baa59d9006
променени са 28 файла, в които са добавени 3095 реда и са изтрити 101 реда
  1. 21 3
      MediaBrowser.Api/MediaBrowser.Api.csproj
  2. 47 0
      MediaBrowser.Api/Reports/Common/HeaderMetadata.cs
  3. 20 0
      MediaBrowser.Api/Reports/Common/ItemViewType.cs
  4. 229 0
      MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs
  5. 12 0
      MediaBrowser.Api/Reports/Common/ReportExportType.cs
  6. 18 0
      MediaBrowser.Api/Reports/Common/ReportFieldType.cs
  7. 12 0
      MediaBrowser.Api/Reports/Common/ReportHeaderIdType.cs
  8. 99 0
      MediaBrowser.Api/Reports/Common/ReportHelper.cs
  9. 25 0
      MediaBrowser.Api/Reports/Common/ReportViewType.cs
  10. 589 0
      MediaBrowser.Api/Reports/Data/ReportBuilder.cs
  11. 212 0
      MediaBrowser.Api/Reports/Data/ReportExport.cs
  12. 44 0
      MediaBrowser.Api/Reports/Data/ReportGroup.cs
  13. 54 0
      MediaBrowser.Api/Reports/Data/ReportHeader.cs
  14. 34 0
      MediaBrowser.Api/Reports/Data/ReportItem.cs
  15. 52 0
      MediaBrowser.Api/Reports/Data/ReportOptions.cs
  16. 53 0
      MediaBrowser.Api/Reports/Data/ReportResult.cs
  17. 71 0
      MediaBrowser.Api/Reports/Data/ReportRow.cs
  18. 0 9
      MediaBrowser.Api/Reports/ReportFieldType.cs
  19. 41 29
      MediaBrowser.Api/Reports/ReportRequests.cs
  20. 0 16
      MediaBrowser.Api/Reports/ReportResult.cs
  21. 1141 42
      MediaBrowser.Api/Reports/ReportsService.cs
  22. 214 0
      MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs
  23. 37 0
      MediaBrowser.Api/Reports/Stat/ReportStatGroup.cs
  24. 29 0
      MediaBrowser.Api/Reports/Stat/ReportStatItem.cs
  25. 28 0
      MediaBrowser.Api/Reports/Stat/ReportStatResult.cs
  26. 6 1
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  27. 1 1
      MediaBrowser.WebDashboard/Api/PackageCreator.cs
  28. 6 0
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 21 - 3
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -84,10 +84,28 @@
     <Compile Include="Playback\MediaInfoService.cs" />
     <Compile Include="Playback\TranscodingThrottler.cs" />
     <Compile Include="PlaylistService.cs" />
-    <Compile Include="Reports\ReportFieldType.cs" />
-    <Compile Include="Reports\ReportResult.cs" />
-    <Compile Include="Reports\ReportsService.cs" />
+    <Compile Include="Reports\Common\HeaderMetadata.cs" />
+    <Compile Include="Reports\Common\ItemViewType.cs" />
+    <Compile Include="Reports\Common\ReportBuilderBase.cs" />
+    <Compile Include="Reports\Common\ReportExportType.cs" />
+    <Compile Include="Reports\Common\ReportFieldType.cs" />
+    <Compile Include="Reports\Common\ReportHeaderIdType.cs" />
+    <Compile Include="Reports\Common\ReportHelper.cs" />
+    <Compile Include="Reports\Common\ReportViewType.cs" />
+    <Compile Include="Reports\Data\ReportBuilder.cs" />
+    <Compile Include="Reports\Data\ReportExport.cs" />
+    <Compile Include="Reports\Data\ReportGroup.cs" />
+    <Compile Include="Reports\Data\ReportHeader.cs" />
+    <Compile Include="Reports\Data\ReportItem.cs" />
+    <Compile Include="Reports\Data\ReportOptions.cs" />
+    <Compile Include="Reports\Data\ReportResult.cs" />
+    <Compile Include="Reports\Data\ReportRow.cs" />
     <Compile Include="Reports\ReportRequests.cs" />
+    <Compile Include="Reports\ReportsService.cs" />
+    <Compile Include="Reports\Stat\ReportStatBuilder.cs" />
+    <Compile Include="Reports\Stat\ReportStatGroup.cs" />
+    <Compile Include="Reports\Stat\ReportStatItem.cs" />
+    <Compile Include="Reports\Stat\ReportStatResult.cs" />
     <Compile Include="StartupWizardService.cs" />
     <Compile Include="Subtitles\SubtitleService.cs" />
     <Compile Include="Movies\CollectionService.cs" />

+ 47 - 0
MediaBrowser.Api/Reports/Common/HeaderMetadata.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+	public enum HeaderMetadata
+	{
+		None,
+		Name,
+		PremiereDate,
+		DateAdded,
+		ReleaseDate,
+		Runtime,
+		PlayCount,
+		Season,
+		SeasonNumber,
+		Series,
+		Network,
+		Year,
+		ParentalRating,
+		CommunityRating,
+		Trailers,
+		Specials,
+		GameSystem,
+		Players,
+		AlbumArtist,
+		Album,
+		Disc,
+		Track,
+		Audio,
+		EmbeddedImage,
+		Video,
+		Resolution,
+		Subtitles,
+		Genres,
+		Countries,
+		StatusImage,
+		Tracks,
+		EpisodeSeries,
+		EpisodeSeason,
+		AudioAlbumArtist,
+		MusicArtist,
+		AudioAlbum,
+		Status
+	}
+}

+ 20 - 0
MediaBrowser.Api/Reports/Common/ItemViewType.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+	public enum ItemViewType
+	{
+		None,
+		Detail,
+		Edit,
+		List,
+		ItemByNameDetails,
+		StatusImage,
+		EmbeddedImage,
+		SubtitleImage,
+		TrailersImage,
+		SpecialsImage
+	}
+}

+ 229 - 0
MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs

@@ -0,0 +1,229 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report builder base. </summary>
+	public class ReportBuilderBase
+	{
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilderBase class. </summary>
+		/// <param name="libraryManager"> Manager for library. </param>
+		public ReportBuilderBase(ILibraryManager libraryManager)
+		{
+			_libraryManager = libraryManager;
+		}
+
+		/// <summary> Manager for library. </summary>
+		protected readonly ILibraryManager _libraryManager;
+
+		/// <summary> Gets audio stream. </summary>
+		/// <param name="item"> The item. </param>
+		/// <returns> The audio stream. </returns>
+		protected string GetAudioStream(BaseItem item)
+		{
+			var stream = GetStream(item, MediaStreamType.Audio);
+			if (stream != null)
+				return stream.Codec.ToUpper() == "DCA" ? stream.Profile : stream.Codec.
+				ToUpper();
+
+			return string.Empty;
+		}
+
+		/// <summary> Gets an episode. </summary>
+		/// <param name="item"> The item. </param>
+		/// <returns> The episode. </returns>
+		protected string GetEpisode(BaseItem item)
+		{
+
+			if (item.GetClientTypeName() == ChannelMediaContentType.Episode.ToString() && item.ParentIndexNumber != null)
+				return "Season " + item.ParentIndexNumber;
+			else
+				return item.Name;
+		}
+
+		/// <summary> Gets a genre. </summary>
+		/// <param name="name"> The name. </param>
+		/// <returns> The genre. </returns>
+		protected Genre GetGenre(string name)
+		{
+			if (string.IsNullOrEmpty(name))
+				return null;
+			return _libraryManager.GetGenre(name);
+		}
+
+		/// <summary> Gets genre identifier. </summary>
+		/// <param name="name"> The name. </param>
+		/// <returns> The genre identifier. </returns>
+		protected string GetGenreID(string name)
+		{
+			if (string.IsNullOrEmpty(name))
+				return string.Empty;
+			return string.Format("{0:N}",
+					GetGenre(name).Id);
+		}
+
+		/// <summary> Gets list as string. </summary>
+		/// <param name="items"> The items. </param>
+		/// <returns> The list as string. </returns>
+		protected string GetListAsString(List<string> items)
+		{
+			return String.Join("; ", items);
+		}
+
+		/// <summary> Gets media source information. </summary>
+		/// <param name="item"> The item. </param>
+		/// <returns> The media source information. </returns>
+		protected MediaSourceInfo GetMediaSourceInfo(BaseItem item)
+		{
+			var mediaSource = item as IHasMediaSources;
+			if (mediaSource != null)
+				return mediaSource.GetMediaSources(false).FirstOrDefault(n => n.Type == MediaSourceType.Default);
+
+			return null;
+		}
+
+		/// <summary> Gets an object. </summary>
+		/// <typeparam name="T"> Generic type parameter. </typeparam>
+		/// <typeparam name="R"> Type of the r. </typeparam>
+		/// <param name="item"> The item. </param>
+		/// <param name="function"> The function. </param>
+		/// <param name="defaultValue"> The default value. </param>
+		/// <returns> The object. </returns>
+		protected R GetObject<T, R>(BaseItem item, Func<T, R> function, R defaultValue = default(R)) where T : class
+		{
+			var value = item as T;
+			if (value != null && function != null)
+				return function(value);
+			else
+				return defaultValue;
+		}
+
+		/// <summary> Gets a person. </summary>
+		/// <param name="name"> The name. </param>
+		/// <returns> The person. </returns>
+		protected Person GetPerson(string name)
+		{
+			if (string.IsNullOrEmpty(name))
+				return null;
+			return _libraryManager.GetPerson(name);
+		}
+
+		/// <summary> Gets person identifier. </summary>
+		/// <param name="name"> The name. </param>
+		/// <returns> The person identifier. </returns>
+		protected string GetPersonID(string name)
+		{
+			if (string.IsNullOrEmpty(name))
+				return string.Empty;
+			return string.Format("{0:N}",
+					GetPerson(name).Id);
+		}
+
+		/// <summary> Gets runtime date time. </summary>
+		/// <param name="runtime"> The runtime. </param>
+		/// <returns> The runtime date time. </returns>
+		protected DateTime? GetRuntimeDateTime(long? runtime)
+		{
+			if (runtime.HasValue)
+				return new DateTime(runtime.Value);
+			return null;
+		}
+
+		/// <summary> Gets series production year. </summary>
+		/// <param name="item"> The item. </param>
+		/// <returns> The series production year. </returns>
+		protected string GetSeriesProductionYear(BaseItem item)
+		{
+
+			string productionYear = item.ProductionYear.ToString();
+			var series = item as Series;
+			if (series == null)
+			{
+				if (item.ProductionYear == null || item.ProductionYear == 0)
+					return string.Empty;
+				return productionYear;
+			}
+
+			if (series.Status == SeriesStatus.Continuing)
+				return productionYear += "-Present";
+
+			if (series.EndDate != null && series.EndDate.Value.Year != series.ProductionYear)
+				return productionYear += "-" + series.EndDate.Value.Year;
+
+			return productionYear;
+		}
+
+		/// <summary> Gets a stream. </summary>
+		/// <param name="item"> The item. </param>
+		/// <param name="streamType"> Type of the stream. </param>
+		/// <returns> The stream. </returns>
+		protected MediaStream GetStream(BaseItem item, MediaStreamType streamType)
+		{
+			var itemInfo = GetMediaSourceInfo(item);
+			if (itemInfo != null)
+				return itemInfo.MediaStreams.FirstOrDefault(n => n.Type == streamType);
+
+			return null;
+		}
+
+		/// <summary> Gets a studio. </summary>
+		/// <param name="name"> The name. </param>
+		/// <returns> The studio. </returns>
+		protected Studio GetStudio(string name)
+		{
+			if (string.IsNullOrEmpty(name))
+				return null;
+			return _libraryManager.GetStudio(name);
+		}
+
+		/// <summary> Gets studio identifier. </summary>
+		/// <param name="name"> The name. </param>
+		/// <returns> The studio identifier. </returns>
+		protected string GetStudioID(string name)
+		{
+			if (string.IsNullOrEmpty(name))
+				return string.Empty;
+			return string.Format("{0:N}",
+					GetStudio(name).Id);
+		}
+
+		/// <summary> Gets video resolution. </summary>
+		/// <param name="item"> The item. </param>
+		/// <returns> The video resolution. </returns>
+		protected string GetVideoResolution(BaseItem item)
+		{
+			var stream = GetStream(item,
+					MediaStreamType.Video);
+			if (stream != null && stream.Width != null)
+				return string.Format("{0} * {1}",
+						stream.Width,
+						(stream.Height != null ? stream.Height.ToString() : "-"));
+
+			return string.Empty;
+		}
+
+		/// <summary> Gets video stream. </summary>
+		/// <param name="item"> The item. </param>
+		/// <returns> The video stream. </returns>
+		protected string GetVideoStream(BaseItem item)
+		{
+			var stream = GetStream(item, MediaStreamType.Video);
+			if (stream != null)
+				return stream.Codec.ToUpper();
+
+			return string.Empty;
+		}
+
+	}
+}

+ 12 - 0
MediaBrowser.Api/Reports/Common/ReportExportType.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+	public enum ReportExportType
+	{
+		CSV,
+		Excel
+	}
+}

+ 18 - 0
MediaBrowser.Api/Reports/Common/ReportFieldType.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+	public enum ReportFieldType
+	{
+		String,
+		Boolean,
+		Date,
+		Time,
+		DateTime,
+		Int,
+		Image,
+		Object
+	}
+}

+ 12 - 0
MediaBrowser.Api/Reports/Common/ReportHeaderIdType.cs

@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+	public enum ReportHeaderIdType
+	{
+		Row,
+		Item
+	}
+}

+ 99 - 0
MediaBrowser.Api/Reports/Common/ReportHelper.cs

@@ -0,0 +1,99 @@
+using MediaBrowser.Controller.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	public class ReportHelper
+	{
+		/// <summary> Gets java script localized string. </summary>
+		/// <param name="phrase"> The phrase. </param>
+		/// <returns> The java script localized string. </returns>
+		public static string GetJavaScriptLocalizedString(string phrase)
+		{
+			var dictionary = BaseItem.LocalizationManager.GetJavaScriptLocalizationDictionary(BaseItem.ConfigurationManager.Configuration.UICulture);
+
+			string value;
+
+			if (dictionary.TryGetValue(phrase, out value))
+			{
+				return value;
+			}
+
+			return phrase;
+		}
+
+		/// <summary> Gets server localized string. </summary>
+		/// <param name="phrase"> The phrase. </param>
+		/// <returns> The server localized string. </returns>
+		public static string GetServerLocalizedString(string phrase)
+		{
+			return BaseItem.LocalizationManager.GetLocalizedString(phrase, BaseItem.ConfigurationManager.Configuration.UICulture);
+		}
+
+		/// <summary> Gets row type. </summary>
+		/// <param name="rowType"> The type. </param>
+		/// <returns> The row type. </returns>
+		public static ReportViewType GetRowType(string rowType)
+		{
+			if (string.IsNullOrEmpty(rowType))
+				return ReportViewType.BaseItem;
+
+			ReportViewType rType;
+
+			if (!Enum.TryParse<ReportViewType>(rowType, out rType))
+				return ReportViewType.BaseItem;
+
+			return rType;
+		}
+
+		/// <summary> Gets header metadata type. </summary>
+		/// <param name="header"> The header. </param>
+		/// <returns> The header metadata type. </returns>
+		public static HeaderMetadata GetHeaderMetadataType(string header)
+		{
+			if (string.IsNullOrEmpty(header))
+				return HeaderMetadata.None;
+
+			HeaderMetadata rType;
+
+			if (!Enum.TryParse<HeaderMetadata>(header, out rType))
+				return HeaderMetadata.None;
+
+			return rType;
+		}
+
+		/// <summary> Convert field to string. </summary>
+		/// <typeparam name="T"> Generic type parameter. </typeparam>
+		/// <param name="value"> The value. </param>
+		/// <param name="fieldType"> Type of the field. </param>
+		/// <returns> The field converted to string. </returns>
+		public static string ConvertToString<T>(T value, ReportFieldType fieldType)
+		{
+			if (value == null)
+				return "";
+			switch (fieldType)
+			{
+				case ReportFieldType.String:
+					return value.ToString();
+				case ReportFieldType.Boolean:
+					return value.ToString();
+				case ReportFieldType.Date:
+					return string.Format("{0:d}", value);
+				case ReportFieldType.Time:
+					return string.Format("{0:t}", value);
+				case ReportFieldType.DateTime:
+					return string.Format("{0:d}", value);
+				case ReportFieldType.Int:
+					return string.Format("", value);
+				default:
+					if (value is Guid)
+						return string.Format("{0:N}", value);
+					return value.ToString();
+			}
+		}
+	}
+}

+ 25 - 0
MediaBrowser.Api/Reports/Common/ReportViewType.cs

@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace MediaBrowser.Api.Reports
+{
+	public enum ReportViewType
+	{
+		MusicArtist,
+		MusicAlbum,
+		Book,
+		BoxSet,
+		Episode,
+		Game,
+		Video,
+		Movie,
+		MusicVideo,
+		Trailer,
+		Season,
+		Series,
+		Audio,
+		BaseItem,
+		Artist
+	}
+}

+ 589 - 0
MediaBrowser.Api/Reports/Data/ReportBuilder.cs

@@ -0,0 +1,589 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Localization;
+using MediaBrowser.Model.Channels;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report builder. </summary>
+	/// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
+	public class ReportBuilder : ReportBuilderBase
+	{
+
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilder class. </summary>
+		/// <param name="libraryManager"> Manager for library. </param>
+		public ReportBuilder(ILibraryManager libraryManager)
+			: base(libraryManager)
+		{
+		}
+
+		private Func<bool, string> GetBoolString = s => s == true ? "x" : "";
+
+		public ReportResult GetReportResult(BaseItem[] items, ReportViewType reportRowType, BaseReportRequest request)
+		{
+			List<HeaderMetadata> headersMetadata = this.GetFilteredReportHeaderMetadata(reportRowType, request);
+
+			var headers = GetReportHeaders(reportRowType, headersMetadata);
+			var rows = GetReportRows(items, headersMetadata);
+
+			ReportResult result = new ReportResult { Headers = headers };
+			HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy);
+			int i = headers.FindIndex(x => x.FieldName == groupBy);
+			if (groupBy != HeaderMetadata.None && i > 0)
+			{
+				var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Genre = g.Trim(), Rows = x })
+					.GroupBy(x => x.Genre)
+					.OrderBy(x => x.Key)
+					.Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() });
+
+				result.Groups = rowsGroup.ToList();
+				result.IsGrouped = true;
+			}
+			else
+			{
+				result.Rows = rows;
+				result.IsGrouped = false;
+			}
+
+			return result;
+		}
+
+		public List<ReportHeader> GetReportHeaders(ReportViewType reportRowType, BaseReportRequest request)
+		{
+			List<ReportHeader> headersMetadata = this.GetReportHeaders(reportRowType);
+			if (request != null && !string.IsNullOrEmpty(request.ReportColumns))
+			{
+				List<HeaderMetadata> headersMetadataFiltered = this.GetFilteredReportHeaderMetadata(reportRowType, request);
+				foreach (ReportHeader reportHeader in headersMetadata)
+				{
+					if (!headersMetadataFiltered.Contains(reportHeader.FieldName))
+					{
+						reportHeader.Visible = false;
+					}
+				}
+
+
+			}
+
+			return headersMetadata;
+		}
+
+		public List<ReportHeader> GetReportHeaders(ReportViewType reportRowType, List<HeaderMetadata> headersMetadata = null)
+		{
+			if (headersMetadata == null)
+				headersMetadata = this.GetDefaultReportHeaderMetadata(reportRowType);
+
+			List<ReportOptions<BaseItem>> options = new List<ReportOptions<BaseItem>>();
+			foreach (HeaderMetadata header in headersMetadata)
+			{
+				options.Add(GetReportOption(header));
+			}
+
+
+			List<ReportHeader> headers = new List<ReportHeader>();
+			foreach (ReportOptions<BaseItem> option in options)
+			{
+				headers.Add(option.Header);
+			}
+			return headers;
+		}
+
+		private List<ReportRow> GetReportRows(IEnumerable<BaseItem> items, List<HeaderMetadata> headersMetadata)
+		{
+			List<ReportOptions<BaseItem>> options = new List<ReportOptions<BaseItem>>();
+			foreach (HeaderMetadata header in headersMetadata)
+			{
+				options.Add(GetReportOption(header));
+			}
+
+			var rows = new List<ReportRow>();
+
+			foreach (BaseItem item in items)
+			{
+				ReportRow rRow = GetRow(item);
+				foreach (ReportOptions<BaseItem> option in options)
+				{
+					object itemColumn = option.Column != null ? option.Column(item, rRow) : "";
+					object itemId = option.ItemID != null ? option.ItemID(item) : "";
+					ReportItem rItem = new ReportItem
+					{
+						Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType),
+						Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object)
+					};
+					rRow.Columns.Add(rItem);
+				}
+
+				rows.Add(rRow);
+			}
+
+			return rows;
+		}
+
+		/// <summary> Gets a row. </summary>
+		/// <param name="item"> The item. </param>
+		/// <returns> The row. </returns>
+		private ReportRow GetRow(BaseItem item)
+		{
+			var hasTrailers = item as IHasTrailers;
+			var hasSpecialFeatures = item as IHasSpecialFeatures;
+			var video = item as Video;
+			ReportRow rRow = new ReportRow
+			{
+				Id = item.Id.ToString("N"),
+				HasLockData = item.IsLocked,
+				IsUnidentified = item.IsUnidentified,
+				HasLocalTrailer = hasTrailers != null ? hasTrailers.GetTrailerIds().Count() > 0 : false,
+				HasImageTagsPrimary = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Primary) > 0),
+				HasImageTagsBackdrop = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Backdrop) > 0),
+				HasImageTagsLogo = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Logo) > 0),
+				HasSpecials = hasSpecialFeatures != null ? hasSpecialFeatures.SpecialFeatureIds.Count > 0 : false,
+				HasSubtitles = video != null ? video.HasSubtitles : false,
+				RowType = ReportHelper.GetRowType(item.GetClientTypeName())
+			};
+			return rRow;
+		}
+		public List<HeaderMetadata> GetFilteredReportHeaderMetadata(ReportViewType reportRowType, BaseReportRequest request)
+		{
+			if (request != null && !string.IsNullOrEmpty(request.ReportColumns))
+			{
+				var s = request.ReportColumns.Split('|').Select(x => ReportHelper.GetHeaderMetadataType(x)).Where(x => x != HeaderMetadata.None);
+				return s.ToList();
+			}
+			else
+				return this.GetDefaultReportHeaderMetadata(reportRowType);
+
+		}
+
+		public List<HeaderMetadata> GetDefaultReportHeaderMetadata(ReportViewType reportRowType)
+		{
+			switch (reportRowType)
+			{
+				case ReportViewType.Season:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Series,
+						HeaderMetadata.Season,
+						HeaderMetadata.SeasonNumber,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres
+					};
+
+				case ReportViewType.Series:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.Network,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating,
+						HeaderMetadata.Runtime,
+						HeaderMetadata.Trailers,
+						HeaderMetadata.Specials
+					};
+
+				case ReportViewType.MusicAlbum:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.AlbumArtist,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.Tracks,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres
+					};
+
+				case ReportViewType.MusicArtist:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.MusicArtist,
+						HeaderMetadata.Countries,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres
+					};
+
+				case ReportViewType.Game:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.GameSystem,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating,
+						HeaderMetadata.Players,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.Trailers
+					};
+
+				case ReportViewType.Movie:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating,
+						HeaderMetadata.Runtime,
+						HeaderMetadata.Video,
+						HeaderMetadata.Resolution,
+						HeaderMetadata.Audio,
+						HeaderMetadata.Subtitles,
+						HeaderMetadata.Trailers,
+						HeaderMetadata.Specials
+					};
+
+				case ReportViewType.Book:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating
+					};
+
+				case ReportViewType.BoxSet:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating,
+						HeaderMetadata.Trailers
+					};
+
+				case ReportViewType.Audio:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.AudioAlbumArtist,
+						HeaderMetadata.AudioAlbum,
+						HeaderMetadata.Disc,
+						HeaderMetadata.Track,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating,
+						HeaderMetadata.Runtime,
+						HeaderMetadata.Audio
+					};
+
+				case ReportViewType.Episode:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.EpisodeSeries,
+						HeaderMetadata.Season,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating,
+						HeaderMetadata.Runtime,
+						HeaderMetadata.Video,
+						HeaderMetadata.Resolution,
+						HeaderMetadata.Audio,
+						HeaderMetadata.Subtitles,
+						HeaderMetadata.Trailers,
+						HeaderMetadata.Specials
+					};
+
+				case ReportViewType.Video:
+				case ReportViewType.MusicVideo:
+				case ReportViewType.Trailer:
+				case ReportViewType.BaseItem:
+				default:
+					return new List<HeaderMetadata>
+					{
+						HeaderMetadata.StatusImage,
+						HeaderMetadata.Name,
+						HeaderMetadata.DateAdded,
+						HeaderMetadata.ReleaseDate,
+						HeaderMetadata.Year,
+						HeaderMetadata.Genres,
+						HeaderMetadata.ParentalRating,
+						HeaderMetadata.CommunityRating,
+						HeaderMetadata.Runtime,
+						HeaderMetadata.Video,
+						HeaderMetadata.Resolution,
+						HeaderMetadata.Audio,
+						HeaderMetadata.Subtitles,
+						HeaderMetadata.Trailers,
+						HeaderMetadata.Specials
+					};
+
+			}
+
+		}
+
+		/// <summary> Gets report option. </summary>
+		/// <param name="header"> The header. </param>
+		/// <param name="sortField"> The sort field. </param>
+		/// <returns> The report option. </returns>
+		private ReportOptions<BaseItem> GetReportOption(HeaderMetadata header, string sortField = "")
+		{
+			ReportHeader reportHeader = new ReportHeader
+			{
+				HeaderFieldType = ReportFieldType.String,
+				SortField = sortField,
+				Type = "",
+				ItemViewType = ItemViewType.None
+			};
+
+			Func<BaseItem, ReportRow, object> column = null;
+			Func<BaseItem, object> itemId = null;
+			HeaderMetadata internalHeader = header;
+
+			switch (header)
+			{
+				case HeaderMetadata.StatusImage:
+					reportHeader.ItemViewType = ItemViewType.StatusImage;
+					internalHeader = HeaderMetadata.Status;
+					reportHeader.CanGroup = false;
+					break;
+
+				case HeaderMetadata.Name:
+					column = (i, r) => i.Name;
+					reportHeader.ItemViewType = ItemViewType.Detail;
+					reportHeader.SortField = "SortName";
+					break;
+
+				case HeaderMetadata.DateAdded:
+					column = (i, r) => i.DateCreated;
+					reportHeader.SortField = "DateCreated,SortName";
+					reportHeader.HeaderFieldType = ReportFieldType.DateTime;
+					reportHeader.Type = "";
+					break;
+
+				case HeaderMetadata.PremiereDate:
+				case HeaderMetadata.ReleaseDate:
+					column = (i, r) => i.PremiereDate;
+					reportHeader.HeaderFieldType = ReportFieldType.DateTime;
+					reportHeader.SortField = "ProductionYear,PremiereDate,SortName";
+					break;
+
+				case HeaderMetadata.Runtime:
+					column = (i, r) => this.GetRuntimeDateTime(i.RunTimeTicks);
+					reportHeader.HeaderFieldType = ReportFieldType.Time;
+					reportHeader.SortField = "Runtime,SortName";
+					break;
+
+				case HeaderMetadata.PlayCount:
+					reportHeader.HeaderFieldType = ReportFieldType.Int;
+					break;
+
+				case HeaderMetadata.Season:
+					column = (i, r) => this.GetEpisode(i);
+					reportHeader.ItemViewType = ItemViewType.Detail;
+					reportHeader.SortField = "SortName";
+					break;
+
+				case HeaderMetadata.SeasonNumber:
+					column = (i, r) => this.GetObject<Season, string>(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString());
+					reportHeader.SortField = "IndexNumber";
+					reportHeader.HeaderFieldType = ReportFieldType.Int;
+					break;
+
+				case HeaderMetadata.Series:
+					column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
+					reportHeader.ItemViewType = ItemViewType.Detail;
+					reportHeader.SortField = "SeriesSortName,SortName";
+					break;
+
+				case HeaderMetadata.EpisodeSeries:
+					column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
+					reportHeader.ItemViewType = ItemViewType.Detail;
+					itemId = (i) =>
+					{
+						Series series = this.GetObject<Episode, Series>(i, (x) => x.Series);
+						if (series == null)
+							return string.Empty;
+						return series.Id;
+					};
+					reportHeader.SortField = "SeriesSortName,SortName";
+					internalHeader = HeaderMetadata.Series;
+					break;
+
+				case HeaderMetadata.EpisodeSeason:
+					column = (i, r) => this.GetObject<IHasSeries, string>(i, (x) => x.SeriesName);
+					reportHeader.ItemViewType = ItemViewType.Detail;
+					itemId = (i) =>
+					{
+						Season season = this.GetObject<Episode, Season>(i, (x) => x.Season);
+						if (season == null)
+							return string.Empty;
+						return season.Id;
+					};
+					reportHeader.SortField = "SortName";
+					internalHeader = HeaderMetadata.Season;
+					break;
+
+				case HeaderMetadata.Network:
+					column = (i, r) => this.GetListAsString(i.Studios);
+					itemId = (i) => this.GetStudioID(i.Studios.FirstOrDefault());
+					reportHeader.ItemViewType = ItemViewType.ItemByNameDetails;
+					reportHeader.SortField = "Studio,SortName";
+					break;
+
+				case HeaderMetadata.Year:
+					column = (i, r) => this.GetSeriesProductionYear(i);
+					reportHeader.SortField = "ProductionYear,PremiereDate,SortName";
+					break;
+
+				case HeaderMetadata.ParentalRating:
+					column = (i, r) => i.OfficialRating;
+					reportHeader.SortField = "OfficialRating,SortName";
+					break;
+
+				case HeaderMetadata.CommunityRating:
+					column = (i, r) => i.CommunityRating;
+					reportHeader.SortField = "CommunityRating,SortName";
+					break;
+
+				case HeaderMetadata.Trailers:
+					column = (i, r) => this.GetBoolString(r.HasLocalTrailer);
+					reportHeader.ItemViewType = ItemViewType.TrailersImage;
+					break;
+
+				case HeaderMetadata.Specials:
+					column = (i, r) => this.GetBoolString(r.HasSpecials);
+					reportHeader.ItemViewType = ItemViewType.SpecialsImage;
+					break;
+
+				case HeaderMetadata.GameSystem:
+					column = (i, r) => this.GetObject<Game, string>(i, (x) => x.GameSystem);
+					reportHeader.SortField = "GameSystem,SortName";
+					break;
+
+				case HeaderMetadata.Players:
+					column = (i, r) => this.GetObject<Game, int?>(i, (x) => x.PlayersSupported);
+					reportHeader.SortField = "Players,GameSystem,SortName";
+					break;
+
+				case HeaderMetadata.AlbumArtist:
+					column = (i, r) => this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist);
+					itemId = (i) => this.GetPersonID(this.GetObject<MusicAlbum, string>(i, (x) => x.AlbumArtist));
+					reportHeader.ItemViewType = ItemViewType.Detail;
+					reportHeader.SortField = "AlbumArtist,Album,SortName";
+
+					break;
+				case HeaderMetadata.MusicArtist:
+					column = (i, r) => this.GetObject<MusicArtist, string>(i, (x) => x.GetLookupInfo().Name);
+					reportHeader.ItemViewType = ItemViewType.Detail;
+					reportHeader.SortField = "AlbumArtist,Album,SortName";
+					internalHeader = HeaderMetadata.AlbumArtist;
+					break;
+				case HeaderMetadata.AudioAlbumArtist:
+					column = (i, r) => this.GetListAsString(this.GetObject<Audio, List<string>>(i, (x) => x.AlbumArtists));
+					reportHeader.SortField = "AlbumArtist,Album,SortName";
+					internalHeader = HeaderMetadata.AlbumArtist;
+					break;
+
+				case HeaderMetadata.AudioAlbum:
+					column = (i, r) => this.GetObject<Audio, string>(i, (x) => x.Album);
+					reportHeader.SortField = "Album,SortName";
+					internalHeader = HeaderMetadata.Album;
+					break;
+
+				case HeaderMetadata.Countries:
+					column = (i, r) => this.GetListAsString(this.GetObject<IHasProductionLocations, List<string>>(i, (x) => x.ProductionLocations));
+					break;
+
+				case HeaderMetadata.Disc:
+					column = (i, r) => i.ParentIndexNumber;
+					break;
+
+				case HeaderMetadata.Track:
+					column = (i, r) => i.IndexNumber;
+					break;
+
+				case HeaderMetadata.Tracks:
+					column = (i, r) => this.GetObject<MusicAlbum, List<Audio>>(i, (x) => x.Tracks.ToList(), new List<Audio>()).Count();
+					break;
+
+				case HeaderMetadata.Audio:
+					column = (i, r) => this.GetAudioStream(i);
+					break;
+
+				case HeaderMetadata.EmbeddedImage:
+					break;
+
+				case HeaderMetadata.Video:
+					column = (i, r) => this.GetVideoStream(i);
+					break;
+
+				case HeaderMetadata.Resolution:
+					column = (i, r) => this.GetVideoResolution(i);
+					break;
+
+				case HeaderMetadata.Subtitles:
+					column = (i, r) => this.GetBoolString(r.HasSubtitles);
+					reportHeader.ItemViewType = ItemViewType.SubtitleImage;
+					break;
+
+				case HeaderMetadata.Genres:
+					column = (i, r) => this.GetListAsString(i.Genres);
+					break;
+
+			}
+
+			string headerName = "";
+			if (internalHeader != HeaderMetadata.None)
+			{
+				string localHeader = "Header" + internalHeader.ToString();
+				headerName = internalHeader != HeaderMetadata.None ? ReportHelper.GetJavaScriptLocalizedString(localHeader) : "";
+				if (string.Compare(localHeader, headerName, StringComparison.CurrentCultureIgnoreCase) == 0)
+					headerName = ReportHelper.GetServerLocalizedString(localHeader);
+			}
+
+			reportHeader.Name = headerName;
+			reportHeader.FieldName = header;
+			ReportOptions<BaseItem> option = new ReportOptions<BaseItem>()
+			{
+				Header = reportHeader,
+				Column = column,
+				ItemID = itemId
+			};
+			return option;
+		}
+	}
+}

+ 212 - 0
MediaBrowser.Api/Reports/Data/ReportExport.cs

@@ -0,0 +1,212 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report export. </summary>
+	public class ReportExport
+	{
+		/// <summary> Export to CSV. </summary>
+		/// <param name="reportResult"> The report result. </param>
+		/// <returns> A string. </returns>
+		public string ExportToCsv(ReportResult reportResult)
+		{
+			StringBuilder returnValue = new StringBuilder();
+
+			returnValue.AppendLine(string.Join(";", reportResult.Headers.Select(s => s.Name.Replace(',', ' ')).ToArray()));
+
+			if (reportResult.IsGrouped)
+				foreach (ReportGroup group in reportResult.Groups)
+				{
+					foreach (ReportRow row in reportResult.Rows)
+					{
+						returnValue.AppendLine(string.Join(";", row.Columns.Select(s => s.Name.Replace(',', ' ')).ToArray()));
+					}
+				}
+			else
+				foreach (ReportRow row in reportResult.Rows)
+				{
+					returnValue.AppendLine(string.Join(";", row.Columns.Select(s => s.Name.Replace(',', ' ')).ToArray()));
+				}
+
+			return returnValue.ToString();
+		}
+
+
+		/// <summary> Export to excel. </summary>
+		/// <param name="reportResult"> The report result. </param>
+		/// <returns> A string. </returns>
+		public string ExportToExcel(ReportResult reportResult)
+		{
+
+			string style = @"<style type='text/css'>
+							BODY {
+									font-family: Arial;
+									font-size: 12px;
+								}
+
+								TABLE {
+									font-family: Arial;
+									font-size: 12px;
+								}
+
+								A {
+									font-family: Arial;
+									color: #144A86;
+									font-size: 12px;
+									cursor: pointer;
+									text-decoration: none;
+									font-weight: bold;
+								}
+								DIV {
+									font-family: Arial;
+									font-size: 12px;
+									margin-bottom: 0px;
+								}
+								P, LI, DIV {
+									font-size: 12px;
+									margin-bottom: 0px;
+								}
+
+								P, UL {
+									font-size: 12px;
+									margin-bottom: 6px;
+									margin-top: 0px;
+								}
+
+								H1 {
+									font-size: 18pt;
+								}
+
+								H2 {
+									font-weight: bold;
+									font-size: 14pt;
+									COLOR: #C0C0C0;
+								}
+
+								H3 {
+									font-weight: normal;
+									font-size: 14pt;
+									text-indent: +1em;
+								}
+
+								H4 {
+									font-size: 10pt;
+									font-weight: normal;
+								}
+
+								H5 {
+									font-size: 10pt;
+									font-weight: normal;
+									background: #A9A9A9;
+									COLOR: white;
+									display: inline;
+								}
+
+								H6 {
+									padding: 2 1 2 5;
+									font-size: 11px;
+									font-weight: bold;
+									text-decoration: none;
+									margin-bottom: 1px;
+								}
+
+								UL {
+									line-height: 1.5em;
+									list-style-type: disc;
+								}
+
+								OL {
+									line-height: 1.5em;
+								}
+
+								LI {
+									line-height: 1.5em;
+								}
+
+								A IMG {
+									border: 0;
+								}
+
+								table.gridtable {
+									color: #333333;
+									border-width: 0.1pt;
+									border-color: #666666;
+									border-collapse: collapse;
+								}
+
+								table.gridtable th {
+									border-width: 0.1pt;
+									padding: 8px;
+									border-style: solid;
+									border-color: #666666;
+									background-color: #dedede;
+								}
+								table.gridtable tr {
+									background-color: #ffffff;
+								}
+								table.gridtable td {
+									border-width: 0.1pt;
+									padding: 8px;
+									border-style: solid;
+									border-color: #666666;
+									background-color: #ffffff;
+								}
+						</style>";
+
+			string Html = @"<!DOCTYPE html>
+							<html xmlns='http://www.w3.org/1999/xhtml'>
+							<head>
+							<meta http-equiv='X-UA-Compatible' content='IE=8, IE=9, IE=10' />
+							<meta charset='utf-8'>
+							<title>Emby Reports Export</title>";
+			Html += "\n" + style + "\n";
+			Html += "</head>\n";
+			Html += "<body>\n";
+
+			StringBuilder returnValue = new StringBuilder();
+			returnValue.AppendLine("<table  class='gridtable'>");
+			returnValue.AppendLine("<tr>");
+			returnValue.AppendLine(string.Join("", reportResult.Headers.Select(s => string.Format("<th>{0}</th>", s.Name)).ToArray()));
+			returnValue.AppendLine("</tr>");
+			if (reportResult.IsGrouped)
+				foreach (ReportGroup group in reportResult.Groups)
+				{
+					returnValue.AppendLine("<tr>");
+					returnValue.AppendLine("<th scope='rowgroup' colspan='" + reportResult.Headers.Count + "'>" + (string.IsNullOrEmpty(group.Name) ? "&nbsp;" : group.Name) + "</th>");
+					returnValue.AppendLine("</tr>");
+					foreach (ReportRow row in group.Rows)
+					{
+						ExportToExcelRow(reportResult, returnValue, row);
+					}
+					returnValue.AppendLine("<tr>");
+					returnValue.AppendLine("<th style='background-color: #ffffff;' scope='rowgroup' colspan='" + reportResult.Headers.Count + "'>" + "&nbsp;" + "</th>");
+					returnValue.AppendLine("</tr>");
+				}
+
+			else
+				foreach (ReportRow row in reportResult.Rows)
+				{
+					ExportToExcelRow(reportResult, returnValue, row);
+				}
+			returnValue.AppendLine("</table>");
+
+			Html += returnValue.ToString();
+			Html += "</body>";
+			Html += "</html>";
+			return Html;
+		}
+		private static void ExportToExcelRow(ReportResult reportResult,
+			StringBuilder returnValue,
+			ReportRow row)
+		{
+			returnValue.AppendLine("<tr>");
+			returnValue.AppendLine(string.Join("", row.Columns.Select(s => string.Format("<td>{0}</td>", s.Name)).ToArray()));
+			returnValue.AppendLine("</tr>");
+		}
+	}
+
+}

+ 44 - 0
MediaBrowser.Api/Reports/Data/ReportGroup.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+
+	/// <summary> A report group. </summary>
+	public class ReportGroup
+	{
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportGroup class. </summary>
+		public ReportGroup()
+		{
+			Rows = new List<ReportRow>();
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportGroup class. </summary>
+		/// <param name="rows"> The rows. </param>
+		public ReportGroup(List<ReportRow> rows)
+		{
+			Rows = rows;
+		}
+
+		/// <summary> Gets or sets the name. </summary>
+		/// <value> The name. </value>
+		public string Name { get; set; }
+
+		/// <summary> Gets or sets the rows. </summary>
+		/// <value> The rows. </value>
+		public List<ReportRow> Rows { get; set; }
+
+		/// <summary> Returns a string that represents the current object. </summary>
+		/// <returns> A string that represents the current object. </returns>
+		/// <seealso cref="M:System.Object.ToString()"/>
+		public override string ToString()
+		{
+			return Name;
+		}
+	}
+}

+ 54 - 0
MediaBrowser.Api/Reports/Data/ReportHeader.cs

@@ -0,0 +1,54 @@
+using MediaBrowser.Controller.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report header. </summary>
+	public class ReportHeader
+	{
+		/// <summary> Initializes a new instance of the ReportHeader class. </summary>
+		public ReportHeader()
+		{
+			ItemViewType = ItemViewType.None;
+			Visible = true;
+			CanGroup = true;
+		}
+
+		/// <summary> Gets or sets the type of the header field. </summary>
+		/// <value> The type of the header field. </value>
+		public ReportFieldType HeaderFieldType { get; set; }
+
+		/// <summary> Gets or sets the name of the header. </summary>
+		/// <value> The name of the header. </value>
+		public string Name { get; set; }
+
+		/// <summary> Gets or sets the name of the field. </summary>
+		/// <value> The name of the field. </value>
+		public HeaderMetadata FieldName { get; set; }
+
+		/// <summary> Gets or sets the sort field. </summary>
+		/// <value> The sort field. </value>
+		public string SortField { get; set; }
+
+		/// <summary> Gets or sets the type. </summary>
+		/// <value> The type. </value>
+		public string Type { get; set; }
+
+		/// <summary> Gets or sets the type of the item view. </summary>
+		/// <value> The type of the item view. </value>
+		public ItemViewType ItemViewType { get; set; }
+
+		/// <summary> Gets or sets a value indicating whether this object is visible. </summary>
+		/// <value> true if visible, false if not. </value>
+		public bool Visible { get; set; }
+
+		/// <summary> Gets or sets a value indicating whether we can group. </summary>
+		/// <value> true if we can group, false if not. </value>
+		public bool CanGroup { get; set; }
+
+	}
+}

+ 34 - 0
MediaBrowser.Api/Reports/Data/ReportItem.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report item. </summary>
+	public class ReportItem
+	{
+		/// <summary> Gets or sets the identifier. </summary>
+		/// <value> The identifier. </value>
+		public string Id { get; set; }
+
+		/// <summary> Gets or sets the name. </summary>
+		/// <value> The name. </value>
+		public string Name { get; set; }
+
+		public string Image { get; set; }
+
+		/// <summary> Gets or sets the custom tag. </summary>
+		/// <value> The custom tag. </value>
+		public string CustomTag { get; set; }
+
+		/// <summary> Returns a string that represents the current object. </summary>
+		/// <returns> A string that represents the current object. </returns>
+		/// <seealso cref="M:System.Object.ToString()"/>
+		public override string ToString()
+		{
+			return Name;
+		}
+	}
+}

+ 52 - 0
MediaBrowser.Api/Reports/Data/ReportOptions.cs

@@ -0,0 +1,52 @@
+using MediaBrowser.Controller.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report options. </summary>
+	internal class ReportOptions<I>
+	{
+		/// <summary> Initializes a new instance of the ReportOptions class. </summary>
+		public ReportOptions()
+		{
+		}
+
+		/// <summary> Initializes a new instance of the ReportOptions class. </summary>
+		/// <param name="header"> . </param>
+		/// <param name="row"> . </param>
+		public ReportOptions(ReportHeader header, Func<I, ReportRow, object> column)
+		{
+			Header = header;
+			Column = column;
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the ReportOptions class.
+		/// </summary>
+		/// <param name="header"></param>
+		/// <param name="column"></param>
+		/// <param name="itemID"></param>
+		public ReportOptions(ReportHeader header, Func<I, ReportRow, object> column, Func<I, object> itemID)
+		{
+			Header = header;
+			Column = column;
+			ItemID = itemID;
+		}
+
+		/// <summary> Gets or sets the header. </summary>
+		/// <value> The header. </value>
+		public ReportHeader Header { get; set; }
+
+		/// <summary> Gets or sets the column. </summary>
+		/// <value> The column. </value>
+		public Func<I, ReportRow, object> Column { get; set; }
+
+		/// <summary> Gets or sets the identifier of the item. </summary>
+		/// <value> The identifier of the item. </value>
+		public Func<I, object> ItemID { get; set; }
+	}
+}

+ 53 - 0
MediaBrowser.Api/Reports/Data/ReportResult.cs

@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Api.Reports
+{
+
+	/// <summary> Encapsulates the result of a report. </summary>
+	public class ReportResult
+	{
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportResult class. </summary>
+		public ReportResult()
+		{
+			Rows = new List<ReportRow>();
+			Headers = new List<ReportHeader>();
+			Groups = new List<ReportGroup>();
+			TotalRecordCount = 0;
+			IsGrouped = false;
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportResult class. </summary>
+		/// <param name="headers"> The headers. </param>
+		/// <param name="rows"> The rows. </param>
+		public ReportResult(List<ReportHeader> headers, List<ReportRow> rows)
+		{
+			Rows = rows;
+			Headers = headers;
+			TotalRecordCount = 0;
+		}
+
+		/// <summary> Gets or sets the rows. </summary>
+		/// <value> The rows. </value>
+		public List<ReportRow> Rows { get; set; }
+
+		/// <summary> Gets or sets the headers. </summary>
+		/// <value> The headers. </value>
+		public List<ReportHeader> Headers { get; set; }
+
+		/// <summary> Gets or sets the groups. </summary>
+		/// <value> The groups. </value>
+		public List<ReportGroup> Groups { get; set; }
+
+
+		/// <summary> Gets or sets the number of total records. </summary>
+		/// <value> The total number of record count. </value>
+		public int TotalRecordCount { get; set; }
+
+		/// <summary> Gets or sets the is grouped. </summary>
+		/// <value> The is grouped. </value>
+		public bool IsGrouped { get; set; }
+
+	}
+}

+ 71 - 0
MediaBrowser.Api/Reports/Data/ReportRow.cs

@@ -0,0 +1,71 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	public class ReportRow
+	{
+		/// <summary>
+		/// Initializes a new instance of the ReportRow class.
+		/// </summary>
+		public ReportRow()
+		{
+			Columns = new List<ReportItem>();
+		}
+
+		/// <summary> Gets or sets the identifier. </summary>
+		/// <value> The identifier. </value>
+		public string Id { get; set; }
+
+		/// <summary>
+		/// Gets or sets a value indicating whether this object has backdrop image. </summary>
+		/// <value> true if this object has backdrop image, false if not. </value>
+		public bool HasImageTagsBackdrop { get; set; }
+
+		/// <summary> Gets or sets a value indicating whether this object has image tags. </summary>
+		/// <value> true if this object has image tags, false if not. </value>
+		public bool HasImageTagsPrimary { get; set; }
+
+		/// <summary>
+		/// Gets or sets a value indicating whether this object has image tags logo. </summary>
+		/// <value> true if this object has image tags logo, false if not. </value>
+		public bool HasImageTagsLogo { get; set; }
+
+		/// <summary>
+		/// Gets or sets a value indicating whether this object has local trailer. </summary>
+		/// <value> true if this object has local trailer, false if not. </value>
+		public bool HasLocalTrailer { get; set; }
+
+		/// <summary> Gets or sets a value indicating whether this object has lock data. </summary>
+		/// <value> true if this object has lock data, false if not. </value>
+		public bool HasLockData { get; set; }
+
+		/// <summary>
+		/// Gets or sets a value indicating whether this object has embedded image. </summary>
+		/// <value> true if this object has embedded image, false if not. </value>
+		public bool HasEmbeddedImage { get; set; }
+
+		/// <summary> Gets or sets a value indicating whether this object has subtitles. </summary>
+		/// <value> true if this object has subtitles, false if not. </value>
+		public bool HasSubtitles { get; set; }
+
+		/// <summary> Gets or sets a value indicating whether this object has specials. </summary>
+		/// <value> true if this object has specials, false if not. </value>
+		public bool HasSpecials { get; set; }
+
+		/// <summary> Gets or sets a value indicating whether this object is unidentified. </summary>
+		/// <value> true if this object is unidentified, false if not. </value>
+		public bool IsUnidentified { get; set; }
+
+		/// <summary> Gets or sets the columns. </summary>
+		/// <value> The columns. </value>
+		public List<ReportItem> Columns { get; set; }
+
+		/// <summary> Gets or sets the type. </summary>
+		/// <value> The type. </value>
+		public ReportViewType RowType { get; set; }
+	}
+}

+ 0 - 9
MediaBrowser.Api/Reports/ReportFieldType.cs

@@ -1,9 +0,0 @@
-
-namespace MediaBrowser.Api.Reports
-{
-    public enum ReportFieldType
-    {
-        String,
-        Boolean
-    }
-}

+ 41 - 29
MediaBrowser.Api/Reports/ReportRequests.cs

@@ -1,33 +1,45 @@
-using ServiceStack;
+using MediaBrowser.Api.UserLibrary;
+using MediaBrowser.Controller.Net;
+using ServiceStack;
+using System.Collections.Generic;
 
 namespace MediaBrowser.Api.Reports
 {
-    public class BaseReportRequest : IReturn<ReportResult>
-    {
-        /// <summary>
-        /// Specify this to localize the search to a specific item or folder. Omit to use the root.
-        /// </summary>
-        /// <value>The parent id.</value>
-        [ApiMember(Name = "ParentId", Description = "Specify this to localize the search to a specific item or folder. Omit to use the root", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public string ParentId { get; set; }
-
-        /// <summary>
-        /// Skips over a given number of items within the results. Use for paging.
-        /// </summary>
-        /// <value>The start index.</value>
-        [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? StartIndex { get; set; }
-
-        /// <summary>
-        /// The maximum number of items to return
-        /// </summary>
-        /// <value>The limit.</value>
-        [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")]
-        public int? Limit { get; set; }
-    }
-
-    [Route("/Reports/Items", "GET", Summary = "Gets reports based on library items")]
-    public class GetItemReport : BaseReportRequest
-    {
-    }
+	public class BaseReportRequest : GetItems
+	{
+		public bool HasQueryLimit { get; set; }
+		public string GroupBy { get; set; }
+
+		public string ReportColumns { get; set; }
+	}
+
+	[Route("/Reports/Items", "GET", Summary = "Gets reports based on library items")]
+	public class GetItemReport : BaseReportRequest, IReturn<ReportResult>
+	{
+
+	}
+
+	[Route("/Reports/Headers", "GET", Summary = "Gets reports headers based on library items")]
+	public class GetReportHeaders : BaseReportRequest, IReturn<List<ReportHeader>>
+	{
+	}
+
+	[Route("/Reports/Statistics", "GET", Summary = "Gets reports statistics based on library items")]
+	public class GetReportStatistics : BaseReportRequest, IReturn<ReportStatResult>
+	{
+		public int? TopItems { get; set; }
+
+	}
+
+	[Route("/Reports/Items/Download", "GET", Summary = "Downloads report")]
+	public class GetReportDownload : BaseReportRequest
+	{
+		public GetReportDownload()
+		{
+			ExportType = ReportExportType.CSV;
+		}
+
+		public ReportExportType ExportType { get; set; }
+	}
+
 }

+ 0 - 16
MediaBrowser.Api/Reports/ReportResult.cs

@@ -1,16 +0,0 @@
-using System.Collections.Generic;
-
-namespace MediaBrowser.Api.Reports
-{
-    public class ReportResult
-    {
-        public List<List<string>> Rows { get; set; }
-        public List<ReportFieldType> Columns { get; set; }
-
-        public ReportResult()
-        {
-            Rows = new List<List<string>>();
-            Columns = new List<ReportFieldType>();
-        }
-    }
-}

+ 1141 - 42
MediaBrowser.Api/Reports/ReportsService.cs

@@ -1,64 +1,1163 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Querying;
+using System.Collections.Generic;
 using System.Threading.Tasks;
+using System.Globalization;
+using System.Linq;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Api.UserLibrary;
+using MediaBrowser.Controller.Collections;
+using MediaBrowser.Controller.Entities.TV;
+using System;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Controller.Net;
+using MediaBrowser.Model.Activity;
+using MediaBrowser.Controller.Activity;
+using System.IO;
+using System.Text;
 
 namespace MediaBrowser.Api.Reports
 {
-    public class ReportsService : BaseApiService
-    {
-        private readonly ILibraryManager _libraryManager;
+	/// <summary> The reports service. </summary>
+	/// <seealso cref="T:MediaBrowser.Api.BaseApiService"/>
+	public class ReportsService : BaseApiService
+	{
 
-        public ReportsService(ILibraryManager libraryManager)
-        {
-            _libraryManager = libraryManager;
-        }
 
-        public async Task<object> Get(GetItemReport request)
-        {
-            var queryResult = await GetQueryResult(request).ConfigureAwait(false);
+		/// <summary> Manager for user. </summary>
+		private readonly IUserManager _userManager;
 
-            var reportResult = GetReportResult(queryResult);
+		/// <summary> Manager for library. </summary>
+		private readonly ILibraryManager _libraryManager;
+		/// <summary> The localization. </summary>
+		private readonly ILocalizationManager _localization;
 
-            return ToOptimizedResult(reportResult);
-        }
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. </summary>
+		/// <param name="userManager"> Manager for user. </param>
+		/// <param name="libraryManager"> Manager for library. </param>
+		/// <param name="localization"> The localization. </param>
+		public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization)
+		{
+			_userManager = userManager;
+			_libraryManager = libraryManager;
+			_localization = localization;
+		}
 
-        private ReportResult GetReportResult(QueryResult<BaseItem> queryResult)
-        {
-            var reportResult = new ReportResult();
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetReportHeaders request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
 
-            // Fill rows and columns
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
+			var reportResult = reportBuilder.GetReportHeaders(reportRowType, request);
 
-            return reportResult;
-        }
+			return ToOptimizedResult(reportResult);
 
-        private Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
-        {
-            // Placeholder in case needed later
-            User user = null;
+		}
 
-            var parentItem = string.IsNullOrEmpty(request.ParentId) ?
-                (user == null ? _libraryManager.RootFolder : user.RootFolder) :
-                _libraryManager.GetItemById(request.ParentId);
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetItemReport request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
 
-            return ((Folder)parentItem).GetItems(GetItemsQuery(request, user));
-        }
+			var reportResult = await GetReportResult(request);
 
-        private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
-        {
-            var query = new InternalItemsQuery
-            {
-                User = user,
-                CollapseBoxSetItems = false
-            };
+			return ToOptimizedResult(reportResult);
+		}
 
-            // Set query values based on request
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetReportDownload request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
 
-            // Example
-            //query.IncludeItemTypes = new[] {"Movie"};
+			var headers = new Dictionary<string, string>();
+			string fileExtension = "csv";
+			string contentType = "text/plain;charset='utf-8'";
 
+			switch (request.ExportType)
+			{
+				case ReportExportType.CSV:
+					break;
+				case ReportExportType.Excel:
+					contentType = "application/vnd.ms-excel";
+					fileExtension = "xls";
+					break;
+			}
 
-            return query;
-        }
-    }
+			var filename = "ReportExport." + fileExtension;
+			headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename);
+			headers["Content-Encoding"] = "UTF-8";
+
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
+			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+			ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request);
+
+			reportResult.TotalRecordCount = queryResult.TotalRecordCount;
+
+			string result = string.Empty;
+			switch (request.ExportType)
+			{
+				case ReportExportType.CSV:
+					result = new ReportExport().ExportToCsv(reportResult);
+					break;
+				case ReportExportType.Excel:
+					result = new ReportExport().ExportToExcel(reportResult);
+					break;
+			}
+
+			object ro = ResultFactory.GetResult(result, contentType, headers);
+			return ro;
+		}
+
+		/// <summary> Gets the given request. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> A Task&lt;object&gt; </returns>
+		public async Task<object> Get(GetReportStatistics request)
+		{
+			if (string.IsNullOrEmpty(request.IncludeItemTypes))
+				return null;
+			var reportResult = await GetReportStatistic(request);
+
+			return ToOptimizedResult(reportResult);
+		}
+
+		/// <summary> Gets report statistic. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> The report statistic. </returns>
+		private async Task<ReportStatResult> GetReportStatistic(GetReportStatistics request)
+		{
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+
+			ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager);
+			ReportStatResult reportResult = reportBuilder.GetReportStatResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5);
+			reportResult.TotalRecordCount = reportResult.Groups.Count();
+			return reportResult;
+		}
+
+		/// <summary> Gets report result. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> The report result. </returns>
+		private async Task<ReportResult> GetReportResult(GetItemReport request)
+		{
+
+			ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes);
+			ReportBuilder reportBuilder = new ReportBuilder(_libraryManager);
+			QueryResult<BaseItem> queryResult = await GetQueryResult(request).ConfigureAwait(false);
+			ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request);
+			reportResult.TotalRecordCount = queryResult.TotalRecordCount;
+
+			return reportResult;
+		}
+
+		/// <summary> Gets query result. </summary>
+		/// <param name="request"> The request. </param>
+		/// <returns> The query result. </returns>
+		private async Task<QueryResult<BaseItem>> GetQueryResult(BaseReportRequest request)
+		{
+			// Placeholder in case needed later
+			request.Recursive = true;
+			var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null;
+			request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts";
+
+			var parentItem = string.IsNullOrEmpty(request.ParentId) ?
+				(user == null ? _libraryManager.RootFolder : user.RootFolder) :
+				_libraryManager.GetItemById(request.ParentId);
+
+			var item = string.IsNullOrEmpty(request.ParentId) ?
+				user == null ? _libraryManager.RootFolder : user.RootFolder :
+				parentItem;
+
+			IEnumerable<BaseItem> items;
+
+			if (request.Recursive)
+			{
+				var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+				return result;
+			}
+			else
+			{
+				if (user == null)
+				{
+					var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
+					return result;
+				}
+
+				var userRoot = item as UserRootFolder;
+
+				if (userRoot == null)
+				{
+					var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
+
+					return result;
+				}
+
+				items = ((Folder)item).GetChildren(user, true);
+			}
+
+			return new QueryResult<BaseItem> { Items = items.ToArray() };
+
+		}
+
+		/// <summary> Gets items query. </summary>
+		/// <param name="request"> The request. </param>
+		/// <param name="user"> The user. </param>
+		/// <returns> The items query. </returns>
+		private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user)
+		{
+			var query = new InternalItemsQuery
+			{
+				User = user,
+				IsPlayed = request.IsPlayed,
+				MediaTypes = request.GetMediaTypes(),
+				IncludeItemTypes = request.GetIncludeItemTypes(),
+				ExcludeItemTypes = request.GetExcludeItemTypes(),
+				Recursive = true,
+				SortBy = request.GetOrderBy(),
+				SortOrder = request.SortOrder ?? SortOrder.Ascending,
+
+				Filter = i => ApplyAdditionalFilters(request, i, user, true, _libraryManager),
+				StartIndex = request.StartIndex,
+				IsMissing = request.IsMissing,
+				IsVirtualUnaired = request.IsVirtualUnaired,
+				IsUnaired = request.IsUnaired,
+				CollapseBoxSetItems = request.CollapseBoxSetItems,
+				NameLessThan = request.NameLessThan,
+				NameStartsWith = request.NameStartsWith,
+				NameStartsWithOrGreater = request.NameStartsWithOrGreater,
+				HasImdbId = request.HasImdbId,
+				IsYearMismatched = request.IsYearMismatched,
+				IsUnidentified = request.IsUnidentified,
+				IsPlaceHolder = request.IsPlaceHolder,
+				IsLocked = request.IsLocked,
+				IsInBoxSet = request.IsInBoxSet,
+				IsHD = request.IsHD,
+				Is3D = request.Is3D,
+				HasTvdbId = request.HasTvdbId,
+				HasTmdbId = request.HasTmdbId,
+				HasOverview = request.HasOverview,
+				HasOfficialRating = request.HasOfficialRating,
+				HasParentalRating = request.HasParentalRating,
+				HasSpecialFeature = request.HasSpecialFeature,
+				HasSubtitles = request.HasSubtitles,
+				HasThemeSong = request.HasThemeSong,
+				HasThemeVideo = request.HasThemeVideo,
+				HasTrailer = request.HasTrailer,
+				Tags = request.GetTags(),
+				OfficialRatings = request.GetOfficialRatings(),
+				Genres = request.GetGenres(),
+				Studios = request.GetStudios(),
+				StudioIds = request.GetStudioIds(),
+				Person = request.Person,
+				PersonIds = request.GetPersonIds(),
+				PersonTypes = request.GetPersonTypes(),
+				Years = request.GetYears(),
+				ImageTypes = request.GetImageTypes().ToArray(),
+				VideoTypes = request.GetVideoTypes().ToArray(),
+				AdjacentTo = request.AdjacentTo
+			};
+
+			if (!string.IsNullOrWhiteSpace(request.Ids))
+			{
+				query.CollapseBoxSetItems = false;
+			}
+
+			foreach (var filter in request.GetFilters())
+			{
+				switch (filter)
+				{
+					case ItemFilter.Dislikes:
+						query.IsLiked = false;
+						break;
+					case ItemFilter.IsFavorite:
+						query.IsFavorite = true;
+						break;
+					case ItemFilter.IsFavoriteOrLikes:
+						query.IsFavoriteOrLiked = true;
+						break;
+					case ItemFilter.IsFolder:
+						query.IsFolder = true;
+						break;
+					case ItemFilter.IsNotFolder:
+						query.IsFolder = false;
+						break;
+					case ItemFilter.IsPlayed:
+						query.IsPlayed = true;
+						break;
+					case ItemFilter.IsRecentlyAdded:
+						break;
+					case ItemFilter.IsResumable:
+						query.IsResumable = true;
+						break;
+					case ItemFilter.IsUnplayed:
+						query.IsPlayed = false;
+						break;
+					case ItemFilter.Likes:
+						query.IsLiked = true;
+						break;
+				}
+			}
+
+			if (request.HasQueryLimit)
+				query.Limit = request.Limit;
+			return query;
+		}
+
+		/// <summary> Applies filtering. </summary>
+		/// <param name="items"> The items. </param>
+		/// <param name="filter"> The filter. </param>
+		/// <param name="user"> The user. </param>
+		/// <param name="repository"> The repository. </param>
+		/// <returns> IEnumerable{BaseItem}. </returns>
+		internal static IEnumerable<BaseItem> ApplyFilter(IEnumerable<BaseItem> items, ItemFilter filter, User user, IUserDataManager repository)
+		{
+			// Avoid implicitly captured closure
+			var currentUser = user;
+
+			switch (filter)
+			{
+				case ItemFilter.IsFavoriteOrLikes:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						if (userdata == null)
+						{
+							return false;
+						}
+
+						var likes = userdata.Likes ?? false;
+						var favorite = userdata.IsFavorite;
+
+						return likes || favorite;
+					});
+
+				case ItemFilter.Likes:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value;
+					});
+
+				case ItemFilter.Dislikes:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value;
+					});
+
+				case ItemFilter.IsFavorite:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.IsFavorite;
+					});
+
+				case ItemFilter.IsResumable:
+					return items.Where(item =>
+					{
+						var userdata = repository.GetUserData(user.Id, item.GetUserDataKey());
+
+						return userdata != null && userdata.PlaybackPositionTicks > 0;
+					});
+
+				case ItemFilter.IsPlayed:
+					return items.Where(item => item.IsPlayed(currentUser));
+
+				case ItemFilter.IsUnplayed:
+					return items.Where(item => item.IsUnplayed(currentUser));
+
+				case ItemFilter.IsFolder:
+					return items.Where(item => item.IsFolder);
+
+				case ItemFilter.IsNotFolder:
+					return items.Where(item => !item.IsFolder);
+
+				case ItemFilter.IsRecentlyAdded:
+					return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10);
+			}
+
+			return items;
+		}
+
+		/// <summary> Applies the additional filters. </summary>
+		/// <param name="request"> The request. </param>
+		/// <param name="i"> Zero-based index of the. </param>
+		/// <param name="user"> The user. </param>
+		/// <param name="isPreFiltered"> true if this object is pre filtered. </param>
+		/// <param name="libraryManager"> Manager for library. </param>
+		/// <returns> true if it succeeds, false if it fails. </returns>
+		private bool ApplyAdditionalFilters(GetItems request, BaseItem i, User user, bool isPreFiltered, ILibraryManager libraryManager)
+		{
+			var video = i as Video;
+
+			if (!isPreFiltered)
+			{
+				var mediaTypes = request.GetMediaTypes();
+				if (mediaTypes.Length > 0)
+				{
+					if (!(!string.IsNullOrEmpty(i.MediaType) && mediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				if (request.IsPlayed.HasValue)
+				{
+					var val = request.IsPlayed.Value;
+					if (i.IsPlayed(user) != val)
+					{
+						return false;
+					}
+				}
+
+				// Exclude item types
+				var excluteItemTypes = request.GetExcludeItemTypes();
+				if (excluteItemTypes.Length > 0 && excluteItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+
+				// Include item types
+				var includeItemTypes = request.GetIncludeItemTypes();
+				if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+
+				if (request.IsInBoxSet.HasValue)
+				{
+					var val = request.IsInBoxSet.Value;
+					if (i.Parents.OfType<BoxSet>().Any() != val)
+					{
+						return false;
+					}
+				}
+
+				// Filter by Video3DFormat
+				if (request.Is3D.HasValue)
+				{
+					var val = request.Is3D.Value;
+
+					if (video == null || val != video.Video3DFormat.HasValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsHD.HasValue)
+				{
+					var val = request.IsHD.Value;
+
+					if (video == null || val != video.IsHD)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsUnidentified.HasValue)
+				{
+					var val = request.IsUnidentified.Value;
+					if (i.IsUnidentified != val)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsLocked.HasValue)
+				{
+					var val = request.IsLocked.Value;
+					if (i.IsLocked != val)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasOverview.HasValue)
+				{
+					var filterValue = request.HasOverview.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.Overview);
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasImdbId.HasValue)
+				{
+					var filterValue = request.HasImdbId.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb));
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasTmdbId.HasValue)
+				{
+					var filterValue = request.HasTmdbId.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb));
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasTvdbId.HasValue)
+				{
+					var filterValue = request.HasTvdbId.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb));
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsYearMismatched.HasValue)
+				{
+					var filterValue = request.IsYearMismatched.Value;
+
+					if (UserViewBuilder.IsYearMismatched(i, libraryManager) != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasOfficialRating.HasValue)
+				{
+					var filterValue = request.HasOfficialRating.Value;
+
+					var hasValue = !string.IsNullOrEmpty(i.OfficialRating);
+
+					if (hasValue != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.IsPlaceHolder.HasValue)
+				{
+					var filterValue = request.IsPlaceHolder.Value;
+
+					var isPlaceHolder = false;
+
+					var hasPlaceHolder = i as ISupportsPlaceHolders;
+
+					if (hasPlaceHolder != null)
+					{
+						isPlaceHolder = hasPlaceHolder.IsPlaceHolder;
+					}
+
+					if (isPlaceHolder != filterValue)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasSpecialFeature.HasValue)
+				{
+					var filterValue = request.HasSpecialFeature.Value;
+
+					var movie = i as IHasSpecialFeatures;
+
+					if (movie != null)
+					{
+						var ok = filterValue
+							? movie.SpecialFeatureIds.Count > 0
+							: movie.SpecialFeatureIds.Count == 0;
+
+						if (!ok)
+						{
+							return false;
+						}
+					}
+					else
+					{
+						return false;
+					}
+				}
+
+				if (request.HasSubtitles.HasValue)
+				{
+					var val = request.HasSubtitles.Value;
+
+					if (video == null || val != video.HasSubtitles)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasParentalRating.HasValue)
+				{
+					var val = request.HasParentalRating.Value;
+
+					var rating = i.CustomRating;
+
+					if (string.IsNullOrEmpty(rating))
+					{
+						rating = i.OfficialRating;
+					}
+
+					if (val)
+					{
+						if (string.IsNullOrEmpty(rating))
+						{
+							return false;
+						}
+					}
+					else
+					{
+						if (!string.IsNullOrEmpty(rating))
+						{
+							return false;
+						}
+					}
+				}
+
+				if (request.HasTrailer.HasValue)
+				{
+					var val = request.HasTrailer.Value;
+					var trailerCount = 0;
+
+					var hasTrailers = i as IHasTrailers;
+					if (hasTrailers != null)
+					{
+						trailerCount = hasTrailers.GetTrailerIds().Count;
+					}
+
+					var ok = val ? trailerCount > 0 : trailerCount == 0;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasThemeSong.HasValue)
+				{
+					var filterValue = request.HasThemeSong.Value;
+
+					var themeCount = 0;
+					var iHasThemeMedia = i as IHasThemeMedia;
+
+					if (iHasThemeMedia != null)
+					{
+						themeCount = iHasThemeMedia.ThemeSongIds.Count;
+					}
+					var ok = filterValue ? themeCount > 0 : themeCount == 0;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+
+				if (request.HasThemeVideo.HasValue)
+				{
+					var filterValue = request.HasThemeVideo.Value;
+
+					var themeCount = 0;
+					var iHasThemeMedia = i as IHasThemeMedia;
+
+					if (iHasThemeMedia != null)
+					{
+						themeCount = iHasThemeMedia.ThemeVideoIds.Count;
+					}
+					var ok = filterValue ? themeCount > 0 : themeCount == 0;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+
+				// Apply tag filter
+				var tags = request.GetTags();
+				if (tags.Length > 0)
+				{
+					var hasTags = i as IHasTags;
+					if (hasTags == null)
+					{
+						return false;
+					}
+					if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase))))
+					{
+						return false;
+					}
+				}
+
+				// Apply official rating filter
+				var officialRatings = request.GetOfficialRatings();
+				if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty))
+				{
+					return false;
+				}
+
+				// Apply genre filter
+				var genres = request.GetGenres();
+				if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))))
+				{
+					return false;
+				}
+
+				// Filter by VideoType
+				var videoTypes = request.GetVideoTypes();
+				if (videoTypes.Length > 0 && (video == null || !videoTypes.Contains(video.VideoType)))
+				{
+					return false;
+				}
+
+				var imageTypes = request.GetImageTypes().ToList();
+				if (imageTypes.Count > 0)
+				{
+					if (!(imageTypes.Any(i.HasImage)))
+					{
+						return false;
+					}
+				}
+
+				// Apply studio filter
+				var studios = request.GetStudios();
+				if (studios.Length > 0 && !studios.Any(v => i.Studios.Contains(v, StringComparer.OrdinalIgnoreCase)))
+				{
+					return false;
+				}
+
+				// Apply studio filter
+				var studioIds = request.GetStudioIds();
+				if (studioIds.Length > 0 && !studioIds.Any(id =>
+				{
+					var studioItem = libraryManager.GetItemById(id);
+					return studioItem != null && i.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase);
+				}))
+				{
+					return false;
+				}
+
+				// Apply year filter
+				var years = request.GetYears();
+				if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value)))
+				{
+					return false;
+				}
+
+				// Apply person filter
+				var personIds = request.GetPersonIds();
+				if (personIds.Length > 0)
+				{
+					var names = personIds
+						.Select(libraryManager.GetItemById)
+						.Select(p => p == null ? "-1" : p.Name)
+						.ToList();
+
+					if (!(names.Any(v => i.People.Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase))))
+					{
+						return false;
+					}
+				}
+
+				// Apply person filter
+				if (!string.IsNullOrEmpty(request.Person))
+				{
+					var personTypes = request.GetPersonTypes();
+
+					if (personTypes.Length == 0)
+					{
+						if (!(i.People.Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase))))
+						{
+							return false;
+						}
+					}
+					else
+					{
+						var types = personTypes;
+
+						var ok = new[] { i }.Any(item =>
+								item.People != null &&
+								item.People.Any(p =>
+									p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase))));
+
+						if (!ok)
+						{
+							return false;
+						}
+					}
+				}
+			}
+
+			if (request.MinCommunityRating.HasValue)
+			{
+				var val = request.MinCommunityRating.Value;
+
+				if (!(i.CommunityRating.HasValue && i.CommunityRating >= val))
+				{
+					return false;
+				}
+			}
+
+			if (request.MinCriticRating.HasValue)
+			{
+				var val = request.MinCriticRating.Value;
+
+				var hasCriticRating = i as IHasCriticRating;
+
+				if (hasCriticRating != null)
+				{
+					if (!(hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val))
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			// Artists
+			if (!string.IsNullOrEmpty(request.ArtistIds))
+			{
+				var artistIds = request.ArtistIds.Split('|');
+
+				var audio = i as IHasArtist;
+
+				if (!(audio != null && artistIds.Any(id =>
+				{
+					var artistItem = libraryManager.GetItemById(id);
+					return artistItem != null && audio.HasAnyArtist(artistItem.Name);
+				})))
+				{
+					return false;
+				}
+			}
+
+			// Artists
+			if (!string.IsNullOrEmpty(request.Artists))
+			{
+				var artists = request.Artists.Split('|');
+
+				var audio = i as IHasArtist;
+
+				if (!(audio != null && artists.Any(audio.HasAnyArtist)))
+				{
+					return false;
+				}
+			}
+
+			// Albums
+			if (!string.IsNullOrEmpty(request.Albums))
+			{
+				var albums = request.Albums.Split('|');
+
+				var audio = i as Audio;
+
+				if (audio != null)
+				{
+					if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				var album = i as MusicAlbum;
+
+				if (album != null)
+				{
+					if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				var musicVideo = i as MusicVideo;
+
+				if (musicVideo != null)
+				{
+					if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase)))
+					{
+						return false;
+					}
+				}
+
+				return false;
+			}
+
+			// Min index number
+			if (request.MinIndexNumber.HasValue)
+			{
+				if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value))
+				{
+					return false;
+				}
+			}
+
+			// Min official rating
+			if (!string.IsNullOrEmpty(request.MinOfficialRating))
+			{
+				var level = _localization.GetRatingLevel(request.MinOfficialRating);
+
+				if (level.HasValue)
+				{
+					var rating = i.CustomRating;
+
+					if (string.IsNullOrEmpty(rating))
+					{
+						rating = i.OfficialRating;
+					}
+
+					if (!string.IsNullOrEmpty(rating))
+					{
+						var itemLevel = _localization.GetRatingLevel(rating);
+
+						if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value))
+						{
+							return false;
+						}
+					}
+				}
+			}
+
+			// Max official rating
+			if (!string.IsNullOrEmpty(request.MaxOfficialRating))
+			{
+				var level = _localization.GetRatingLevel(request.MaxOfficialRating);
+
+				if (level.HasValue)
+				{
+					var rating = i.CustomRating;
+
+					if (string.IsNullOrEmpty(rating))
+					{
+						rating = i.OfficialRating;
+					}
+
+					if (!string.IsNullOrEmpty(rating))
+					{
+						var itemLevel = _localization.GetRatingLevel(rating);
+
+						if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value))
+						{
+							return false;
+						}
+					}
+				}
+			}
+
+			// LocationTypes
+			if (!string.IsNullOrEmpty(request.LocationTypes))
+			{
+				var vals = request.LocationTypes.Split(',');
+				if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+			}
+
+			// ExcludeLocationTypes
+			if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
+			{
+				var vals = request.ExcludeLocationTypes.Split(',');
+				if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
+				{
+					return false;
+				}
+			}
+
+			if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater))
+			{
+				var ok = new[] { i }.OfType<IHasAlbumArtist>()
+					.Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1);
+
+				if (!ok)
+				{
+					return false;
+				}
+			}
+
+			// Filter by Series Status
+			if (!string.IsNullOrEmpty(request.SeriesStatus))
+			{
+				var vals = request.SeriesStatus.Split(',');
+
+				var ok = new[] { i }.OfType<Series>().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase));
+
+				if (!ok)
+				{
+					return false;
+				}
+			}
+
+			// Filter by Series AirDays
+			if (!string.IsNullOrEmpty(request.AirDays))
+			{
+				var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true));
+
+				var ok = new[] { i }.OfType<Series>().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d)));
+
+				if (!ok)
+				{
+					return false;
+				}
+			}
+
+			if (request.MinPlayers.HasValue)
+			{
+				var filterValue = request.MinPlayers.Value;
+
+				var game = i as Game;
+
+				if (game != null)
+				{
+					var players = game.PlayersSupported ?? 1;
+
+					var ok = players >= filterValue;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			if (request.MaxPlayers.HasValue)
+			{
+				var filterValue = request.MaxPlayers.Value;
+
+				var game = i as Game;
+
+				if (game != null)
+				{
+					var players = game.PlayersSupported ?? 1;
+
+					var ok = players <= filterValue;
+
+					if (!ok)
+					{
+						return false;
+					}
+				}
+				else
+				{
+					return false;
+				}
+			}
+
+			if (request.ParentIndexNumber.HasValue)
+			{
+				var filterValue = request.ParentIndexNumber.Value;
+
+				var episode = i as Episode;
+
+				if (episode != null)
+				{
+					if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue)
+					{
+						return false;
+					}
+				}
+
+				var song = i as Audio;
+
+				if (song != null)
+				{
+					if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue)
+					{
+						return false;
+					}
+				}
+			}
+
+			if (request.AiredDuringSeason.HasValue)
+			{
+				var episode = i as Episode;
+
+				if (episode == null)
+				{
+					return false;
+				}
+
+				if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any())
+				{
+					return false;
+				}
+			}
+
+			if (!string.IsNullOrEmpty(request.MinPremiereDate))
+			{
+				var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+
+				if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date))
+				{
+					return false;
+				}
+			}
+
+			if (!string.IsNullOrEmpty(request.MaxPremiereDate))
+			{
+				var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime();
+
+				if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date))
+				{
+					return false;
+				}
+			}
+
+			return true;
+		}
+
+		/// <summary> Applies the paging. </summary>
+		/// <param name="request"> The request. </param>
+		/// <param name="items"> The items. </param>
+		/// <returns> IEnumerable{BaseItem}. </returns>
+		private IEnumerable<BaseItem> ApplyPaging(GetItems request, IEnumerable<BaseItem> items)
+		{
+			// Start at
+			if (request.StartIndex.HasValue)
+			{
+				items = items.Skip(request.StartIndex.Value);
+			}
+
+			// Return limit
+			if (request.Limit.HasValue)
+			{
+				items = items.Take(request.Limit.Value);
+			}
+
+			return items;
+		}
+
+	}
 }

+ 214 - 0
MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs

@@ -0,0 +1,214 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report stat builder. </summary>
+	/// <seealso cref="T:MediaBrowser.Api.Reports.ReportBuilderBase"/>
+	public class ReportStatBuilder : ReportBuilderBase
+	{
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. </summary>
+		/// <param name="libraryManager"> Manager for library. </param>
+		public ReportStatBuilder(ILibraryManager libraryManager)
+			: base(libraryManager)
+		{
+		}
+
+		/// <summary> Gets report stat result. </summary>
+		/// <param name="items"> The items. </param>
+		/// <param name="reportRowType"> Type of the report row. </param>
+		/// <param name="topItem"> The top item. </param>
+		/// <returns> The report stat result. </returns>
+		public ReportStatResult GetReportStatResult(BaseItem[] items, ReportViewType reportRowType, int topItem = 5)
+		{
+			ReportStatResult result = new ReportStatResult();
+			result = this.GetResultGenres(result, items, topItem);
+			result = this.GetResultStudios(result, items, topItem);
+			result = this.GetResultPersons(result, items, topItem);
+			result = this.GetResultProductionYears(result, items, topItem);
+			result = this.GetResulProductionLocations(result, items, topItem);
+			result = this.GetResultCommunityRatings(result, items, topItem);
+			result = this.GetResultParentalRatings(result, items, topItem);
+
+			switch (reportRowType)
+			{
+				case ReportViewType.Season:
+				case ReportViewType.Series:
+				case ReportViewType.MusicAlbum:
+				case ReportViewType.MusicArtist:
+				case ReportViewType.Game:
+					break;
+				case ReportViewType.Movie:
+				case ReportViewType.BoxSet:
+
+					break;
+				case ReportViewType.Book:
+				case ReportViewType.Episode:
+				case ReportViewType.Video:
+				case ReportViewType.MusicVideo:
+				case ReportViewType.Trailer:
+				case ReportViewType.Audio:
+				case ReportViewType.BaseItem:
+				default:
+					break;
+			}
+
+			result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList();
+
+			return result;
+		}
+
+		private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderGenres"), topItem,
+							items.SelectMany(x => x.Genres)
+								.GroupBy(x => x)
+								.OrderByDescending(x => x.Count())
+								.Take(topItem)
+								.Select(x => new ReportStatItem
+								{
+									Name = x.Key,
+									Value = x.Count().ToString(),
+									Id = GetGenreID(x.Key)
+								}));
+			return result;
+
+		}
+
+		private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderStudios"), topItem,
+									items.SelectMany(x => x.Studios)
+										.GroupBy(x => x)
+										.OrderByDescending(x => x.Count())
+										.Take(topItem)
+										.Select(x => new ReportStatItem
+										{
+											Name = x.Key,
+											Value = x.Count().ToString(),
+											Id = GetStudioID(x.Key)
+										})
+					);
+
+			return result;
+
+		}
+
+		private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			List<string> t = new List<string> { PersonType.Actor, PersonType.Composer, PersonType.Director, PersonType.GuestStar, PersonType.Producer, PersonType.Writer, "Artist", "AlbumArtist" };
+			foreach (var item in t)
+			{
+				this.GetGroups(result, ReportHelper.GetServerLocalizedString("Option" + item), topItem,
+						items.SelectMany(x => x.People)
+								.Where(n => n.Type == item)
+								.GroupBy(x => x.Name)
+								.OrderByDescending(x => x.Count())
+								.Take(topItem)
+								.Select(x => new ReportStatItem
+								{
+									Name = x.Key,
+									Value = x.Count().ToString(),
+									Id = GetPersonID(x.Key)
+								})
+				);
+			}
+
+			return result;
+		}
+
+		private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("LabelCommunityRating"), topItem,
+					   items.Where(x => x.CommunityRating != null && x.CommunityRating > 0)
+						   .GroupBy(x => x.CommunityRating)
+						   .OrderByDescending(x => x.Count())
+						   .Take(topItem)
+						   .Select(x => new ReportStatItem
+						   {
+							   Name = x.Key.ToString(),
+							   Value = x.Count().ToString()
+						   })
+			   );
+
+			return result;
+		}
+
+		private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderParentalRatings"), topItem,
+					   items.Where(x => x.OfficialRating != null)
+						   .GroupBy(x => x.OfficialRating)
+						   .OrderByDescending(x => x.Count())
+						   .Take(topItem)
+						   .Select(x => new ReportStatItem
+						   {
+							   Name = x.Key.ToString(),
+							   Value = x.Count().ToString()
+						   })
+			   );
+
+			return result;
+		}
+
+
+		private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderYears"), topItem,
+					items.Where(x => x.ProductionYear != null && x.ProductionYear > 0)
+						.GroupBy(x => x.ProductionYear)
+						.OrderByDescending(x => x.Count())
+						.Take(topItem)
+						.Select(x => new ReportStatItem
+						{
+							Name = x.Key.ToString(),
+							Value = x.Count().ToString()
+						})
+			);
+
+			return result;
+		}
+
+		private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5)
+		{
+			this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderCountries"), topItem,
+						items.OfType<IHasProductionLocations>()
+						.Where(x => x.ProductionLocations != null)
+						.SelectMany(x => x.ProductionLocations)
+						.GroupBy(x => x)
+						.OrderByDescending(x => x.Count())
+						.Take(topItem)
+						.Select(x => new ReportStatItem
+						{
+							Name = x.Key.ToString(),
+							Value = x.Count().ToString()
+						})
+			);
+
+			return result;
+		}
+
+
+		/// <summary> Gets the groups. </summary>
+		/// <param name="result"> The result. </param>
+		/// <param name="header"> The header. </param>
+		/// <param name="topItem"> The top item. </param>
+		/// <param name="top"> The top. </param>
+		private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable<ReportStatItem> top)
+		{
+			if (top.Count() > 0)
+			{
+				var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) };
+				group.Items.AddRange(top);
+				result.Groups.Add(group);
+			}
+		}
+	}
+}

+ 37 - 0
MediaBrowser.Api/Reports/Stat/ReportStatGroup.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report stat group. </summary>
+	public class ReportStatGroup
+	{
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatGroup class. </summary>
+		public ReportStatGroup()
+		{
+			Items = new List<ReportStatItem>();
+			TotalRecordCount = 0;
+		}
+
+		/// <summary> Gets or sets the header. </summary>
+		/// <value> The header. </value>
+		public string Header { get; set; }
+
+		/// <summary> Gets or sets the items. </summary>
+		/// <value> The items. </value>
+		public List<ReportStatItem> Items { get; set; }
+
+		/// <summary> Gets or sets the number of total records. </summary>
+		/// <value> The total number of record count. </value>
+		public int TotalRecordCount { get; set; }
+
+		internal static string FormatedHeader(string header, int topItem)
+		{
+			return string.Format("Top {0} {1}", topItem, header);
+		}
+	}
+}

+ 29 - 0
MediaBrowser.Api/Reports/Stat/ReportStatItem.cs

@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> A report stat item. </summary>
+	public class ReportStatItem
+	{
+		/// <summary> Gets or sets the name. </summary>
+		/// <value> The name. </value>
+		public string Name { get; set; }
+
+		/// <summary> Gets or sets the image. </summary>
+		/// <value> The image. </value>
+		public string Image { get; set; }
+
+		/// <summary> Gets or sets the value. </summary>
+		/// <value> The value. </value>
+		public string Value { get; set; }
+
+		/// <summary> Gets or sets the identifier. </summary>
+		/// <value> The identifier. </value>
+		public string Id { get; set; }
+
+	}
+}

+ 28 - 0
MediaBrowser.Api/Reports/Stat/ReportStatResult.cs

@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.Reports
+{
+	/// <summary> Encapsulates the result of a report stat. </summary>
+	public class ReportStatResult 
+	{
+		/// <summary>
+		/// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatResult class. </summary>
+		public ReportStatResult()
+		{
+			Groups = new List<ReportStatGroup>();
+			TotalRecordCount = 0;
+		}
+
+		/// <summary> Gets or sets the groups. </summary>
+		/// <value> The groups. </value>
+		public List<ReportStatGroup> Groups { get; set; }
+
+		/// <summary> Gets or sets the number of total records. </summary>
+		/// <value> The total number of record count. </value>
+		public int TotalRecordCount { get; set; }	
+	}
+}

+ 6 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -1448,5 +1448,10 @@
     "LabelServerPort": "Port:",
     "HeaderNewServer": "New Server",
     "ButtonChangeServer": "Change Server",
-    "HeaderConnectToServer": "Connect to Server"
+    "HeaderConnectToServer": "Connect to Server",
+    "OptionReportList": "List View",
+    "OptionReportStatistics": "Statistics",
+    "OptionReportGrouping": "Grouping",
+    "OptionReportExport": "Report Export",
+    "OptionReportColumns": "Report Columns"
 }

+ 1 - 1
MediaBrowser.WebDashboard/Api/PackageCreator.cs

@@ -472,7 +472,7 @@ namespace MediaBrowser.WebDashboard.Api
                                 "itemlistpage.js",
                                 "kids.js",
                                 "librarypathmapping.js",
-                                "reports.js",
+                                "reportmanager.js",
                                 "librarysettings.js",
                                 "livetvchannel.js",
                                 "livetvchannels.js",

+ 6 - 0
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -138,6 +138,9 @@
     <Content Include="dashboard-ui\photos.html">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\ReportManager.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\dashboardhosting.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -156,6 +159,9 @@
     <Content Include="dashboard-ui\scripts\photos.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
+    <Content Include="dashboard-ui\scripts\reportmanager.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
     <Content Include="dashboard-ui\scripts\selectserver.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>