ControlHandler.cs 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Threading;
  9. using System.Xml;
  10. using Emby.Dlna.Configuration;
  11. using Emby.Dlna.Didl;
  12. using Emby.Dlna.Service;
  13. using Jellyfin.Data.Entities;
  14. using Jellyfin.Data.Enums;
  15. using MediaBrowser.Common.Extensions;
  16. using MediaBrowser.Controller.Configuration;
  17. using MediaBrowser.Controller.Drawing;
  18. using MediaBrowser.Controller.Dto;
  19. using MediaBrowser.Controller.Entities;
  20. using MediaBrowser.Controller.Entities.Audio;
  21. using MediaBrowser.Controller.Entities.Movies;
  22. using MediaBrowser.Controller.Library;
  23. using MediaBrowser.Controller.LiveTv;
  24. using MediaBrowser.Controller.MediaEncoding;
  25. using MediaBrowser.Controller.Playlists;
  26. using MediaBrowser.Controller.TV;
  27. using MediaBrowser.Model.Dlna;
  28. using MediaBrowser.Model.Entities;
  29. using MediaBrowser.Model.Globalization;
  30. using MediaBrowser.Model.Querying;
  31. using Microsoft.Extensions.Logging;
  32. using Book = MediaBrowser.Controller.Entities.Book;
  33. using Episode = MediaBrowser.Controller.Entities.TV.Episode;
  34. using Genre = MediaBrowser.Controller.Entities.Genre;
  35. using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
  36. using MusicAlbum = MediaBrowser.Controller.Entities.Audio.MusicAlbum;
  37. using Series = MediaBrowser.Controller.Entities.TV.Series;
  38. namespace Emby.Dlna.ContentDirectory
  39. {
  40. /// <summary>
  41. /// Defines the <see cref="ControlHandler" />.
  42. /// </summary>
  43. public class ControlHandler : BaseControlHandler
  44. {
  45. private const string NsDc = "http://purl.org/dc/elements/1.1/";
  46. private const string NsDidl = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
  47. private const string NsDlna = "urn:schemas-dlna-org:metadata-1-0/";
  48. private const string NsUpnp = "urn:schemas-upnp-org:metadata-1-0/upnp/";
  49. private readonly ILibraryManager _libraryManager;
  50. private readonly IUserDataManager _userDataManager;
  51. private readonly IServerConfigurationManager _config;
  52. private readonly User _user;
  53. private readonly IUserViewManager _userViewManager;
  54. private readonly ITVSeriesManager _tvSeriesManager;
  55. private readonly int _systemUpdateId;
  56. private readonly DidlBuilder _didlBuilder;
  57. private readonly DeviceProfile _profile;
  58. /// <summary>
  59. /// Initializes a new instance of the <see cref="ControlHandler"/> class.
  60. /// </summary>
  61. /// <param name="logger">The <see cref="ILogger"/> for use with the <see cref="ControlHandler"/> instance.</param>
  62. /// <param name="libraryManager">The <see cref="ILibraryManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
  63. /// <param name="profile">The <see cref="DeviceProfile"/> for use with the <see cref="ControlHandler"/> instance.</param>
  64. /// <param name="serverAddress">The server address to use in this instance> for use with the <see cref="ControlHandler"/> instance.</param>
  65. /// <param name="accessToken">The <see cref="string"/> for use with the <see cref="ControlHandler"/> instance.</param>
  66. /// <param name="imageProcessor">The <see cref="IImageProcessor"/> for use with the <see cref="ControlHandler"/> instance.</param>
  67. /// <param name="userDataManager">The <see cref="IUserDataManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
  68. /// <param name="user">The <see cref="User"/> for use with the <see cref="ControlHandler"/> instance.</param>
  69. /// <param name="systemUpdateId">The system id for use with the <see cref="ControlHandler"/> instance.</param>
  70. /// <param name="config">The <see cref="IServerConfigurationManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
  71. /// <param name="localization">The <see cref="ILocalizationManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
  72. /// <param name="mediaSourceManager">The <see cref="IMediaSourceManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
  73. /// <param name="userViewManager">The <see cref="IUserViewManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
  74. /// <param name="mediaEncoder">The <see cref="IMediaEncoder"/> for use with the <see cref="ControlHandler"/> instance.</param>
  75. /// <param name="tvSeriesManager">The <see cref="ITVSeriesManager"/> for use with the <see cref="ControlHandler"/> instance.</param>
  76. public ControlHandler(
  77. ILogger logger,
  78. ILibraryManager libraryManager,
  79. DeviceProfile profile,
  80. string serverAddress,
  81. string accessToken,
  82. IImageProcessor imageProcessor,
  83. IUserDataManager userDataManager,
  84. User user,
  85. int systemUpdateId,
  86. IServerConfigurationManager config,
  87. ILocalizationManager localization,
  88. IMediaSourceManager mediaSourceManager,
  89. IUserViewManager userViewManager,
  90. IMediaEncoder mediaEncoder,
  91. ITVSeriesManager tvSeriesManager)
  92. : base(config, logger)
  93. {
  94. _libraryManager = libraryManager;
  95. _userDataManager = userDataManager;
  96. _user = user;
  97. _systemUpdateId = systemUpdateId;
  98. _userViewManager = userViewManager;
  99. _tvSeriesManager = tvSeriesManager;
  100. _profile = profile;
  101. _config = config;
  102. _didlBuilder = new DidlBuilder(
  103. profile,
  104. user,
  105. imageProcessor,
  106. serverAddress,
  107. accessToken,
  108. userDataManager,
  109. localization,
  110. mediaSourceManager,
  111. Logger,
  112. mediaEncoder,
  113. libraryManager);
  114. }
  115. /// <inheritdoc />
  116. protected override void WriteResult(string methodName, IReadOnlyDictionary<string, string> methodParams, XmlWriter xmlWriter)
  117. {
  118. if (xmlWriter == null)
  119. {
  120. throw new ArgumentNullException(nameof(xmlWriter));
  121. }
  122. if (methodParams == null)
  123. {
  124. throw new ArgumentNullException(nameof(methodParams));
  125. }
  126. const string DeviceId = "test";
  127. if (string.Equals(methodName, "GetSearchCapabilities", StringComparison.OrdinalIgnoreCase))
  128. {
  129. HandleGetSearchCapabilities(xmlWriter);
  130. return;
  131. }
  132. if (string.Equals(methodName, "GetSortCapabilities", StringComparison.OrdinalIgnoreCase))
  133. {
  134. HandleGetSortCapabilities(xmlWriter);
  135. return;
  136. }
  137. if (string.Equals(methodName, "GetSortExtensionCapabilities", StringComparison.OrdinalIgnoreCase))
  138. {
  139. HandleGetSortExtensionCapabilities(xmlWriter);
  140. return;
  141. }
  142. if (string.Equals(methodName, "GetSystemUpdateID", StringComparison.OrdinalIgnoreCase))
  143. {
  144. HandleGetSystemUpdateID(xmlWriter);
  145. return;
  146. }
  147. if (string.Equals(methodName, "Browse", StringComparison.OrdinalIgnoreCase))
  148. {
  149. HandleBrowse(xmlWriter, methodParams, DeviceId);
  150. return;
  151. }
  152. if (string.Equals(methodName, "X_GetFeatureList", StringComparison.OrdinalIgnoreCase))
  153. {
  154. HandleXGetFeatureList(xmlWriter);
  155. return;
  156. }
  157. if (string.Equals(methodName, "GetFeatureList", StringComparison.OrdinalIgnoreCase))
  158. {
  159. HandleGetFeatureList(xmlWriter);
  160. return;
  161. }
  162. if (string.Equals(methodName, "X_SetBookmark", StringComparison.OrdinalIgnoreCase))
  163. {
  164. HandleXSetBookmark(methodParams);
  165. return;
  166. }
  167. if (string.Equals(methodName, "Search", StringComparison.OrdinalIgnoreCase))
  168. {
  169. HandleSearch(xmlWriter, methodParams, DeviceId);
  170. return;
  171. }
  172. if (string.Equals(methodName, "X_BrowseByLetter", StringComparison.OrdinalIgnoreCase))
  173. {
  174. HandleXBrowseByLetter(xmlWriter, methodParams, DeviceId);
  175. return;
  176. }
  177. throw new ResourceNotFoundException("Unexpected control request name: " + methodName);
  178. }
  179. /// <summary>
  180. /// Adds a "XSetBookmark" element to the xml document.
  181. /// </summary>
  182. /// <param name="sparams">The method parameters.</param>
  183. private void HandleXSetBookmark(IReadOnlyDictionary<string, string> sparams)
  184. {
  185. var id = sparams["ObjectID"];
  186. var serverItem = GetItemFromObjectId(id);
  187. var item = serverItem.Item;
  188. var newbookmark = int.Parse(sparams["PosSecond"], CultureInfo.InvariantCulture);
  189. var userdata = _userDataManager.GetUserData(_user, item);
  190. userdata.PlaybackPositionTicks = TimeSpan.FromSeconds(newbookmark).Ticks;
  191. _userDataManager.SaveUserData(
  192. _user,
  193. item,
  194. userdata,
  195. UserDataSaveReason.TogglePlayed,
  196. CancellationToken.None);
  197. }
  198. /// <summary>
  199. /// Adds the "SearchCaps" element to the xml document.
  200. /// </summary>
  201. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  202. private static void HandleGetSearchCapabilities(XmlWriter xmlWriter)
  203. {
  204. xmlWriter.WriteElementString(
  205. "SearchCaps",
  206. "res@resolution,res@size,res@duration,dc:title,dc:creator,upnp:actor,upnp:artist,upnp:genre,upnp:album,dc:date,upnp:class,@id,@refID,@protocolInfo,upnp:author,dc:description,pv:avKeywords");
  207. }
  208. /// <summary>
  209. /// Adds the "SortCaps" element to the xml document.
  210. /// </summary>
  211. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  212. private static void HandleGetSortCapabilities(XmlWriter xmlWriter)
  213. {
  214. xmlWriter.WriteElementString(
  215. "SortCaps",
  216. "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
  217. }
  218. /// <summary>
  219. /// Adds the "SortExtensionCaps" element to the xml document.
  220. /// </summary>
  221. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  222. private static void HandleGetSortExtensionCapabilities(XmlWriter xmlWriter)
  223. {
  224. xmlWriter.WriteElementString(
  225. "SortExtensionCaps",
  226. "res@duration,res@size,res@bitrate,dc:date,dc:title,dc:size,upnp:album,upnp:artist,upnp:albumArtist,upnp:episodeNumber,upnp:genre,upnp:originalTrackNumber,upnp:rating");
  227. }
  228. /// <summary>
  229. /// Adds the "Id" element to the xml document.
  230. /// </summary>
  231. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  232. private void HandleGetSystemUpdateID(XmlWriter xmlWriter)
  233. {
  234. xmlWriter.WriteElementString("Id", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
  235. }
  236. /// <summary>
  237. /// Adds the "FeatureList" element to the xml document.
  238. /// </summary>
  239. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  240. private static void HandleGetFeatureList(XmlWriter xmlWriter)
  241. {
  242. xmlWriter.WriteElementString("FeatureList", WriteFeatureListXml());
  243. }
  244. /// <summary>
  245. /// Adds the "FeatureList" element to the xml document.
  246. /// </summary>
  247. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  248. private static void HandleXGetFeatureList(XmlWriter xmlWriter)
  249. => HandleGetFeatureList(xmlWriter);
  250. /// <summary>
  251. /// Builds a static feature list.
  252. /// </summary>
  253. /// <returns>The xml feature list.</returns>
  254. private static string WriteFeatureListXml()
  255. {
  256. // TODO: clean this up
  257. var builder = new StringBuilder();
  258. builder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  259. builder.Append("<Features xmlns=\"urn:schemas-upnp-org:av:avs\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd\">");
  260. builder.Append("<Feature name=\"samsung.com_BASICVIEW\" version=\"1\">");
  261. builder.Append("<container id=\"I\" type=\"object.item.imageItem\"/>");
  262. builder.Append("<container id=\"A\" type=\"object.item.audioItem\"/>");
  263. builder.Append("<container id=\"V\" type=\"object.item.videoItem\"/>");
  264. builder.Append("</Feature>");
  265. builder.Append("</Features>");
  266. return builder.ToString();
  267. }
  268. /// <summary>
  269. /// Builds the "Browse" xml response.
  270. /// </summary>
  271. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  272. /// <param name="sparams">The method parameters.</param>
  273. /// <param name="deviceId">The device Id to use.</param>
  274. private void HandleBrowse(XmlWriter xmlWriter, IReadOnlyDictionary<string, string> sparams, string deviceId)
  275. {
  276. var id = sparams["ObjectID"];
  277. var flag = sparams["BrowseFlag"];
  278. var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
  279. var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty));
  280. var provided = 0;
  281. // Default to null instead of 0
  282. // Upnp inspector sends 0 as requestedCount when it wants everything
  283. int? requestedCount = null;
  284. int? start = 0;
  285. if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out var requestedVal) && requestedVal > 0)
  286. {
  287. requestedCount = requestedVal;
  288. }
  289. if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out var startVal) && startVal > 0)
  290. {
  291. start = startVal;
  292. }
  293. int totalCount;
  294. using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
  295. {
  296. var settings = new XmlWriterSettings()
  297. {
  298. Encoding = Encoding.UTF8,
  299. CloseOutput = false,
  300. OmitXmlDeclaration = true,
  301. ConformanceLevel = ConformanceLevel.Fragment
  302. };
  303. using (var writer = XmlWriter.Create(builder, settings))
  304. {
  305. writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
  306. writer.WriteAttributeString("xmlns", "dc", null, NsDc);
  307. writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
  308. writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
  309. DidlBuilder.WriteXmlRootAttributes(_profile, writer);
  310. var serverItem = GetItemFromObjectId(id);
  311. var item = serverItem.Item;
  312. if (string.Equals(flag, "BrowseMetadata", StringComparison.Ordinal))
  313. {
  314. totalCount = 1;
  315. if (item.IsDisplayedAsFolder || serverItem.StubType.HasValue)
  316. {
  317. var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
  318. _didlBuilder.WriteFolderElement(writer, item, serverItem.StubType, null, childrenResult.TotalRecordCount, filter, id);
  319. }
  320. else
  321. {
  322. _didlBuilder.WriteItemElement(writer, item, _user, null, null, deviceId, filter);
  323. }
  324. provided++;
  325. }
  326. else
  327. {
  328. var childrenResult = GetUserItems(item, serverItem.StubType, _user, sortCriteria, start, requestedCount);
  329. totalCount = childrenResult.TotalRecordCount;
  330. provided = childrenResult.Items.Count;
  331. foreach (var i in childrenResult.Items)
  332. {
  333. var childItem = i.Item;
  334. var displayStubType = i.StubType;
  335. if (childItem.IsDisplayedAsFolder || displayStubType.HasValue)
  336. {
  337. var childCount = GetUserItems(childItem, displayStubType, _user, sortCriteria, null, 0)
  338. .TotalRecordCount;
  339. _didlBuilder.WriteFolderElement(writer, childItem, displayStubType, item, childCount, filter);
  340. }
  341. else
  342. {
  343. _didlBuilder.WriteItemElement(writer, childItem, _user, item, serverItem.StubType, deviceId, filter);
  344. }
  345. }
  346. }
  347. writer.WriteFullEndElement();
  348. }
  349. xmlWriter.WriteElementString("Result", builder.ToString());
  350. }
  351. xmlWriter.WriteElementString("NumberReturned", provided.ToString(CultureInfo.InvariantCulture));
  352. xmlWriter.WriteElementString("TotalMatches", totalCount.ToString(CultureInfo.InvariantCulture));
  353. xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
  354. }
  355. /// <summary>
  356. /// Builds the response to the "X_BrowseByLetter request.
  357. /// </summary>
  358. /// <param name="xmlWriter">The <see cref="XmlWriter"/>.</param>
  359. /// <param name="sparams">The method parameters.</param>
  360. /// <param name="deviceId">The device id.</param>
  361. private void HandleXBrowseByLetter(XmlWriter xmlWriter, IReadOnlyDictionary<string, string> sparams, string deviceId)
  362. {
  363. // TODO: Implement this method
  364. HandleSearch(xmlWriter, sparams, deviceId);
  365. }
  366. /// <summary>
  367. /// Builds a response to the "Search" request.
  368. /// </summary>
  369. /// <param name="xmlWriter">The xmlWriter<see cref="XmlWriter"/>.</param>
  370. /// <param name="sparams">The method parameters.</param>
  371. /// <param name="deviceId">The deviceId<see cref="string"/>.</param>
  372. private void HandleSearch(XmlWriter xmlWriter, IReadOnlyDictionary<string, string> sparams, string deviceId)
  373. {
  374. var searchCriteria = new SearchCriteria(sparams.GetValueOrDefault("SearchCriteria", string.Empty));
  375. var sortCriteria = new SortCriteria(sparams.GetValueOrDefault("SortCriteria", string.Empty));
  376. var filter = new Filter(sparams.GetValueOrDefault("Filter", "*"));
  377. // sort example: dc:title, dc:date
  378. // Default to null instead of 0
  379. // Upnp inspector sends 0 as requestedCount when it wants everything
  380. int? requestedCount = null;
  381. int? start = 0;
  382. if (sparams.ContainsKey("RequestedCount") && int.TryParse(sparams["RequestedCount"], out var requestedVal) && requestedVal > 0)
  383. {
  384. requestedCount = requestedVal;
  385. }
  386. if (sparams.ContainsKey("StartingIndex") && int.TryParse(sparams["StartingIndex"], out var startVal) && startVal > 0)
  387. {
  388. start = startVal;
  389. }
  390. QueryResult<BaseItem> childrenResult;
  391. using (StringWriter builder = new StringWriterWithEncoding(Encoding.UTF8))
  392. {
  393. var settings = new XmlWriterSettings()
  394. {
  395. Encoding = Encoding.UTF8,
  396. CloseOutput = false,
  397. OmitXmlDeclaration = true,
  398. ConformanceLevel = ConformanceLevel.Fragment
  399. };
  400. using (var writer = XmlWriter.Create(builder, settings))
  401. {
  402. writer.WriteStartElement(string.Empty, "DIDL-Lite", NsDidl);
  403. writer.WriteAttributeString("xmlns", "dc", null, NsDc);
  404. writer.WriteAttributeString("xmlns", "dlna", null, NsDlna);
  405. writer.WriteAttributeString("xmlns", "upnp", null, NsUpnp);
  406. DidlBuilder.WriteXmlRootAttributes(_profile, writer);
  407. var serverItem = GetItemFromObjectId(sparams["ContainerID"]);
  408. var item = serverItem.Item;
  409. childrenResult = GetChildrenSorted(item, _user, searchCriteria, sortCriteria, start, requestedCount);
  410. var dlnaOptions = _config.GetDlnaConfiguration();
  411. foreach (var i in childrenResult.Items)
  412. {
  413. if (i.IsDisplayedAsFolder)
  414. {
  415. var childCount = GetChildrenSorted(i, _user, searchCriteria, sortCriteria, null, 0)
  416. .TotalRecordCount;
  417. _didlBuilder.WriteFolderElement(writer, i, null, item, childCount, filter);
  418. }
  419. else
  420. {
  421. _didlBuilder.WriteItemElement(writer, i, _user, item, serverItem.StubType, deviceId, filter);
  422. }
  423. }
  424. writer.WriteFullEndElement();
  425. }
  426. xmlWriter.WriteElementString("Result", builder.ToString());
  427. }
  428. xmlWriter.WriteElementString("NumberReturned", childrenResult.Items.Count.ToString(CultureInfo.InvariantCulture));
  429. xmlWriter.WriteElementString("TotalMatches", childrenResult.TotalRecordCount.ToString(CultureInfo.InvariantCulture));
  430. xmlWriter.WriteElementString("UpdateID", _systemUpdateId.ToString(CultureInfo.InvariantCulture));
  431. }
  432. /// <summary>
  433. /// Returns the child items meeting the criteria.
  434. /// </summary>
  435. /// <param name="item">The <see cref="BaseItem"/>.</param>
  436. /// <param name="user">The <see cref="User"/>.</param>
  437. /// <param name="search">The <see cref="SearchCriteria"/>.</param>
  438. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  439. /// <param name="startIndex">The start index.</param>
  440. /// <param name="limit">The maximum number to return.</param>
  441. /// <returns>The <see cref="QueryResult{BaseItem}"/>.</returns>
  442. private static QueryResult<BaseItem> GetChildrenSorted(BaseItem item, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit)
  443. {
  444. var folder = (Folder)item;
  445. var sortOrders = folder.IsPreSorted
  446. ? Array.Empty<(string, SortOrder)>()
  447. : new[] { (ItemSortBy.SortName, sort.SortOrder) };
  448. string[] mediaTypes = Array.Empty<string>();
  449. bool? isFolder = null;
  450. if (search.SearchType == SearchType.Audio)
  451. {
  452. mediaTypes = new[] { MediaType.Audio };
  453. isFolder = false;
  454. }
  455. else if (search.SearchType == SearchType.Video)
  456. {
  457. mediaTypes = new[] { MediaType.Video };
  458. isFolder = false;
  459. }
  460. else if (search.SearchType == SearchType.Image)
  461. {
  462. mediaTypes = new[] { MediaType.Photo };
  463. isFolder = false;
  464. }
  465. else if (search.SearchType == SearchType.Playlist)
  466. {
  467. // items = items.OfType<Playlist>();
  468. isFolder = true;
  469. }
  470. else if (search.SearchType == SearchType.MusicAlbum)
  471. {
  472. // items = items.OfType<MusicAlbum>();
  473. isFolder = true;
  474. }
  475. return folder.GetItems(new InternalItemsQuery
  476. {
  477. Limit = limit,
  478. StartIndex = startIndex,
  479. OrderBy = sortOrders,
  480. User = user,
  481. Recursive = true,
  482. IsMissing = false,
  483. ExcludeItemTypes = new[] { nameof(Book) },
  484. IsFolder = isFolder,
  485. MediaTypes = mediaTypes,
  486. DtoOptions = GetDtoOptions()
  487. });
  488. }
  489. /// <summary>
  490. /// Returns a new DtoOptions object.
  491. /// </summary>
  492. /// <returns>The <see cref="DtoOptions"/>.</returns>
  493. private static DtoOptions GetDtoOptions()
  494. {
  495. return new DtoOptions(true);
  496. }
  497. /// <summary>
  498. /// Returns the User items meeting the criteria.
  499. /// </summary>
  500. /// <param name="item">The <see cref="BaseItem"/>.</param>
  501. /// <param name="stubType">The <see cref="StubType"/>.</param>
  502. /// <param name="user">The <see cref="User"/>.</param>
  503. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  504. /// <param name="startIndex">The start index.</param>
  505. /// <param name="limit">The maximum number to return.</param>
  506. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  507. private QueryResult<ServerItem> GetUserItems(BaseItem item, StubType? stubType, User user, SortCriteria sort, int? startIndex, int? limit)
  508. {
  509. if (item is MusicGenre)
  510. {
  511. return GetMusicGenreItems(item, Guid.Empty, user, sort, startIndex, limit);
  512. }
  513. if (item is MusicArtist)
  514. {
  515. return GetMusicArtistItems(item, Guid.Empty, user, sort, startIndex, limit);
  516. }
  517. if (item is Genre)
  518. {
  519. return GetGenreItems(item, Guid.Empty, user, sort, startIndex, limit);
  520. }
  521. if ((!stubType.HasValue || stubType.Value != StubType.Folder)
  522. && item is IHasCollectionType collectionFolder)
  523. {
  524. if (string.Equals(CollectionType.Music, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
  525. {
  526. return GetMusicFolders(item, user, stubType, sort, startIndex, limit);
  527. }
  528. else if (string.Equals(CollectionType.Movies, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
  529. {
  530. return GetMovieFolders(item, user, stubType, sort, startIndex, limit);
  531. }
  532. else if (string.Equals(CollectionType.TvShows, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
  533. {
  534. return GetTvFolders(item, user, stubType, sort, startIndex, limit);
  535. }
  536. else if (string.Equals(CollectionType.Folders, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
  537. {
  538. return GetFolders(user, startIndex, limit);
  539. }
  540. else if (string.Equals(CollectionType.LiveTv, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
  541. {
  542. return GetLiveTvChannels(user, sort, startIndex, limit);
  543. }
  544. }
  545. if (stubType.HasValue)
  546. {
  547. if (stubType.Value != StubType.Folder)
  548. {
  549. return ApplyPaging(new QueryResult<ServerItem>(), startIndex, limit);
  550. }
  551. }
  552. var folder = (Folder)item;
  553. var query = new InternalItemsQuery(user)
  554. {
  555. Limit = limit,
  556. StartIndex = startIndex,
  557. IsVirtualItem = false,
  558. ExcludeItemTypes = new[] { nameof(Book) },
  559. IsPlaceHolder = false,
  560. DtoOptions = GetDtoOptions()
  561. };
  562. SetSorting(query, sort, folder.IsPreSorted);
  563. var queryResult = folder.GetItems(query);
  564. return ToResult(queryResult);
  565. }
  566. /// <summary>
  567. /// Returns the Live Tv Channels meeting the criteria.
  568. /// </summary>
  569. /// <param name="user">The <see cref="User"/>.</param>
  570. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  571. /// <param name="startIndex">The start index.</param>
  572. /// <param name="limit">The maximum number to return.</param>
  573. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  574. private QueryResult<ServerItem> GetLiveTvChannels(User user, SortCriteria sort, int? startIndex, int? limit)
  575. {
  576. var query = new InternalItemsQuery(user)
  577. {
  578. StartIndex = startIndex,
  579. Limit = limit,
  580. };
  581. query.IncludeItemTypes = new[] { nameof(LiveTvChannel) };
  582. SetSorting(query, sort, false);
  583. var result = _libraryManager.GetItemsResult(query);
  584. return ToResult(result);
  585. }
  586. /// <summary>
  587. /// Returns the music folders meeting the criteria.
  588. /// </summary>
  589. /// <param name="item">The <see cref="BaseItem"/>.</param>
  590. /// <param name="user">The <see cref="User"/>.</param>
  591. /// <param name="stubType">The <see cref="StubType"/>.</param>
  592. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  593. /// <param name="startIndex">The start index.</param>
  594. /// <param name="limit">The maximum number to return.</param>
  595. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  596. private QueryResult<ServerItem> GetMusicFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
  597. {
  598. var query = new InternalItemsQuery(user)
  599. {
  600. StartIndex = startIndex,
  601. Limit = limit
  602. };
  603. SetSorting(query, sort, false);
  604. if (stubType.HasValue && stubType.Value == StubType.Latest)
  605. {
  606. return GetMusicLatest(item, user, query);
  607. }
  608. if (stubType.HasValue && stubType.Value == StubType.Playlists)
  609. {
  610. return GetMusicPlaylists(user, query);
  611. }
  612. if (stubType.HasValue && stubType.Value == StubType.Albums)
  613. {
  614. return GetMusicAlbums(item, user, query);
  615. }
  616. if (stubType.HasValue && stubType.Value == StubType.Artists)
  617. {
  618. return GetMusicArtists(item, user, query);
  619. }
  620. if (stubType.HasValue && stubType.Value == StubType.AlbumArtists)
  621. {
  622. return GetMusicAlbumArtists(item, user, query);
  623. }
  624. if (stubType.HasValue && stubType.Value == StubType.FavoriteAlbums)
  625. {
  626. return GetFavoriteAlbums(item, user, query);
  627. }
  628. if (stubType.HasValue && stubType.Value == StubType.FavoriteArtists)
  629. {
  630. return GetFavoriteArtists(item, user, query);
  631. }
  632. if (stubType.HasValue && stubType.Value == StubType.FavoriteSongs)
  633. {
  634. return GetFavoriteSongs(item, user, query);
  635. }
  636. if (stubType.HasValue && stubType.Value == StubType.Songs)
  637. {
  638. return GetMusicSongs(item, user, query);
  639. }
  640. if (stubType.HasValue && stubType.Value == StubType.Genres)
  641. {
  642. return GetMusicGenres(item, user, query);
  643. }
  644. var list = new List<ServerItem>
  645. {
  646. new ServerItem(item)
  647. {
  648. StubType = StubType.Latest
  649. },
  650. new ServerItem(item)
  651. {
  652. StubType = StubType.Playlists
  653. },
  654. new ServerItem(item)
  655. {
  656. StubType = StubType.Albums
  657. },
  658. new ServerItem(item)
  659. {
  660. StubType = StubType.AlbumArtists
  661. },
  662. new ServerItem(item)
  663. {
  664. StubType = StubType.Artists
  665. },
  666. new ServerItem(item)
  667. {
  668. StubType = StubType.Songs
  669. },
  670. new ServerItem(item)
  671. {
  672. StubType = StubType.Genres
  673. },
  674. new ServerItem(item)
  675. {
  676. StubType = StubType.FavoriteArtists
  677. },
  678. new ServerItem(item)
  679. {
  680. StubType = StubType.FavoriteAlbums
  681. },
  682. new ServerItem(item)
  683. {
  684. StubType = StubType.FavoriteSongs
  685. }
  686. };
  687. return new QueryResult<ServerItem>
  688. {
  689. Items = list,
  690. TotalRecordCount = list.Count
  691. };
  692. }
  693. /// <summary>
  694. /// Returns the movie folders meeting the criteria.
  695. /// </summary>
  696. /// <param name="item">The <see cref="BaseItem"/>.</param>
  697. /// <param name="user">The <see cref="User"/>.</param>
  698. /// <param name="stubType">The <see cref="StubType"/>.</param>
  699. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  700. /// <param name="startIndex">The start index.</param>
  701. /// <param name="limit">The maximum number to return.</param>
  702. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  703. private QueryResult<ServerItem> GetMovieFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
  704. {
  705. var query = new InternalItemsQuery(user)
  706. {
  707. StartIndex = startIndex,
  708. Limit = limit
  709. };
  710. SetSorting(query, sort, false);
  711. if (stubType.HasValue && stubType.Value == StubType.ContinueWatching)
  712. {
  713. return GetMovieContinueWatching(item, user, query);
  714. }
  715. if (stubType.HasValue && stubType.Value == StubType.Latest)
  716. {
  717. return GetMovieLatest(item, user, query);
  718. }
  719. if (stubType.HasValue && stubType.Value == StubType.Movies)
  720. {
  721. return GetMovieMovies(item, user, query);
  722. }
  723. if (stubType.HasValue && stubType.Value == StubType.Collections)
  724. {
  725. return GetMovieCollections(user, query);
  726. }
  727. if (stubType.HasValue && stubType.Value == StubType.Favorites)
  728. {
  729. return GetMovieFavorites(item, user, query);
  730. }
  731. if (stubType.HasValue && stubType.Value == StubType.Genres)
  732. {
  733. return GetGenres(item, user, query);
  734. }
  735. var array = new[]
  736. {
  737. new ServerItem(item)
  738. {
  739. StubType = StubType.ContinueWatching
  740. },
  741. new ServerItem(item)
  742. {
  743. StubType = StubType.Latest
  744. },
  745. new ServerItem(item)
  746. {
  747. StubType = StubType.Movies
  748. },
  749. new ServerItem(item)
  750. {
  751. StubType = StubType.Collections
  752. },
  753. new ServerItem(item)
  754. {
  755. StubType = StubType.Favorites
  756. },
  757. new ServerItem(item)
  758. {
  759. StubType = StubType.Genres
  760. }
  761. };
  762. return new QueryResult<ServerItem>
  763. {
  764. Items = array,
  765. TotalRecordCount = array.Length
  766. };
  767. }
  768. /// <summary>
  769. /// Returns the folders meeting the criteria.
  770. /// </summary>
  771. /// <param name="user">The <see cref="User"/>.</param>
  772. /// <param name="startIndex">The start index.</param>
  773. /// <param name="limit">The maximum number to return.</param>
  774. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  775. private QueryResult<ServerItem> GetFolders(User user, int? startIndex, int? limit)
  776. {
  777. var folders = _libraryManager.GetUserRootFolder().GetChildren(user, true)
  778. .OrderBy(i => i.SortName)
  779. .Select(i => new ServerItem(i)
  780. {
  781. StubType = StubType.Folder
  782. })
  783. .ToArray();
  784. return ApplyPaging(
  785. new QueryResult<ServerItem>
  786. {
  787. Items = folders,
  788. TotalRecordCount = folders.Length
  789. },
  790. startIndex,
  791. limit);
  792. }
  793. /// <summary>
  794. /// Returns the TV folders meeting the criteria.
  795. /// </summary>
  796. /// <param name="item">The <see cref="BaseItem"/>.</param>
  797. /// <param name="user">The <see cref="User"/>.</param>
  798. /// <param name="stubType">The <see cref="StubType"/>.</param>
  799. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  800. /// <param name="startIndex">The start index.</param>
  801. /// <param name="limit">The maximum number to return.</param>
  802. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  803. private QueryResult<ServerItem> GetTvFolders(BaseItem item, User user, StubType? stubType, SortCriteria sort, int? startIndex, int? limit)
  804. {
  805. var query = new InternalItemsQuery(user)
  806. {
  807. StartIndex = startIndex,
  808. Limit = limit
  809. };
  810. SetSorting(query, sort, false);
  811. if (stubType.HasValue && stubType.Value == StubType.ContinueWatching)
  812. {
  813. return GetMovieContinueWatching(item, user, query);
  814. }
  815. if (stubType.HasValue && stubType.Value == StubType.NextUp)
  816. {
  817. return GetNextUp(item, query);
  818. }
  819. if (stubType.HasValue && stubType.Value == StubType.Latest)
  820. {
  821. return GetTvLatest(item, user, query);
  822. }
  823. if (stubType.HasValue && stubType.Value == StubType.Series)
  824. {
  825. return GetSeries(item, user, query);
  826. }
  827. if (stubType.HasValue && stubType.Value == StubType.FavoriteSeries)
  828. {
  829. return GetFavoriteSeries(item, user, query);
  830. }
  831. if (stubType.HasValue && stubType.Value == StubType.FavoriteEpisodes)
  832. {
  833. return GetFavoriteEpisodes(item, user, query);
  834. }
  835. if (stubType.HasValue && stubType.Value == StubType.Genres)
  836. {
  837. return GetGenres(item, user, query);
  838. }
  839. var list = new List<ServerItem>
  840. {
  841. new ServerItem(item)
  842. {
  843. StubType = StubType.ContinueWatching
  844. },
  845. new ServerItem(item)
  846. {
  847. StubType = StubType.NextUp
  848. },
  849. new ServerItem(item)
  850. {
  851. StubType = StubType.Latest
  852. },
  853. new ServerItem(item)
  854. {
  855. StubType = StubType.Series
  856. },
  857. new ServerItem(item)
  858. {
  859. StubType = StubType.FavoriteSeries
  860. },
  861. new ServerItem(item)
  862. {
  863. StubType = StubType.FavoriteEpisodes
  864. },
  865. new ServerItem(item)
  866. {
  867. StubType = StubType.Genres
  868. }
  869. };
  870. return new QueryResult<ServerItem>
  871. {
  872. Items = list,
  873. TotalRecordCount = list.Count
  874. };
  875. }
  876. /// <summary>
  877. /// Returns the Movies that are part watched that meet the criteria.
  878. /// </summary>
  879. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  880. /// <param name="user">The <see cref="User"/>.</param>
  881. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  882. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  883. private QueryResult<ServerItem> GetMovieContinueWatching(BaseItem parent, User user, InternalItemsQuery query)
  884. {
  885. query.Recursive = true;
  886. query.Parent = parent;
  887. query.SetUser(user);
  888. query.OrderBy = new[]
  889. {
  890. (ItemSortBy.DatePlayed, SortOrder.Descending),
  891. (ItemSortBy.SortName, SortOrder.Ascending)
  892. };
  893. query.IsResumable = true;
  894. query.Limit = 10;
  895. var result = _libraryManager.GetItemsResult(query);
  896. return ToResult(result);
  897. }
  898. /// <summary>
  899. /// Returns the series meeting the criteria.
  900. /// </summary>
  901. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  902. /// <param name="user">The <see cref="User"/>.</param>
  903. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  904. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  905. private QueryResult<ServerItem> GetSeries(BaseItem parent, User user, InternalItemsQuery query)
  906. {
  907. query.Recursive = true;
  908. query.Parent = parent;
  909. query.SetUser(user);
  910. query.IncludeItemTypes = new[] { nameof(Series) };
  911. var result = _libraryManager.GetItemsResult(query);
  912. return ToResult(result);
  913. }
  914. /// <summary>
  915. /// Returns the Movie folders meeting the criteria.
  916. /// </summary>
  917. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  918. /// <param name="user">The <see cref="User"/>.</param>
  919. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  920. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  921. private QueryResult<ServerItem> GetMovieMovies(BaseItem parent, User user, InternalItemsQuery query)
  922. {
  923. query.Recursive = true;
  924. query.Parent = parent;
  925. query.SetUser(user);
  926. query.IncludeItemTypes = new[] { nameof(Movie) };
  927. var result = _libraryManager.GetItemsResult(query);
  928. return ToResult(result);
  929. }
  930. /// <summary>
  931. /// Returns the Movie collections meeting the criteria.
  932. /// </summary>
  933. /// <param name="user">The see cref="User"/>.</param>
  934. /// <param name="query">The see cref="InternalItemsQuery"/>.</param>
  935. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  936. private QueryResult<ServerItem> GetMovieCollections(User user, InternalItemsQuery query)
  937. {
  938. query.Recursive = true;
  939. // query.Parent = parent;
  940. query.SetUser(user);
  941. query.IncludeItemTypes = new[] { nameof(BoxSet) };
  942. var result = _libraryManager.GetItemsResult(query);
  943. return ToResult(result);
  944. }
  945. /// <summary>
  946. /// Returns the Music albums meeting the criteria.
  947. /// </summary>
  948. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  949. /// <param name="user">The <see cref="User"/>.</param>
  950. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  951. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  952. private QueryResult<ServerItem> GetMusicAlbums(BaseItem parent, User user, InternalItemsQuery query)
  953. {
  954. query.Recursive = true;
  955. query.Parent = parent;
  956. query.SetUser(user);
  957. query.IncludeItemTypes = new[] { nameof(MusicAlbum) };
  958. var result = _libraryManager.GetItemsResult(query);
  959. return ToResult(result);
  960. }
  961. /// <summary>
  962. /// Returns the Music songs meeting the criteria.
  963. /// </summary>
  964. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  965. /// <param name="user">The <see cref="User"/>.</param>
  966. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  967. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  968. private QueryResult<ServerItem> GetMusicSongs(BaseItem parent, User user, InternalItemsQuery query)
  969. {
  970. query.Recursive = true;
  971. query.Parent = parent;
  972. query.SetUser(user);
  973. query.IncludeItemTypes = new[] { nameof(Audio) };
  974. var result = _libraryManager.GetItemsResult(query);
  975. return ToResult(result);
  976. }
  977. /// <summary>
  978. /// Returns the songs tagged as favourite that meet the criteria.
  979. /// </summary>
  980. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  981. /// <param name="user">The <see cref="User"/>.</param>
  982. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  983. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  984. private QueryResult<ServerItem> GetFavoriteSongs(BaseItem parent, User user, InternalItemsQuery query)
  985. {
  986. query.Recursive = true;
  987. query.Parent = parent;
  988. query.SetUser(user);
  989. query.IsFavorite = true;
  990. query.IncludeItemTypes = new[] { nameof(Audio) };
  991. var result = _libraryManager.GetItemsResult(query);
  992. return ToResult(result);
  993. }
  994. /// <summary>
  995. /// Returns the series tagged as favourite that meet the criteria.
  996. /// </summary>
  997. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  998. /// <param name="user">The <see cref="User"/>.</param>
  999. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1000. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1001. private QueryResult<ServerItem> GetFavoriteSeries(BaseItem parent, User user, InternalItemsQuery query)
  1002. {
  1003. query.Recursive = true;
  1004. query.Parent = parent;
  1005. query.SetUser(user);
  1006. query.IsFavorite = true;
  1007. query.IncludeItemTypes = new[] { nameof(Series) };
  1008. var result = _libraryManager.GetItemsResult(query);
  1009. return ToResult(result);
  1010. }
  1011. /// <summary>
  1012. /// Returns the episodes tagged as favourite that meet the criteria.
  1013. /// </summary>
  1014. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1015. /// <param name="user">The <see cref="User"/>.</param>
  1016. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1017. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1018. private QueryResult<ServerItem> GetFavoriteEpisodes(BaseItem parent, User user, InternalItemsQuery query)
  1019. {
  1020. query.Recursive = true;
  1021. query.Parent = parent;
  1022. query.SetUser(user);
  1023. query.IsFavorite = true;
  1024. query.IncludeItemTypes = new[] { nameof(Episode) };
  1025. var result = _libraryManager.GetItemsResult(query);
  1026. return ToResult(result);
  1027. }
  1028. /// <summary>
  1029. /// Returns the movies tagged as favourite that meet the criteria.
  1030. /// </summary>
  1031. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1032. /// <param name="user">The <see cref="User"/>.</param>
  1033. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1034. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1035. private QueryResult<ServerItem> GetMovieFavorites(BaseItem parent, User user, InternalItemsQuery query)
  1036. {
  1037. query.Recursive = true;
  1038. query.Parent = parent;
  1039. query.SetUser(user);
  1040. query.IsFavorite = true;
  1041. query.IncludeItemTypes = new[] { nameof(Movie) };
  1042. var result = _libraryManager.GetItemsResult(query);
  1043. return ToResult(result);
  1044. }
  1045. /// <summary>
  1046. /// /// Returns the albums tagged as favourite that meet the criteria.
  1047. /// </summary>
  1048. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1049. /// <param name="user">The <see cref="User"/>.</param>
  1050. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1051. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1052. private QueryResult<ServerItem> GetFavoriteAlbums(BaseItem parent, User user, InternalItemsQuery query)
  1053. {
  1054. query.Recursive = true;
  1055. query.Parent = parent;
  1056. query.SetUser(user);
  1057. query.IsFavorite = true;
  1058. query.IncludeItemTypes = new[] { nameof(MusicAlbum) };
  1059. var result = _libraryManager.GetItemsResult(query);
  1060. return ToResult(result);
  1061. }
  1062. /// <summary>
  1063. /// Returns the genres meeting the criteria.
  1064. /// The GetGenres.
  1065. /// </summary>
  1066. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1067. /// <param name="user">The <see cref="User"/>.</param>
  1068. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1069. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1070. private QueryResult<ServerItem> GetGenres(BaseItem parent, User user, InternalItemsQuery query)
  1071. {
  1072. var genresResult = _libraryManager.GetGenres(new InternalItemsQuery(user)
  1073. {
  1074. AncestorIds = new[] { parent.Id },
  1075. StartIndex = query.StartIndex,
  1076. Limit = query.Limit
  1077. });
  1078. var result = new QueryResult<BaseItem>
  1079. {
  1080. TotalRecordCount = genresResult.TotalRecordCount,
  1081. Items = genresResult.Items.Select(i => i.Item1).ToArray()
  1082. };
  1083. return ToResult(result);
  1084. }
  1085. /// <summary>
  1086. /// Returns the music genres meeting the criteria.
  1087. /// </summary>
  1088. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1089. /// <param name="user">The <see cref="User"/>.</param>
  1090. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1091. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1092. private QueryResult<ServerItem> GetMusicGenres(BaseItem parent, User user, InternalItemsQuery query)
  1093. {
  1094. var genresResult = _libraryManager.GetMusicGenres(new InternalItemsQuery(user)
  1095. {
  1096. AncestorIds = new[] { parent.Id },
  1097. StartIndex = query.StartIndex,
  1098. Limit = query.Limit
  1099. });
  1100. var result = new QueryResult<BaseItem>
  1101. {
  1102. TotalRecordCount = genresResult.TotalRecordCount,
  1103. Items = genresResult.Items.Select(i => i.Item1).ToArray()
  1104. };
  1105. return ToResult(result);
  1106. }
  1107. /// <summary>
  1108. /// Returns the music albums by artist that meet the criteria.
  1109. /// </summary>
  1110. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1111. /// <param name="user">The <see cref="User"/>.</param>
  1112. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1113. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1114. private QueryResult<ServerItem> GetMusicAlbumArtists(BaseItem parent, User user, InternalItemsQuery query)
  1115. {
  1116. var artists = _libraryManager.GetAlbumArtists(new InternalItemsQuery(user)
  1117. {
  1118. AncestorIds = new[] { parent.Id },
  1119. StartIndex = query.StartIndex,
  1120. Limit = query.Limit
  1121. });
  1122. var result = new QueryResult<BaseItem>
  1123. {
  1124. TotalRecordCount = artists.TotalRecordCount,
  1125. Items = artists.Items.Select(i => i.Item1).ToArray()
  1126. };
  1127. return ToResult(result);
  1128. }
  1129. /// <summary>
  1130. /// Returns the music artists meeting the criteria.
  1131. /// </summary>
  1132. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1133. /// <param name="user">The <see cref="User"/>.</param>
  1134. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1135. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1136. private QueryResult<ServerItem> GetMusicArtists(BaseItem parent, User user, InternalItemsQuery query)
  1137. {
  1138. var artists = _libraryManager.GetArtists(new InternalItemsQuery(user)
  1139. {
  1140. AncestorIds = new[] { parent.Id },
  1141. StartIndex = query.StartIndex,
  1142. Limit = query.Limit
  1143. });
  1144. var result = new QueryResult<BaseItem>
  1145. {
  1146. TotalRecordCount = artists.TotalRecordCount,
  1147. Items = artists.Items.Select(i => i.Item1).ToArray()
  1148. };
  1149. return ToResult(result);
  1150. }
  1151. /// <summary>
  1152. /// Returns the artists tagged as favourite that meet the criteria.
  1153. /// </summary>
  1154. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1155. /// <param name="user">The <see cref="User"/>.</param>
  1156. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1157. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1158. private QueryResult<ServerItem> GetFavoriteArtists(BaseItem parent, User user, InternalItemsQuery query)
  1159. {
  1160. var artists = _libraryManager.GetArtists(new InternalItemsQuery(user)
  1161. {
  1162. AncestorIds = new[] { parent.Id },
  1163. StartIndex = query.StartIndex,
  1164. Limit = query.Limit,
  1165. IsFavorite = true
  1166. });
  1167. var result = new QueryResult<BaseItem>
  1168. {
  1169. TotalRecordCount = artists.TotalRecordCount,
  1170. Items = artists.Items.Select(i => i.Item1).ToArray()
  1171. };
  1172. return ToResult(result);
  1173. }
  1174. /// <summary>
  1175. /// Returns the music playlists meeting the criteria.
  1176. /// </summary>
  1177. /// <param name="user">The user<see cref="User"/>.</param>
  1178. /// <param name="query">The query<see cref="InternalItemsQuery"/>.</param>
  1179. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1180. private QueryResult<ServerItem> GetMusicPlaylists(User user, InternalItemsQuery query)
  1181. {
  1182. query.Parent = null;
  1183. query.IncludeItemTypes = new[] { nameof(Playlist) };
  1184. query.SetUser(user);
  1185. query.Recursive = true;
  1186. var result = _libraryManager.GetItemsResult(query);
  1187. return ToResult(result);
  1188. }
  1189. /// <summary>
  1190. /// Returns the latest music meeting the criteria.
  1191. /// </summary>
  1192. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1193. /// <param name="user">The <see cref="User"/>.</param>
  1194. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1195. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1196. private QueryResult<ServerItem> GetMusicLatest(BaseItem parent, User user, InternalItemsQuery query)
  1197. {
  1198. query.OrderBy = Array.Empty<(string, SortOrder)>();
  1199. var items = _userViewManager.GetLatestItems(
  1200. new LatestItemsQuery
  1201. {
  1202. UserId = user.Id,
  1203. Limit = 50,
  1204. IncludeItemTypes = new[] { nameof(Audio) },
  1205. ParentId = parent?.Id ?? Guid.Empty,
  1206. GroupItems = true
  1207. },
  1208. query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
  1209. return ToResult(items);
  1210. }
  1211. /// <summary>
  1212. /// Returns the next up item meeting the criteria.
  1213. /// </summary>
  1214. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1215. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1216. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1217. private QueryResult<ServerItem> GetNextUp(BaseItem parent, InternalItemsQuery query)
  1218. {
  1219. query.OrderBy = Array.Empty<(string, SortOrder)>();
  1220. var result = _tvSeriesManager.GetNextUp(
  1221. new NextUpQuery
  1222. {
  1223. Limit = query.Limit,
  1224. StartIndex = query.StartIndex,
  1225. UserId = query.User.Id
  1226. },
  1227. new[] { parent },
  1228. query.DtoOptions);
  1229. return ToResult(result);
  1230. }
  1231. /// <summary>
  1232. /// Returns the latest tv meeting the criteria.
  1233. /// </summary>
  1234. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1235. /// <param name="user">The <see cref="User"/>.</param>
  1236. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1237. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1238. private QueryResult<ServerItem> GetTvLatest(BaseItem parent, User user, InternalItemsQuery query)
  1239. {
  1240. query.OrderBy = Array.Empty<(string, SortOrder)>();
  1241. var items = _userViewManager.GetLatestItems(
  1242. new LatestItemsQuery
  1243. {
  1244. UserId = user.Id,
  1245. Limit = 50,
  1246. IncludeItemTypes = new[] { nameof(Episode) },
  1247. ParentId = parent == null ? Guid.Empty : parent.Id,
  1248. GroupItems = false
  1249. },
  1250. query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
  1251. return ToResult(items);
  1252. }
  1253. /// <summary>
  1254. /// Returns the latest movies meeting the criteria.
  1255. /// </summary>
  1256. /// <param name="parent">The <see cref="BaseItem"/>.</param>
  1257. /// <param name="user">The <see cref="User"/>.</param>
  1258. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1259. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1260. private QueryResult<ServerItem> GetMovieLatest(BaseItem parent, User user, InternalItemsQuery query)
  1261. {
  1262. query.OrderBy = Array.Empty<(string, SortOrder)>();
  1263. var items = _userViewManager.GetLatestItems(
  1264. new LatestItemsQuery
  1265. {
  1266. UserId = user.Id,
  1267. Limit = 50,
  1268. IncludeItemTypes = new[] { nameof(Movie) },
  1269. ParentId = parent?.Id ?? Guid.Empty,
  1270. GroupItems = true
  1271. },
  1272. query.DtoOptions).Select(i => i.Item1 ?? i.Item2.FirstOrDefault()).Where(i => i != null).ToArray();
  1273. return ToResult(items);
  1274. }
  1275. /// <summary>
  1276. /// Returns music artist items that meet the criteria.
  1277. /// </summary>
  1278. /// <param name="item">The <see cref="BaseItem"/>.</param>
  1279. /// <param name="parentId">The <see cref="Guid"/>.</param>
  1280. /// <param name="user">The <see cref="User"/>.</param>
  1281. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  1282. /// <param name="startIndex">The start index.</param>
  1283. /// <param name="limit">The maximum number to return.</param>
  1284. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1285. private QueryResult<ServerItem> GetMusicArtistItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit)
  1286. {
  1287. var query = new InternalItemsQuery(user)
  1288. {
  1289. Recursive = true,
  1290. ParentId = parentId,
  1291. ArtistIds = new[] { item.Id },
  1292. IncludeItemTypes = new[] { nameof(MusicAlbum) },
  1293. Limit = limit,
  1294. StartIndex = startIndex,
  1295. DtoOptions = GetDtoOptions()
  1296. };
  1297. SetSorting(query, sort, false);
  1298. var result = _libraryManager.GetItemsResult(query);
  1299. return ToResult(result);
  1300. }
  1301. /// <summary>
  1302. /// Returns the genre items meeting the criteria.
  1303. /// </summary>
  1304. /// <param name="item">The <see cref="BaseItem"/>.</param>
  1305. /// <param name="parentId">The <see cref="Guid"/>.</param>
  1306. /// <param name="user">The <see cref="User"/>.</param>
  1307. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  1308. /// <param name="startIndex">The start index.</param>
  1309. /// <param name="limit">The maximum number to return.</param>
  1310. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1311. private QueryResult<ServerItem> GetGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit)
  1312. {
  1313. var query = new InternalItemsQuery(user)
  1314. {
  1315. Recursive = true,
  1316. ParentId = parentId,
  1317. GenreIds = new[] { item.Id },
  1318. IncludeItemTypes = new[]
  1319. {
  1320. nameof(Movie),
  1321. nameof(Series)
  1322. },
  1323. Limit = limit,
  1324. StartIndex = startIndex,
  1325. DtoOptions = GetDtoOptions()
  1326. };
  1327. SetSorting(query, sort, false);
  1328. var result = _libraryManager.GetItemsResult(query);
  1329. return ToResult(result);
  1330. }
  1331. /// <summary>
  1332. /// Returns the music genre items meeting the criteria.
  1333. /// </summary>
  1334. /// <param name="item">The <see cref="BaseItem"/>.</param>
  1335. /// <param name="parentId">The <see cref="Guid"/>.</param>
  1336. /// <param name="user">The <see cref="User"/>.</param>
  1337. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  1338. /// <param name="startIndex">The start index.</param>
  1339. /// <param name="limit">The maximum number to return.</param>
  1340. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1341. private QueryResult<ServerItem> GetMusicGenreItems(BaseItem item, Guid parentId, User user, SortCriteria sort, int? startIndex, int? limit)
  1342. {
  1343. var query = new InternalItemsQuery(user)
  1344. {
  1345. Recursive = true,
  1346. ParentId = parentId,
  1347. GenreIds = new[] { item.Id },
  1348. IncludeItemTypes = new[] { nameof(MusicAlbum) },
  1349. Limit = limit,
  1350. StartIndex = startIndex,
  1351. DtoOptions = GetDtoOptions()
  1352. };
  1353. SetSorting(query, sort, false);
  1354. var result = _libraryManager.GetItemsResult(query);
  1355. return ToResult(result);
  1356. }
  1357. /// <summary>
  1358. /// Converts a <see cref="BaseItem"/> array into a <see cref="QueryResult{ServerItem}"/>.
  1359. /// </summary>
  1360. /// <param name="result">An array of <see cref="BaseItem"/>.</param>
  1361. /// <returns>A <see cref="QueryResult{ServerItem}"/>.</returns>
  1362. private static QueryResult<ServerItem> ToResult(BaseItem[] result)
  1363. {
  1364. var serverItems = result
  1365. .Select(i => new ServerItem(i))
  1366. .ToArray();
  1367. return new QueryResult<ServerItem>
  1368. {
  1369. TotalRecordCount = result.Length,
  1370. Items = serverItems
  1371. };
  1372. }
  1373. /// <summary>
  1374. /// Converts a <see cref="QueryResult{BaseItem}"/> to a <see cref="QueryResult{ServerItem}"/>.
  1375. /// </summary>
  1376. /// <param name="result">A <see cref="QueryResult{BaseItem}"/>.</param>
  1377. /// <returns>The <see cref="QueryResult{ServerItem}"/>.</returns>
  1378. private static QueryResult<ServerItem> ToResult(QueryResult<BaseItem> result)
  1379. {
  1380. var serverItems = result
  1381. .Items
  1382. .Select(i => new ServerItem(i))
  1383. .ToArray();
  1384. return new QueryResult<ServerItem>
  1385. {
  1386. TotalRecordCount = result.TotalRecordCount,
  1387. Items = serverItems
  1388. };
  1389. }
  1390. /// <summary>
  1391. /// Sets the sorting method on a query.
  1392. /// </summary>
  1393. /// <param name="query">The <see cref="InternalItemsQuery"/>.</param>
  1394. /// <param name="sort">The <see cref="SortCriteria"/>.</param>
  1395. /// <param name="isPreSorted">True if pre-sorted.</param>
  1396. private static void SetSorting(InternalItemsQuery query, SortCriteria sort, bool isPreSorted)
  1397. {
  1398. if (isPreSorted)
  1399. {
  1400. query.OrderBy = Array.Empty<(string, SortOrder)>();
  1401. }
  1402. else
  1403. {
  1404. query.OrderBy = new[] { (ItemSortBy.SortName, sort.SortOrder) };
  1405. }
  1406. }
  1407. /// <summary>
  1408. /// Apply paging to a query.
  1409. /// </summary>
  1410. /// <param name="result">The <see cref="QueryResult{ServerItem}"/>.</param>
  1411. /// <param name="startIndex">The start index.</param>
  1412. /// <param name="limit">The maximum number to return.</param>
  1413. /// <returns>A <see cref="QueryResult{ServerItem}"/>.</returns>
  1414. private static QueryResult<ServerItem> ApplyPaging(QueryResult<ServerItem> result, int? startIndex, int? limit)
  1415. {
  1416. result.Items = result.Items.Skip(startIndex ?? 0).Take(limit ?? int.MaxValue).ToArray();
  1417. return result;
  1418. }
  1419. /// <summary>
  1420. /// Retrieves the ServerItem id.
  1421. /// </summary>
  1422. /// <param name="id">The id<see cref="string"/>.</param>
  1423. /// <returns>The <see cref="ServerItem"/>.</returns>
  1424. private ServerItem GetItemFromObjectId(string id)
  1425. {
  1426. return DidlBuilder.IsIdRoot(id)
  1427. ? new ServerItem(_libraryManager.GetUserRootFolder())
  1428. : ParseItemId(id);
  1429. }
  1430. /// <summary>
  1431. /// Parses the item id into a <see cref="ServerItem"/>.
  1432. /// </summary>
  1433. /// <param name="id">The <see cref="string"/>.</param>
  1434. /// <returns>The corresponding <see cref="ServerItem"/>.</returns>
  1435. private ServerItem ParseItemId(string id)
  1436. {
  1437. StubType? stubType = null;
  1438. // After using PlayTo, MediaMonkey sends a request to the server trying to get item info
  1439. const string ParamsSrch = "Params=";
  1440. var paramsIndex = id.IndexOf(ParamsSrch, StringComparison.OrdinalIgnoreCase);
  1441. if (paramsIndex != -1)
  1442. {
  1443. id = id.Substring(paramsIndex + ParamsSrch.Length);
  1444. var parts = id.Split(';');
  1445. id = parts[23];
  1446. }
  1447. var enumNames = Enum.GetNames(typeof(StubType));
  1448. foreach (var name in enumNames)
  1449. {
  1450. if (id.StartsWith(name + "_", StringComparison.OrdinalIgnoreCase))
  1451. {
  1452. stubType = Enum.Parse<StubType>(name, true);
  1453. id = id.Split('_', 2)[1];
  1454. break;
  1455. }
  1456. }
  1457. if (Guid.TryParse(id, out var itemId))
  1458. {
  1459. var item = _libraryManager.GetItemById(itemId);
  1460. return new ServerItem(item)
  1461. {
  1462. StubType = stubType
  1463. };
  1464. }
  1465. Logger.LogError("Error parsing item Id: {Id}. Returning user root folder.", id);
  1466. return new ServerItem(_libraryManager.GetUserRootFolder());
  1467. }
  1468. }
  1469. }