SqliteMediaStreamsRepository.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. using System.Globalization;
  2. using MediaBrowser.Controller.Persistence;
  3. using MediaBrowser.Model.Entities;
  4. using MediaBrowser.Model.Logging;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Data;
  8. using System.Linq;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace MediaBrowser.Server.Implementations.Persistence
  13. {
  14. class SqliteMediaStreamsRepository
  15. {
  16. private IDbConnection _connection;
  17. private readonly ILogger _logger;
  18. private IDbCommand _deleteStreamsCommand;
  19. private IDbCommand _saveStreamCommand;
  20. public SqliteMediaStreamsRepository(IDbConnection connection, ILogManager logManager)
  21. {
  22. _connection = connection;
  23. _logger = logManager.GetLogger(GetType().Name);
  24. }
  25. /// <summary>
  26. /// Opens the connection to the database
  27. /// </summary>
  28. /// <returns>Task.</returns>
  29. public void Initialize()
  30. {
  31. var createTableCommand
  32. = "create table if not exists mediastreams ";
  33. // Add PixelFormat column
  34. createTableCommand += "(ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, IsCabac BIT NULL, KeyFrames TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
  35. string[] queries = {
  36. createTableCommand,
  37. "create index if not exists idx_mediastreams on mediastreams(ItemId, StreamIndex)",
  38. //pragmas
  39. "pragma temp_store = memory",
  40. "pragma shrink_memory"
  41. };
  42. _connection.RunQueries(queries, _logger);
  43. AddPixelFormatColumnCommand();
  44. AddBitDepthCommand();
  45. AddIsAnamorphicColumn();
  46. AddIsCabacColumn();
  47. AddKeyFramesColumn();
  48. AddRefFramesCommand();
  49. PrepareStatements();
  50. }
  51. private void AddPixelFormatColumnCommand()
  52. {
  53. using (var cmd = _connection.CreateCommand())
  54. {
  55. cmd.CommandText = "PRAGMA table_info(mediastreams)";
  56. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  57. {
  58. while (reader.Read())
  59. {
  60. if (!reader.IsDBNull(1))
  61. {
  62. var name = reader.GetString(1);
  63. if (string.Equals(name, "PixelFormat", StringComparison.OrdinalIgnoreCase))
  64. {
  65. return;
  66. }
  67. }
  68. }
  69. }
  70. }
  71. var builder = new StringBuilder();
  72. builder.AppendLine("alter table mediastreams");
  73. builder.AppendLine("add column PixelFormat TEXT");
  74. _connection.RunQueries(new[] { builder.ToString() }, _logger);
  75. }
  76. private void AddBitDepthCommand()
  77. {
  78. using (var cmd = _connection.CreateCommand())
  79. {
  80. cmd.CommandText = "PRAGMA table_info(mediastreams)";
  81. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  82. {
  83. while (reader.Read())
  84. {
  85. if (!reader.IsDBNull(1))
  86. {
  87. var name = reader.GetString(1);
  88. if (string.Equals(name, "BitDepth", StringComparison.OrdinalIgnoreCase))
  89. {
  90. return;
  91. }
  92. }
  93. }
  94. }
  95. }
  96. var builder = new StringBuilder();
  97. builder.AppendLine("alter table mediastreams");
  98. builder.AppendLine("add column BitDepth INT NULL");
  99. _connection.RunQueries(new[] { builder.ToString() }, _logger);
  100. }
  101. private void AddRefFramesCommand()
  102. {
  103. using (var cmd = _connection.CreateCommand())
  104. {
  105. cmd.CommandText = "PRAGMA table_info(mediastreams)";
  106. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  107. {
  108. while (reader.Read())
  109. {
  110. if (!reader.IsDBNull(1))
  111. {
  112. var name = reader.GetString(1);
  113. if (string.Equals(name, "RefFrames", StringComparison.OrdinalIgnoreCase))
  114. {
  115. return;
  116. }
  117. }
  118. }
  119. }
  120. }
  121. var builder = new StringBuilder();
  122. builder.AppendLine("alter table mediastreams");
  123. builder.AppendLine("add column RefFrames INT NULL");
  124. _connection.RunQueries(new[] { builder.ToString() }, _logger);
  125. }
  126. private void AddIsCabacColumn()
  127. {
  128. using (var cmd = _connection.CreateCommand())
  129. {
  130. cmd.CommandText = "PRAGMA table_info(mediastreams)";
  131. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  132. {
  133. while (reader.Read())
  134. {
  135. if (!reader.IsDBNull(1))
  136. {
  137. var name = reader.GetString(1);
  138. if (string.Equals(name, "IsCabac", StringComparison.OrdinalIgnoreCase))
  139. {
  140. return;
  141. }
  142. }
  143. }
  144. }
  145. }
  146. var builder = new StringBuilder();
  147. builder.AppendLine("alter table mediastreams");
  148. builder.AppendLine("add column IsCabac BIT NULL");
  149. _connection.RunQueries(new[] { builder.ToString() }, _logger);
  150. }
  151. private void AddKeyFramesColumn()
  152. {
  153. using (var cmd = _connection.CreateCommand())
  154. {
  155. cmd.CommandText = "PRAGMA table_info(mediastreams)";
  156. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  157. {
  158. while (reader.Read())
  159. {
  160. if (!reader.IsDBNull(1))
  161. {
  162. var name = reader.GetString(1);
  163. if (string.Equals(name, "KeyFrames", StringComparison.OrdinalIgnoreCase))
  164. {
  165. return;
  166. }
  167. }
  168. }
  169. }
  170. }
  171. var builder = new StringBuilder();
  172. builder.AppendLine("alter table mediastreams");
  173. builder.AppendLine("add column KeyFrames TEXT NULL");
  174. _connection.RunQueries(new[] { builder.ToString() }, _logger);
  175. }
  176. private void AddIsAnamorphicColumn()
  177. {
  178. using (var cmd = _connection.CreateCommand())
  179. {
  180. cmd.CommandText = "PRAGMA table_info(mediastreams)";
  181. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  182. {
  183. while (reader.Read())
  184. {
  185. if (!reader.IsDBNull(1))
  186. {
  187. var name = reader.GetString(1);
  188. if (string.Equals(name, "IsAnamorphic", StringComparison.OrdinalIgnoreCase))
  189. {
  190. return;
  191. }
  192. }
  193. }
  194. }
  195. }
  196. var builder = new StringBuilder();
  197. builder.AppendLine("alter table mediastreams");
  198. builder.AppendLine("add column IsAnamorphic BIT NULL");
  199. _connection.RunQueries(new[] { builder.ToString() }, _logger);
  200. }
  201. private readonly string[] _saveColumns =
  202. {
  203. "ItemId",
  204. "StreamIndex",
  205. "StreamType",
  206. "Codec",
  207. "Language",
  208. "ChannelLayout",
  209. "Profile",
  210. "AspectRatio",
  211. "Path",
  212. "IsInterlaced",
  213. "BitRate",
  214. "Channels",
  215. "SampleRate",
  216. "IsDefault",
  217. "IsForced",
  218. "IsExternal",
  219. "Height",
  220. "Width",
  221. "AverageFrameRate",
  222. "RealFrameRate",
  223. "Level",
  224. "PixelFormat",
  225. "BitDepth",
  226. "IsAnamorphic",
  227. "RefFrames",
  228. "IsCabac",
  229. "KeyFrames"
  230. };
  231. /// <summary>
  232. /// The _write lock
  233. /// </summary>
  234. private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
  235. /// <summary>
  236. /// Prepares the statements.
  237. /// </summary>
  238. private void PrepareStatements()
  239. {
  240. _deleteStreamsCommand = _connection.CreateCommand();
  241. _deleteStreamsCommand.CommandText = "delete from mediastreams where ItemId=@ItemId";
  242. _deleteStreamsCommand.Parameters.Add(_deleteStreamsCommand, "@ItemId");
  243. _saveStreamCommand = _connection.CreateCommand();
  244. _saveStreamCommand.CommandText = string.Format("replace into mediastreams ({0}) values ({1})",
  245. string.Join(",", _saveColumns),
  246. string.Join(",", _saveColumns.Select(i => "@" + i).ToArray()));
  247. foreach (var col in _saveColumns)
  248. {
  249. _saveStreamCommand.Parameters.Add(_saveStreamCommand, "@" + col);
  250. }
  251. }
  252. public IEnumerable<MediaStream> GetMediaStreams(MediaStreamQuery query)
  253. {
  254. if (query == null)
  255. {
  256. throw new ArgumentNullException("query");
  257. }
  258. using (var cmd = _connection.CreateCommand())
  259. {
  260. var cmdText = "select " + string.Join(",", _saveColumns) + " from mediastreams where";
  261. cmdText += " ItemId=@ItemId";
  262. cmd.Parameters.Add(cmd, "@ItemId", DbType.Guid).Value = query.ItemId;
  263. if (query.Type.HasValue)
  264. {
  265. cmdText += " AND StreamType=@StreamType";
  266. cmd.Parameters.Add(cmd, "@StreamType", DbType.String).Value = query.Type.Value.ToString();
  267. }
  268. if (query.Index.HasValue)
  269. {
  270. cmdText += " AND StreamIndex=@StreamIndex";
  271. cmd.Parameters.Add(cmd, "@StreamIndex", DbType.Int32).Value = query.Index.Value;
  272. }
  273. cmdText += " order by StreamIndex ASC";
  274. cmd.CommandText = cmdText;
  275. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  276. {
  277. while (reader.Read())
  278. {
  279. yield return GetMediaStream(reader);
  280. }
  281. }
  282. }
  283. }
  284. /// <summary>
  285. /// Gets the chapter.
  286. /// </summary>
  287. /// <param name="reader">The reader.</param>
  288. /// <returns>ChapterInfo.</returns>
  289. private MediaStream GetMediaStream(IDataReader reader)
  290. {
  291. var item = new MediaStream
  292. {
  293. Index = reader.GetInt32(1)
  294. };
  295. item.Type = (MediaStreamType)Enum.Parse(typeof(MediaStreamType), reader.GetString(2), true);
  296. if (!reader.IsDBNull(3))
  297. {
  298. item.Codec = reader.GetString(3);
  299. }
  300. if (!reader.IsDBNull(4))
  301. {
  302. item.Language = reader.GetString(4);
  303. }
  304. if (!reader.IsDBNull(5))
  305. {
  306. item.ChannelLayout = reader.GetString(5);
  307. }
  308. if (!reader.IsDBNull(6))
  309. {
  310. item.Profile = reader.GetString(6);
  311. }
  312. if (!reader.IsDBNull(7))
  313. {
  314. item.AspectRatio = reader.GetString(7);
  315. }
  316. if (!reader.IsDBNull(8))
  317. {
  318. item.Path = reader.GetString(8);
  319. }
  320. item.IsInterlaced = reader.GetBoolean(9);
  321. if (!reader.IsDBNull(10))
  322. {
  323. item.BitRate = reader.GetInt32(10);
  324. }
  325. if (!reader.IsDBNull(11))
  326. {
  327. item.Channels = reader.GetInt32(11);
  328. }
  329. if (!reader.IsDBNull(12))
  330. {
  331. item.SampleRate = reader.GetInt32(12);
  332. }
  333. item.IsDefault = reader.GetBoolean(13);
  334. item.IsForced = reader.GetBoolean(14);
  335. item.IsExternal = reader.GetBoolean(15);
  336. if (!reader.IsDBNull(16))
  337. {
  338. item.Width = reader.GetInt32(16);
  339. }
  340. if (!reader.IsDBNull(17))
  341. {
  342. item.Height = reader.GetInt32(17);
  343. }
  344. if (!reader.IsDBNull(18))
  345. {
  346. item.AverageFrameRate = reader.GetFloat(18);
  347. }
  348. if (!reader.IsDBNull(19))
  349. {
  350. item.RealFrameRate = reader.GetFloat(19);
  351. }
  352. if (!reader.IsDBNull(20))
  353. {
  354. item.Level = reader.GetFloat(20);
  355. }
  356. if (!reader.IsDBNull(21))
  357. {
  358. item.PixelFormat = reader.GetString(21);
  359. }
  360. if (!reader.IsDBNull(22))
  361. {
  362. item.BitDepth = reader.GetInt32(22);
  363. }
  364. if (!reader.IsDBNull(23))
  365. {
  366. item.IsAnamorphic = reader.GetBoolean(23);
  367. }
  368. if (!reader.IsDBNull(24))
  369. {
  370. item.RefFrames = reader.GetInt32(24);
  371. }
  372. if (!reader.IsDBNull(25))
  373. {
  374. item.IsCabac = reader.GetBoolean(25);
  375. }
  376. if (!reader.IsDBNull(26))
  377. {
  378. var frames = reader.GetString(26);
  379. if (!string.IsNullOrWhiteSpace(frames))
  380. {
  381. item.KeyFrames = frames.Split(',').Select(i => int.Parse(i, CultureInfo.InvariantCulture)).ToList();
  382. }
  383. }
  384. return item;
  385. }
  386. public async Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken)
  387. {
  388. if (id == Guid.Empty)
  389. {
  390. throw new ArgumentNullException("id");
  391. }
  392. if (streams == null)
  393. {
  394. throw new ArgumentNullException("streams");
  395. }
  396. cancellationToken.ThrowIfCancellationRequested();
  397. await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  398. IDbTransaction transaction = null;
  399. try
  400. {
  401. transaction = _connection.BeginTransaction();
  402. // First delete chapters
  403. _deleteStreamsCommand.GetParameter(0).Value = id;
  404. _deleteStreamsCommand.Transaction = transaction;
  405. _deleteStreamsCommand.ExecuteNonQuery();
  406. foreach (var stream in streams)
  407. {
  408. cancellationToken.ThrowIfCancellationRequested();
  409. var index = 0;
  410. _saveStreamCommand.GetParameter(index++).Value = id;
  411. _saveStreamCommand.GetParameter(index++).Value = stream.Index;
  412. _saveStreamCommand.GetParameter(index++).Value = stream.Type.ToString();
  413. _saveStreamCommand.GetParameter(index++).Value = stream.Codec;
  414. _saveStreamCommand.GetParameter(index++).Value = stream.Language;
  415. _saveStreamCommand.GetParameter(index++).Value = stream.ChannelLayout;
  416. _saveStreamCommand.GetParameter(index++).Value = stream.Profile;
  417. _saveStreamCommand.GetParameter(index++).Value = stream.AspectRatio;
  418. _saveStreamCommand.GetParameter(index++).Value = stream.Path;
  419. _saveStreamCommand.GetParameter(index++).Value = stream.IsInterlaced;
  420. _saveStreamCommand.GetParameter(index++).Value = stream.BitRate;
  421. _saveStreamCommand.GetParameter(index++).Value = stream.Channels;
  422. _saveStreamCommand.GetParameter(index++).Value = stream.SampleRate;
  423. _saveStreamCommand.GetParameter(index++).Value = stream.IsDefault;
  424. _saveStreamCommand.GetParameter(index++).Value = stream.IsForced;
  425. _saveStreamCommand.GetParameter(index++).Value = stream.IsExternal;
  426. _saveStreamCommand.GetParameter(index++).Value = stream.Width;
  427. _saveStreamCommand.GetParameter(index++).Value = stream.Height;
  428. _saveStreamCommand.GetParameter(index++).Value = stream.AverageFrameRate;
  429. _saveStreamCommand.GetParameter(index++).Value = stream.RealFrameRate;
  430. _saveStreamCommand.GetParameter(index++).Value = stream.Level;
  431. _saveStreamCommand.GetParameter(index++).Value = stream.PixelFormat;
  432. _saveStreamCommand.GetParameter(index++).Value = stream.BitDepth;
  433. _saveStreamCommand.GetParameter(index++).Value = stream.IsAnamorphic;
  434. _saveStreamCommand.GetParameter(index++).Value = stream.RefFrames;
  435. _saveStreamCommand.GetParameter(index++).Value = stream.IsCabac;
  436. if (stream.KeyFrames == null || stream.KeyFrames.Count == 0)
  437. {
  438. _saveStreamCommand.GetParameter(index++).Value = null;
  439. }
  440. else
  441. {
  442. _saveStreamCommand.GetParameter(index++).Value = string.Join(",", stream.KeyFrames.Select(i => i.ToString(CultureInfo.InvariantCulture)).ToArray());
  443. }
  444. _saveStreamCommand.Transaction = transaction;
  445. _saveStreamCommand.ExecuteNonQuery();
  446. }
  447. transaction.Commit();
  448. }
  449. catch (OperationCanceledException)
  450. {
  451. if (transaction != null)
  452. {
  453. transaction.Rollback();
  454. }
  455. throw;
  456. }
  457. catch (Exception e)
  458. {
  459. _logger.ErrorException("Failed to save media streams:", e);
  460. if (transaction != null)
  461. {
  462. transaction.Rollback();
  463. }
  464. throw;
  465. }
  466. finally
  467. {
  468. if (transaction != null)
  469. {
  470. transaction.Dispose();
  471. }
  472. _writeLock.Release();
  473. }
  474. }
  475. /// <summary>
  476. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  477. /// </summary>
  478. public void Dispose()
  479. {
  480. Dispose(true);
  481. GC.SuppressFinalize(this);
  482. }
  483. private readonly object _disposeLock = new object();
  484. /// <summary>
  485. /// Releases unmanaged and - optionally - managed resources.
  486. /// </summary>
  487. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  488. protected virtual void Dispose(bool dispose)
  489. {
  490. if (dispose)
  491. {
  492. try
  493. {
  494. lock (_disposeLock)
  495. {
  496. if (_connection != null)
  497. {
  498. if (_connection.IsOpen())
  499. {
  500. _connection.Close();
  501. }
  502. _connection.Dispose();
  503. _connection = null;
  504. }
  505. }
  506. }
  507. catch (Exception ex)
  508. {
  509. _logger.ErrorException("Error disposing database", ex);
  510. }
  511. }
  512. }
  513. }
  514. }