SqliteItemRepository.cs 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Controller.Entities;
  3. using MediaBrowser.Controller.LiveTv;
  4. using MediaBrowser.Controller.Persistence;
  5. using MediaBrowser.Model.Entities;
  6. using MediaBrowser.Model.Logging;
  7. using MediaBrowser.Model.Querying;
  8. using MediaBrowser.Model.Serialization;
  9. using System;
  10. using System.Collections.Generic;
  11. using System.Data;
  12. using System.Globalization;
  13. using System.IO;
  14. using System.Linq;
  15. using System.Threading;
  16. using System.Threading.Tasks;
  17. namespace MediaBrowser.Server.Implementations.Persistence
  18. {
  19. /// <summary>
  20. /// Class SQLiteItemRepository
  21. /// </summary>
  22. public class SqliteItemRepository : IItemRepository
  23. {
  24. private IDbConnection _connection;
  25. private readonly ILogger _logger;
  26. private readonly TypeMapper _typeMapper = new TypeMapper();
  27. /// <summary>
  28. /// Gets the name of the repository
  29. /// </summary>
  30. /// <value>The name.</value>
  31. public string Name
  32. {
  33. get
  34. {
  35. return "SQLite";
  36. }
  37. }
  38. /// <summary>
  39. /// Gets the json serializer.
  40. /// </summary>
  41. /// <value>The json serializer.</value>
  42. private readonly IJsonSerializer _jsonSerializer;
  43. /// <summary>
  44. /// The _app paths
  45. /// </summary>
  46. private readonly IApplicationPaths _appPaths;
  47. /// <summary>
  48. /// The _save item command
  49. /// </summary>
  50. private IDbCommand _saveItemCommand;
  51. private readonly string _criticReviewsPath;
  52. private SqliteChapterRepository _chapterRepository;
  53. private SqliteMediaStreamsRepository _mediaStreamsRepository;
  54. private IDbCommand _deleteChildrenCommand;
  55. private IDbCommand _saveChildrenCommand;
  56. private IDbCommand _deleteItemCommand;
  57. /// <summary>
  58. /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
  59. /// </summary>
  60. /// <param name="appPaths">The app paths.</param>
  61. /// <param name="jsonSerializer">The json serializer.</param>
  62. /// <param name="logManager">The log manager.</param>
  63. /// <exception cref="System.ArgumentNullException">
  64. /// appPaths
  65. /// or
  66. /// jsonSerializer
  67. /// </exception>
  68. public SqliteItemRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
  69. {
  70. if (appPaths == null)
  71. {
  72. throw new ArgumentNullException("appPaths");
  73. }
  74. if (jsonSerializer == null)
  75. {
  76. throw new ArgumentNullException("jsonSerializer");
  77. }
  78. _appPaths = appPaths;
  79. _jsonSerializer = jsonSerializer;
  80. _criticReviewsPath = Path.Combine(_appPaths.DataPath, "critic-reviews");
  81. _logger = logManager.GetLogger(GetType().Name);
  82. var chapterDbFile = Path.Combine(_appPaths.DataPath, "chapters.db");
  83. var chapterConnection = SqliteExtensions.ConnectToDb(chapterDbFile, _logger).Result;
  84. _chapterRepository = new SqliteChapterRepository(chapterConnection, logManager);
  85. var mediaStreamsDbFile = Path.Combine(_appPaths.DataPath, "mediainfo.db");
  86. var mediaStreamsConnection = SqliteExtensions.ConnectToDb(mediaStreamsDbFile, _logger).Result;
  87. _mediaStreamsRepository = new SqliteMediaStreamsRepository(mediaStreamsConnection, logManager);
  88. }
  89. /// <summary>
  90. /// Opens the connection to the database
  91. /// </summary>
  92. /// <returns>Task.</returns>
  93. public async Task Initialize()
  94. {
  95. var dbFile = Path.Combine(_appPaths.DataPath, "library.db");
  96. _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
  97. string[] queries = {
  98. "create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB)",
  99. "create index if not exists idx_TypedBaseItems on TypedBaseItems(guid)",
  100. "create table if not exists ChildrenIds (ParentId GUID, ItemId GUID, PRIMARY KEY (ParentId, ItemId))",
  101. "create index if not exists idx_ChildrenIds on ChildrenIds(ParentId,ItemId)",
  102. //pragmas
  103. "pragma temp_store = memory",
  104. "pragma shrink_memory"
  105. };
  106. _connection.RunQueries(queries, _logger);
  107. _connection.AddColumn(_logger, "TypedBaseItems", "Path", "Text");
  108. _connection.AddColumn(_logger, "TypedBaseItems", "StartDate", "DATETIME");
  109. _connection.AddColumn(_logger, "TypedBaseItems", "EndDate", "DATETIME");
  110. _connection.AddColumn(_logger, "TypedBaseItems", "ChannelId", "Text");
  111. _connection.AddColumn(_logger, "TypedBaseItems", "IsMovie", "BIT");
  112. _connection.AddColumn(_logger, "TypedBaseItems", "IsSports", "BIT");
  113. _connection.AddColumn(_logger, "TypedBaseItems", "IsKids", "BIT");
  114. _connection.AddColumn(_logger, "TypedBaseItems", "CommunityRating", "Float");
  115. _connection.AddColumn(_logger, "TypedBaseItems", "CustomRating", "Text");
  116. _connection.AddColumn(_logger, "TypedBaseItems", "IndexNumber", "INT");
  117. _connection.AddColumn(_logger, "TypedBaseItems", "IsLocked", "BIT");
  118. _connection.AddColumn(_logger, "TypedBaseItems", "Name", "Text");
  119. _connection.AddColumn(_logger, "TypedBaseItems", "OfficialRating", "Text");
  120. PrepareStatements();
  121. _mediaStreamsRepository.Initialize();
  122. _chapterRepository.Initialize();
  123. }
  124. /// <summary>
  125. /// The _write lock
  126. /// </summary>
  127. private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
  128. /// <summary>
  129. /// Prepares the statements.
  130. /// </summary>
  131. private void PrepareStatements()
  132. {
  133. var saveColumns = new List<string>
  134. {
  135. "guid",
  136. "type",
  137. "data",
  138. "Path",
  139. "StartDate",
  140. "EndDate",
  141. "ChannelId",
  142. "IsKids",
  143. "IsMovie",
  144. "IsSports",
  145. "CommunityRating",
  146. "CustomRating",
  147. "IndexNumber",
  148. "IsLocked",
  149. "Name",
  150. "OfficialRating"
  151. };
  152. _saveItemCommand = _connection.CreateCommand();
  153. _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (@1, @2, @3, @4, @5, @6, @7, @8, @9, @10, @11, @12, @13, @14, @15, @16)";
  154. for (var i = 1; i <= saveColumns.Count; i++)
  155. {
  156. _saveItemCommand.Parameters.Add(_saveItemCommand, "@" + i.ToString(CultureInfo.InvariantCulture));
  157. }
  158. _deleteChildrenCommand = _connection.CreateCommand();
  159. _deleteChildrenCommand.CommandText = "delete from ChildrenIds where ParentId=@ParentId";
  160. _deleteChildrenCommand.Parameters.Add(_deleteChildrenCommand, "@ParentId");
  161. _deleteItemCommand = _connection.CreateCommand();
  162. _deleteItemCommand.CommandText = "delete from TypedBaseItems where guid=@Id";
  163. _deleteItemCommand.Parameters.Add(_deleteItemCommand, "@Id");
  164. _saveChildrenCommand = _connection.CreateCommand();
  165. _saveChildrenCommand.CommandText = "replace into ChildrenIds (ParentId, ItemId) values (@ParentId, @ItemId)";
  166. _saveChildrenCommand.Parameters.Add(_saveChildrenCommand, "@ParentId");
  167. _saveChildrenCommand.Parameters.Add(_saveChildrenCommand, "@ItemId");
  168. }
  169. /// <summary>
  170. /// Save a standard item in the repo
  171. /// </summary>
  172. /// <param name="item">The item.</param>
  173. /// <param name="cancellationToken">The cancellation token.</param>
  174. /// <returns>Task.</returns>
  175. /// <exception cref="System.ArgumentNullException">item</exception>
  176. public Task SaveItem(BaseItem item, CancellationToken cancellationToken)
  177. {
  178. if (item == null)
  179. {
  180. throw new ArgumentNullException("item");
  181. }
  182. return SaveItems(new[] { item }, cancellationToken);
  183. }
  184. /// <summary>
  185. /// Saves the items.
  186. /// </summary>
  187. /// <param name="items">The items.</param>
  188. /// <param name="cancellationToken">The cancellation token.</param>
  189. /// <returns>Task.</returns>
  190. /// <exception cref="System.ArgumentNullException">
  191. /// items
  192. /// or
  193. /// cancellationToken
  194. /// </exception>
  195. public async Task SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken)
  196. {
  197. if (items == null)
  198. {
  199. throw new ArgumentNullException("items");
  200. }
  201. cancellationToken.ThrowIfCancellationRequested();
  202. CheckDisposed();
  203. await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  204. IDbTransaction transaction = null;
  205. try
  206. {
  207. transaction = _connection.BeginTransaction();
  208. foreach (var item in items)
  209. {
  210. cancellationToken.ThrowIfCancellationRequested();
  211. var index = 0;
  212. _saveItemCommand.GetParameter(index++).Value = item.Id;
  213. _saveItemCommand.GetParameter(index++).Value = item.GetType().FullName;
  214. _saveItemCommand.GetParameter(index++).Value = _jsonSerializer.SerializeToBytes(item);
  215. _saveItemCommand.GetParameter(index++).Value = item.Path;
  216. var hasStartDate = item as IHasStartDate;
  217. if (hasStartDate != null)
  218. {
  219. _saveItemCommand.GetParameter(index++).Value = hasStartDate.StartDate;
  220. }
  221. else
  222. {
  223. _saveItemCommand.GetParameter(index++).Value = null;
  224. }
  225. _saveItemCommand.GetParameter(index++).Value = item.EndDate;
  226. _saveItemCommand.GetParameter(index++).Value = item.ChannelId;
  227. var hasProgramAttributes = item as IHasProgramAttributes;
  228. if (hasProgramAttributes != null)
  229. {
  230. _saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsKids;
  231. _saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsMovie;
  232. _saveItemCommand.GetParameter(index++).Value = hasProgramAttributes.IsSports;
  233. }
  234. else
  235. {
  236. _saveItemCommand.GetParameter(index++).Value = null;
  237. _saveItemCommand.GetParameter(index++).Value = null;
  238. _saveItemCommand.GetParameter(index++).Value = null;
  239. }
  240. _saveItemCommand.GetParameter(index++).Value = item.CommunityRating;
  241. _saveItemCommand.GetParameter(index++).Value = item.CustomRating;
  242. _saveItemCommand.GetParameter(index++).Value = item.IndexNumber;
  243. _saveItemCommand.GetParameter(index++).Value = item.IsLocked;
  244. _saveItemCommand.GetParameter(index++).Value = item.Name;
  245. _saveItemCommand.GetParameter(index++).Value = item.OfficialRating;
  246. _saveItemCommand.Transaction = transaction;
  247. _saveItemCommand.ExecuteNonQuery();
  248. }
  249. transaction.Commit();
  250. }
  251. catch (OperationCanceledException)
  252. {
  253. if (transaction != null)
  254. {
  255. transaction.Rollback();
  256. }
  257. throw;
  258. }
  259. catch (Exception e)
  260. {
  261. _logger.ErrorException("Failed to save items:", e);
  262. if (transaction != null)
  263. {
  264. transaction.Rollback();
  265. }
  266. throw;
  267. }
  268. finally
  269. {
  270. if (transaction != null)
  271. {
  272. transaction.Dispose();
  273. }
  274. _writeLock.Release();
  275. }
  276. }
  277. /// <summary>
  278. /// Internal retrieve from items or users table
  279. /// </summary>
  280. /// <param name="id">The id.</param>
  281. /// <returns>BaseItem.</returns>
  282. /// <exception cref="System.ArgumentNullException">id</exception>
  283. /// <exception cref="System.ArgumentException"></exception>
  284. public BaseItem RetrieveItem(Guid id)
  285. {
  286. if (id == Guid.Empty)
  287. {
  288. throw new ArgumentNullException("id");
  289. }
  290. CheckDisposed();
  291. using (var cmd = _connection.CreateCommand())
  292. {
  293. cmd.CommandText = "select type,data from TypedBaseItems where guid = @guid";
  294. cmd.Parameters.Add(cmd, "@guid", DbType.Guid).Value = id;
  295. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
  296. {
  297. if (reader.Read())
  298. {
  299. return GetItem(reader);
  300. }
  301. }
  302. return null;
  303. }
  304. }
  305. private BaseItem GetItem(IDataReader reader)
  306. {
  307. var typeString = reader.GetString(0);
  308. var type = _typeMapper.GetType(typeString);
  309. if (type == null)
  310. {
  311. _logger.Debug("Unknown type {0}", typeString);
  312. return null;
  313. }
  314. using (var stream = reader.GetMemoryStream(1))
  315. {
  316. return _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem;
  317. }
  318. }
  319. /// <summary>
  320. /// Gets the critic reviews.
  321. /// </summary>
  322. /// <param name="itemId">The item id.</param>
  323. /// <returns>Task{IEnumerable{ItemReview}}.</returns>
  324. public IEnumerable<ItemReview> GetCriticReviews(Guid itemId)
  325. {
  326. try
  327. {
  328. var path = Path.Combine(_criticReviewsPath, itemId + ".json");
  329. return _jsonSerializer.DeserializeFromFile<List<ItemReview>>(path);
  330. }
  331. catch (DirectoryNotFoundException)
  332. {
  333. return new List<ItemReview>();
  334. }
  335. catch (FileNotFoundException)
  336. {
  337. return new List<ItemReview>();
  338. }
  339. }
  340. private readonly Task _cachedTask = Task.FromResult(true);
  341. /// <summary>
  342. /// Saves the critic reviews.
  343. /// </summary>
  344. /// <param name="itemId">The item id.</param>
  345. /// <param name="criticReviews">The critic reviews.</param>
  346. /// <returns>Task.</returns>
  347. public Task SaveCriticReviews(Guid itemId, IEnumerable<ItemReview> criticReviews)
  348. {
  349. Directory.CreateDirectory(_criticReviewsPath);
  350. var path = Path.Combine(_criticReviewsPath, itemId + ".json");
  351. _jsonSerializer.SerializeToFile(criticReviews.ToList(), path);
  352. return _cachedTask;
  353. }
  354. /// <summary>
  355. /// Gets chapters for an item
  356. /// </summary>
  357. /// <param name="id">The id.</param>
  358. /// <returns>IEnumerable{ChapterInfo}.</returns>
  359. /// <exception cref="System.ArgumentNullException">id</exception>
  360. public IEnumerable<ChapterInfo> GetChapters(Guid id)
  361. {
  362. CheckDisposed();
  363. return _chapterRepository.GetChapters(id);
  364. }
  365. /// <summary>
  366. /// Gets a single chapter for an item
  367. /// </summary>
  368. /// <param name="id">The id.</param>
  369. /// <param name="index">The index.</param>
  370. /// <returns>ChapterInfo.</returns>
  371. /// <exception cref="System.ArgumentNullException">id</exception>
  372. public ChapterInfo GetChapter(Guid id, int index)
  373. {
  374. CheckDisposed();
  375. return _chapterRepository.GetChapter(id, index);
  376. }
  377. /// <summary>
  378. /// Saves the chapters.
  379. /// </summary>
  380. /// <param name="id">The id.</param>
  381. /// <param name="chapters">The chapters.</param>
  382. /// <param name="cancellationToken">The cancellation token.</param>
  383. /// <returns>Task.</returns>
  384. /// <exception cref="System.ArgumentNullException">
  385. /// id
  386. /// or
  387. /// chapters
  388. /// or
  389. /// cancellationToken
  390. /// </exception>
  391. public Task SaveChapters(Guid id, IEnumerable<ChapterInfo> chapters, CancellationToken cancellationToken)
  392. {
  393. CheckDisposed();
  394. return _chapterRepository.SaveChapters(id, chapters, cancellationToken);
  395. }
  396. /// <summary>
  397. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  398. /// </summary>
  399. public void Dispose()
  400. {
  401. Dispose(true);
  402. GC.SuppressFinalize(this);
  403. }
  404. private readonly object _disposeLock = new object();
  405. private bool _disposed;
  406. private void CheckDisposed()
  407. {
  408. if (_disposed)
  409. {
  410. throw new ObjectDisposedException(GetType().Name + " has been disposed and cannot be accessed.");
  411. }
  412. }
  413. /// <summary>
  414. /// Releases unmanaged and - optionally - managed resources.
  415. /// </summary>
  416. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  417. protected virtual void Dispose(bool dispose)
  418. {
  419. if (dispose)
  420. {
  421. _disposed = true;
  422. try
  423. {
  424. lock (_disposeLock)
  425. {
  426. _writeLock.Wait();
  427. if (_connection != null)
  428. {
  429. if (_connection.IsOpen())
  430. {
  431. _connection.Close();
  432. }
  433. _connection.Dispose();
  434. _connection = null;
  435. }
  436. if (_chapterRepository != null)
  437. {
  438. _chapterRepository.Dispose();
  439. _chapterRepository = null;
  440. }
  441. if (_mediaStreamsRepository != null)
  442. {
  443. _mediaStreamsRepository.Dispose();
  444. _mediaStreamsRepository = null;
  445. }
  446. }
  447. }
  448. catch (Exception ex)
  449. {
  450. _logger.ErrorException("Error disposing database", ex);
  451. }
  452. }
  453. }
  454. public IEnumerable<Guid> GetChildren(Guid parentId)
  455. {
  456. if (parentId == Guid.Empty)
  457. {
  458. throw new ArgumentNullException("parentId");
  459. }
  460. CheckDisposed();
  461. using (var cmd = _connection.CreateCommand())
  462. {
  463. cmd.CommandText = "select ItemId from ChildrenIds where ParentId = @ParentId";
  464. cmd.Parameters.Add(cmd, "@ParentId", DbType.Guid).Value = parentId;
  465. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  466. {
  467. while (reader.Read())
  468. {
  469. yield return reader.GetGuid(0);
  470. }
  471. }
  472. }
  473. }
  474. public IEnumerable<BaseItem> GetChildrenItems(Guid parentId)
  475. {
  476. if (parentId == Guid.Empty)
  477. {
  478. throw new ArgumentNullException("parentId");
  479. }
  480. CheckDisposed();
  481. using (var cmd = _connection.CreateCommand())
  482. {
  483. cmd.CommandText = "select type,data from TypedBaseItems where guid in (select ItemId from ChildrenIds where ParentId = @ParentId)";
  484. cmd.Parameters.Add(cmd, "@ParentId", DbType.Guid).Value = parentId;
  485. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  486. {
  487. while (reader.Read())
  488. {
  489. var item = GetItem(reader);
  490. if (item != null)
  491. {
  492. yield return item;
  493. }
  494. }
  495. }
  496. }
  497. }
  498. public IEnumerable<BaseItem> GetItemsOfType(Type type)
  499. {
  500. if (type == null)
  501. {
  502. throw new ArgumentNullException("type");
  503. }
  504. CheckDisposed();
  505. using (var cmd = _connection.CreateCommand())
  506. {
  507. cmd.CommandText = "select type,data from TypedBaseItems where type = @type";
  508. cmd.Parameters.Add(cmd, "@type", DbType.String).Value = type.FullName;
  509. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  510. {
  511. while (reader.Read())
  512. {
  513. var item = GetItem(reader);
  514. if (item != null)
  515. {
  516. yield return item;
  517. }
  518. }
  519. }
  520. }
  521. }
  522. public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
  523. {
  524. if (query == null)
  525. {
  526. throw new ArgumentNullException("query");
  527. }
  528. CheckDisposed();
  529. using (var cmd = _connection.CreateCommand())
  530. {
  531. cmd.CommandText = "select type,data from TypedBaseItems";
  532. var whereClauses = GetWhereClauses(query, cmd, false);
  533. var whereTextWithoutPaging = whereClauses.Count == 0 ?
  534. string.Empty :
  535. " where " + string.Join(" AND ", whereClauses.ToArray());
  536. whereClauses = GetWhereClauses(query, cmd, true);
  537. var whereText = whereClauses.Count == 0 ?
  538. string.Empty :
  539. " where " + string.Join(" AND ", whereClauses.ToArray());
  540. cmd.CommandText += whereText;
  541. if (query.Limit.HasValue)
  542. {
  543. cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
  544. }
  545. cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
  546. var list = new List<BaseItem>();
  547. var count = 0;
  548. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
  549. {
  550. while (reader.Read())
  551. {
  552. list.Add(GetItem(reader));
  553. }
  554. if (reader.NextResult() && reader.Read())
  555. {
  556. count = reader.GetInt32(0);
  557. }
  558. }
  559. return new QueryResult<BaseItem>()
  560. {
  561. Items = list.ToArray(),
  562. TotalRecordCount = count
  563. };
  564. }
  565. }
  566. public List<Guid> GetItemIdsList(InternalItemsQuery query)
  567. {
  568. if (query == null)
  569. {
  570. throw new ArgumentNullException("query");
  571. }
  572. CheckDisposed();
  573. using (var cmd = _connection.CreateCommand())
  574. {
  575. cmd.CommandText = "select guid from TypedBaseItems";
  576. var whereClauses = GetWhereClauses(query, cmd, false);
  577. whereClauses = GetWhereClauses(query, cmd, true);
  578. var whereText = whereClauses.Count == 0 ?
  579. string.Empty :
  580. " where " + string.Join(" AND ", whereClauses.ToArray());
  581. cmd.CommandText += whereText;
  582. if (query.Limit.HasValue)
  583. {
  584. cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
  585. }
  586. var list = new List<Guid>();
  587. _logger.Debug(cmd.CommandText);
  588. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
  589. {
  590. while (reader.Read())
  591. {
  592. list.Add(reader.GetGuid(0));
  593. }
  594. }
  595. return list;
  596. }
  597. }
  598. public QueryResult<Guid> GetItemIds(InternalItemsQuery query)
  599. {
  600. if (query == null)
  601. {
  602. throw new ArgumentNullException("query");
  603. }
  604. CheckDisposed();
  605. using (var cmd = _connection.CreateCommand())
  606. {
  607. cmd.CommandText = "select guid from TypedBaseItems";
  608. var whereClauses = GetWhereClauses(query, cmd, false);
  609. var whereTextWithoutPaging = whereClauses.Count == 0 ?
  610. string.Empty :
  611. " where " + string.Join(" AND ", whereClauses.ToArray());
  612. whereClauses = GetWhereClauses(query, cmd, true);
  613. var whereText = whereClauses.Count == 0 ?
  614. string.Empty :
  615. " where " + string.Join(" AND ", whereClauses.ToArray());
  616. cmd.CommandText += whereText;
  617. if (query.Limit.HasValue)
  618. {
  619. cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
  620. }
  621. cmd.CommandText += "; select count (guid) from TypedBaseItems" + whereTextWithoutPaging;
  622. var list = new List<Guid>();
  623. var count = 0;
  624. _logger.Debug(cmd.CommandText);
  625. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
  626. {
  627. while (reader.Read())
  628. {
  629. list.Add(reader.GetGuid(0));
  630. }
  631. if (reader.NextResult() && reader.Read())
  632. {
  633. count = reader.GetInt32(0);
  634. }
  635. }
  636. return new QueryResult<Guid>()
  637. {
  638. Items = list.ToArray(),
  639. TotalRecordCount = count
  640. };
  641. }
  642. }
  643. private List<string> GetWhereClauses(InternalItemsQuery query, IDbCommand cmd, bool addPaging)
  644. {
  645. var whereClauses = new List<string>();
  646. if (query.IsMovie.HasValue)
  647. {
  648. whereClauses.Add("IsMovie=@IsMovie");
  649. cmd.Parameters.Add(cmd, "@IsMovie", DbType.Boolean).Value = query.IsMovie;
  650. }
  651. if (query.IsKids.HasValue)
  652. {
  653. whereClauses.Add("IsKids=@IsKids");
  654. cmd.Parameters.Add(cmd, "@IsKids", DbType.Boolean).Value = query.IsKids;
  655. }
  656. if (query.IsSports.HasValue)
  657. {
  658. whereClauses.Add("IsSports=@IsSports");
  659. cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports;
  660. }
  661. var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
  662. if (includeTypes.Length == 1)
  663. {
  664. whereClauses.Add("type=@type");
  665. cmd.Parameters.Add(cmd, "@type", DbType.String).Value = includeTypes[0];
  666. }
  667. if (includeTypes.Length > 1)
  668. {
  669. var inClause = string.Join(",", includeTypes.Select(i => "'" + i + "'").ToArray());
  670. whereClauses.Add(string.Format("type in ({0})", inClause));
  671. }
  672. if (query.ChannelIds.Length == 1)
  673. {
  674. whereClauses.Add("ChannelId=@ChannelId");
  675. cmd.Parameters.Add(cmd, "@ChannelId", DbType.String).Value = query.ChannelIds[0];
  676. }
  677. if (query.ChannelIds.Length > 1)
  678. {
  679. var inClause = string.Join(",", query.ChannelIds.Select(i => "'" + i + "'").ToArray());
  680. whereClauses.Add(string.Format("ChannelId in ({0})", inClause));
  681. }
  682. if (query.MinEndDate.HasValue)
  683. {
  684. whereClauses.Add("EndDate>=@MinEndDate");
  685. cmd.Parameters.Add(cmd, "@MinEndDate", DbType.Date).Value = query.MinEndDate.Value;
  686. }
  687. if (query.MaxEndDate.HasValue)
  688. {
  689. whereClauses.Add("EndDate<=@MaxEndDate");
  690. cmd.Parameters.Add(cmd, "@MaxEndDate", DbType.Date).Value = query.MaxEndDate.Value;
  691. }
  692. if (query.MinStartDate.HasValue)
  693. {
  694. whereClauses.Add("StartDate>=@MinStartDate");
  695. cmd.Parameters.Add(cmd, "@MinStartDate", DbType.Date).Value = query.MinStartDate.Value;
  696. }
  697. if (query.MaxStartDate.HasValue)
  698. {
  699. whereClauses.Add("StartDate<=@MaxStartDate");
  700. cmd.Parameters.Add(cmd, "@MaxStartDate", DbType.Date).Value = query.MaxStartDate.Value;
  701. }
  702. if (query.IsAiring.HasValue)
  703. {
  704. if (query.IsAiring.Value)
  705. {
  706. whereClauses.Add("StartDate<=@MaxStartDate");
  707. cmd.Parameters.Add(cmd, "@MaxStartDate", DbType.Date).Value = DateTime.UtcNow;
  708. whereClauses.Add("EndDate>=@MinEndDate");
  709. cmd.Parameters.Add(cmd, "@MinEndDate", DbType.Date).Value = DateTime.UtcNow;
  710. }
  711. else
  712. {
  713. whereClauses.Add("(StartDate>@IsAiringDate OR EndDate < @IsAiringDate)");
  714. cmd.Parameters.Add(cmd, "@IsAiringDate", DbType.Date).Value = DateTime.UtcNow;
  715. }
  716. }
  717. if (addPaging)
  718. {
  719. if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
  720. {
  721. var pagingWhereText = whereClauses.Count == 0 ?
  722. string.Empty :
  723. " where " + string.Join(" AND ", whereClauses.ToArray());
  724. whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM TypedBaseItems {0} ORDER BY DateCreated DESC LIMIT {1})",
  725. pagingWhereText,
  726. query.StartIndex.Value.ToString(CultureInfo.InvariantCulture)));
  727. }
  728. }
  729. return whereClauses;
  730. }
  731. // Not crazy about having this all the way down here, but at least it's in one place
  732. readonly Dictionary<string, string[]> _types = new Dictionary<string, string[]>(StringComparer.OrdinalIgnoreCase)
  733. {
  734. {typeof(LiveTvProgram).Name, new []{typeof(LiveTvProgram).FullName}},
  735. {typeof(LiveTvChannel).Name, new []{typeof(LiveTvChannel).FullName}},
  736. {typeof(LiveTvVideoRecording).Name, new []{typeof(LiveTvVideoRecording).FullName}},
  737. {typeof(LiveTvAudioRecording).Name, new []{typeof(LiveTvAudioRecording).FullName}},
  738. {"Recording", new []{typeof(LiveTvAudioRecording).FullName, typeof(LiveTvVideoRecording).FullName}}
  739. };
  740. private IEnumerable<string> MapIncludeItemTypes(string value)
  741. {
  742. string[] result;
  743. if (_types.TryGetValue(value, out result))
  744. {
  745. return result;
  746. }
  747. return new[] { value };
  748. }
  749. public IEnumerable<Guid> GetItemIdsOfType(Type type)
  750. {
  751. if (type == null)
  752. {
  753. throw new ArgumentNullException("type");
  754. }
  755. CheckDisposed();
  756. using (var cmd = _connection.CreateCommand())
  757. {
  758. cmd.CommandText = "select guid from TypedBaseItems where type = @type";
  759. cmd.Parameters.Add(cmd, "@type", DbType.String).Value = type.FullName;
  760. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  761. {
  762. while (reader.Read())
  763. {
  764. yield return reader.GetGuid(0);
  765. }
  766. }
  767. }
  768. }
  769. public async Task DeleteItem(Guid id, CancellationToken cancellationToken)
  770. {
  771. if (id == Guid.Empty)
  772. {
  773. throw new ArgumentNullException("id");
  774. }
  775. CheckDisposed();
  776. await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  777. IDbTransaction transaction = null;
  778. try
  779. {
  780. transaction = _connection.BeginTransaction();
  781. // First delete children
  782. _deleteChildrenCommand.GetParameter(0).Value = id;
  783. _deleteChildrenCommand.Transaction = transaction;
  784. _deleteChildrenCommand.ExecuteNonQuery();
  785. // Delete the item
  786. _deleteItemCommand.GetParameter(0).Value = id;
  787. _deleteItemCommand.Transaction = transaction;
  788. _deleteItemCommand.ExecuteNonQuery();
  789. transaction.Commit();
  790. }
  791. catch (OperationCanceledException)
  792. {
  793. if (transaction != null)
  794. {
  795. transaction.Rollback();
  796. }
  797. throw;
  798. }
  799. catch (Exception e)
  800. {
  801. _logger.ErrorException("Failed to save children:", e);
  802. if (transaction != null)
  803. {
  804. transaction.Rollback();
  805. }
  806. throw;
  807. }
  808. finally
  809. {
  810. if (transaction != null)
  811. {
  812. transaction.Dispose();
  813. }
  814. _writeLock.Release();
  815. }
  816. }
  817. public async Task SaveChildren(Guid parentId, IEnumerable<Guid> children, CancellationToken cancellationToken)
  818. {
  819. if (parentId == Guid.Empty)
  820. {
  821. throw new ArgumentNullException("parentId");
  822. }
  823. if (children == null)
  824. {
  825. throw new ArgumentNullException("children");
  826. }
  827. CheckDisposed();
  828. await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  829. IDbTransaction transaction = null;
  830. try
  831. {
  832. transaction = _connection.BeginTransaction();
  833. // First delete
  834. _deleteChildrenCommand.GetParameter(0).Value = parentId;
  835. _deleteChildrenCommand.Transaction = transaction;
  836. _deleteChildrenCommand.ExecuteNonQuery();
  837. foreach (var id in children)
  838. {
  839. cancellationToken.ThrowIfCancellationRequested();
  840. _saveChildrenCommand.GetParameter(0).Value = parentId;
  841. _saveChildrenCommand.GetParameter(1).Value = id;
  842. _saveChildrenCommand.Transaction = transaction;
  843. _saveChildrenCommand.ExecuteNonQuery();
  844. }
  845. transaction.Commit();
  846. }
  847. catch (OperationCanceledException)
  848. {
  849. if (transaction != null)
  850. {
  851. transaction.Rollback();
  852. }
  853. throw;
  854. }
  855. catch (Exception e)
  856. {
  857. _logger.ErrorException("Failed to save children:", e);
  858. if (transaction != null)
  859. {
  860. transaction.Rollback();
  861. }
  862. throw;
  863. }
  864. finally
  865. {
  866. if (transaction != null)
  867. {
  868. transaction.Dispose();
  869. }
  870. _writeLock.Release();
  871. }
  872. }
  873. public IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query)
  874. {
  875. CheckDisposed();
  876. return _mediaStreamsRepository.GetMediaStreams(query);
  877. }
  878. public Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken)
  879. {
  880. CheckDisposed();
  881. return _mediaStreamsRepository.SaveMediaStreams(id, streams, cancellationToken);
  882. }
  883. }
  884. }