SyncRepository.cs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  1. using MediaBrowser.Controller;
  2. using MediaBrowser.Controller.Sync;
  3. using MediaBrowser.Model.Dto;
  4. using MediaBrowser.Model.Logging;
  5. using MediaBrowser.Model.Querying;
  6. using MediaBrowser.Model.Serialization;
  7. using MediaBrowser.Model.Sync;
  8. using MediaBrowser.Server.Implementations.Persistence;
  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.Tasks;
  16. namespace MediaBrowser.Server.Implementations.Sync
  17. {
  18. public class SyncRepository : BaseSqliteRepository, ISyncRepository
  19. {
  20. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  21. private readonly IJsonSerializer _json;
  22. public SyncRepository(ILogManager logManager, IJsonSerializer json, IServerApplicationPaths appPaths, IDbConnector connector)
  23. : base(logManager, connector)
  24. {
  25. _json = json;
  26. DbFilePath = Path.Combine(appPaths.DataPath, "sync14.db");
  27. }
  28. private class SyncSummary
  29. {
  30. public Dictionary<string, int> Items { get; set; }
  31. public SyncSummary()
  32. {
  33. Items = new Dictionary<string, int>();
  34. }
  35. }
  36. public async Task Initialize()
  37. {
  38. using (var connection = await CreateConnection().ConfigureAwait(false))
  39. {
  40. string[] queries = {
  41. "create table if not exists SyncJobs (Id GUID PRIMARY KEY, TargetId TEXT NOT NULL, Name TEXT NOT NULL, Profile TEXT, Quality TEXT, Bitrate INT, Status TEXT NOT NULL, Progress FLOAT, UserId TEXT NOT NULL, ItemIds TEXT NOT NULL, Category TEXT, ParentId TEXT, UnwatchedOnly BIT, ItemLimit INT, SyncNewContent BIT, DateCreated DateTime, DateLastModified DateTime, ItemCount int)",
  42. "create index if not exists idx_SyncJobs on SyncJobs(Id)",
  43. "create index if not exists idx_SyncJobs1 on SyncJobs(TargetId)",
  44. "create table if not exists SyncJobItems (Id GUID PRIMARY KEY, ItemId TEXT, ItemName TEXT, MediaSourceId TEXT, JobId TEXT, TemporaryPath TEXT, OutputPath TEXT, Status TEXT, TargetId TEXT, DateCreated DateTime, Progress FLOAT, AdditionalFiles TEXT, MediaSource TEXT, IsMarkedForRemoval BIT, JobItemIndex INT, ItemDateModifiedTicks BIGINT)",
  45. "create index if not exists idx_SyncJobItems1 on SyncJobItems(Id)",
  46. "drop index if exists idx_SyncJobItems2",
  47. "drop index if exists idx_SyncJobItems3",
  48. "create index if not exists idx_SyncJobItems4 on SyncJobItems(TargetId,ItemId,Status,Progress,DateCreated)",
  49. "pragma shrink_memory"
  50. };
  51. connection.RunQueries(queries, Logger);
  52. connection.AddColumn(Logger, "SyncJobs", "Profile", "TEXT");
  53. connection.AddColumn(Logger, "SyncJobs", "Bitrate", "INT");
  54. connection.AddColumn(Logger, "SyncJobItems", "ItemDateModifiedTicks", "BIGINT");
  55. }
  56. }
  57. private const string BaseJobSelectText = "select Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount from SyncJobs";
  58. private const string BaseJobItemSelectText = "select Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks from SyncJobItems";
  59. public SyncJob GetJob(string id)
  60. {
  61. if (string.IsNullOrEmpty(id))
  62. {
  63. throw new ArgumentNullException("id");
  64. }
  65. CheckDisposed();
  66. var guid = new Guid(id);
  67. if (guid == Guid.Empty)
  68. {
  69. throw new ArgumentNullException("id");
  70. }
  71. using (var connection = CreateConnection(true).Result)
  72. {
  73. using (var cmd = connection.CreateCommand())
  74. {
  75. cmd.CommandText = BaseJobSelectText + " where Id=@Id";
  76. cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
  77. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
  78. {
  79. if (reader.Read())
  80. {
  81. return GetJob(reader);
  82. }
  83. }
  84. }
  85. return null;
  86. }
  87. }
  88. private SyncJob GetJob(IDataReader reader)
  89. {
  90. var info = new SyncJob
  91. {
  92. Id = reader.GetGuid(0).ToString("N"),
  93. TargetId = reader.GetString(1),
  94. Name = reader.GetString(2)
  95. };
  96. if (!reader.IsDBNull(3))
  97. {
  98. info.Profile = reader.GetString(3);
  99. }
  100. if (!reader.IsDBNull(4))
  101. {
  102. info.Quality = reader.GetString(4);
  103. }
  104. if (!reader.IsDBNull(5))
  105. {
  106. info.Bitrate = reader.GetInt32(5);
  107. }
  108. if (!reader.IsDBNull(6))
  109. {
  110. info.Status = (SyncJobStatus)Enum.Parse(typeof(SyncJobStatus), reader.GetString(6), true);
  111. }
  112. if (!reader.IsDBNull(7))
  113. {
  114. info.Progress = reader.GetDouble(7);
  115. }
  116. if (!reader.IsDBNull(8))
  117. {
  118. info.UserId = reader.GetString(8);
  119. }
  120. if (!reader.IsDBNull(9))
  121. {
  122. info.RequestedItemIds = reader.GetString(9).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList();
  123. }
  124. if (!reader.IsDBNull(10))
  125. {
  126. info.Category = (SyncCategory)Enum.Parse(typeof(SyncCategory), reader.GetString(10), true);
  127. }
  128. if (!reader.IsDBNull(11))
  129. {
  130. info.ParentId = reader.GetString(11);
  131. }
  132. if (!reader.IsDBNull(12))
  133. {
  134. info.UnwatchedOnly = reader.GetBoolean(12);
  135. }
  136. if (!reader.IsDBNull(13))
  137. {
  138. info.ItemLimit = reader.GetInt32(13);
  139. }
  140. info.SyncNewContent = reader.GetBoolean(14);
  141. info.DateCreated = reader.GetDateTime(15).ToUniversalTime();
  142. info.DateLastModified = reader.GetDateTime(16).ToUniversalTime();
  143. info.ItemCount = reader.GetInt32(17);
  144. return info;
  145. }
  146. public Task Create(SyncJob job)
  147. {
  148. return InsertOrUpdate(job, true);
  149. }
  150. public Task Update(SyncJob job)
  151. {
  152. return InsertOrUpdate(job, false);
  153. }
  154. private async Task InsertOrUpdate(SyncJob job, bool insert)
  155. {
  156. if (job == null)
  157. {
  158. throw new ArgumentNullException("job");
  159. }
  160. CheckDisposed();
  161. using (var connection = await CreateConnection().ConfigureAwait(false))
  162. {
  163. using (var cmd = connection.CreateCommand())
  164. {
  165. if (insert)
  166. {
  167. cmd.CommandText = "insert into SyncJobs (Id, TargetId, Name, Profile, Quality, Bitrate, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Profile, @Quality, @Bitrate, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)";
  168. cmd.Parameters.Add(cmd, "@Id");
  169. cmd.Parameters.Add(cmd, "@TargetId");
  170. cmd.Parameters.Add(cmd, "@Name");
  171. cmd.Parameters.Add(cmd, "@Profile");
  172. cmd.Parameters.Add(cmd, "@Quality");
  173. cmd.Parameters.Add(cmd, "@Bitrate");
  174. cmd.Parameters.Add(cmd, "@Status");
  175. cmd.Parameters.Add(cmd, "@Progress");
  176. cmd.Parameters.Add(cmd, "@UserId");
  177. cmd.Parameters.Add(cmd, "@ItemIds");
  178. cmd.Parameters.Add(cmd, "@Category");
  179. cmd.Parameters.Add(cmd, "@ParentId");
  180. cmd.Parameters.Add(cmd, "@UnwatchedOnly");
  181. cmd.Parameters.Add(cmd, "@ItemLimit");
  182. cmd.Parameters.Add(cmd, "@SyncNewContent");
  183. cmd.Parameters.Add(cmd, "@DateCreated");
  184. cmd.Parameters.Add(cmd, "@DateLastModified");
  185. cmd.Parameters.Add(cmd, "@ItemCount");
  186. }
  187. else
  188. {
  189. cmd.CommandText = "update SyncJobs set TargetId=@TargetId,Name=@Name,Profile=@Profile,Quality=@Quality,Bitrate=@Bitrate,Status=@Status,Progress=@Progress,UserId=@UserId,ItemIds=@ItemIds,Category=@Category,ParentId=@ParentId,UnwatchedOnly=@UnwatchedOnly,ItemLimit=@ItemLimit,SyncNewContent=@SyncNewContent,DateCreated=@DateCreated,DateLastModified=@DateLastModified,ItemCount=@ItemCount where Id=@Id";
  190. cmd.Parameters.Add(cmd, "@Id");
  191. cmd.Parameters.Add(cmd, "@TargetId");
  192. cmd.Parameters.Add(cmd, "@Name");
  193. cmd.Parameters.Add(cmd, "@Profile");
  194. cmd.Parameters.Add(cmd, "@Quality");
  195. cmd.Parameters.Add(cmd, "@Bitrate");
  196. cmd.Parameters.Add(cmd, "@Status");
  197. cmd.Parameters.Add(cmd, "@Progress");
  198. cmd.Parameters.Add(cmd, "@UserId");
  199. cmd.Parameters.Add(cmd, "@ItemIds");
  200. cmd.Parameters.Add(cmd, "@Category");
  201. cmd.Parameters.Add(cmd, "@ParentId");
  202. cmd.Parameters.Add(cmd, "@UnwatchedOnly");
  203. cmd.Parameters.Add(cmd, "@ItemLimit");
  204. cmd.Parameters.Add(cmd, "@SyncNewContent");
  205. cmd.Parameters.Add(cmd, "@DateCreated");
  206. cmd.Parameters.Add(cmd, "@DateLastModified");
  207. cmd.Parameters.Add(cmd, "@ItemCount");
  208. }
  209. IDbTransaction transaction = null;
  210. try
  211. {
  212. transaction = connection.BeginTransaction();
  213. var index = 0;
  214. cmd.GetParameter(index++).Value = new Guid(job.Id);
  215. cmd.GetParameter(index++).Value = job.TargetId;
  216. cmd.GetParameter(index++).Value = job.Name;
  217. cmd.GetParameter(index++).Value = job.Profile;
  218. cmd.GetParameter(index++).Value = job.Quality;
  219. cmd.GetParameter(index++).Value = job.Bitrate;
  220. cmd.GetParameter(index++).Value = job.Status.ToString();
  221. cmd.GetParameter(index++).Value = job.Progress;
  222. cmd.GetParameter(index++).Value = job.UserId;
  223. cmd.GetParameter(index++).Value = string.Join(",", job.RequestedItemIds.ToArray());
  224. cmd.GetParameter(index++).Value = job.Category;
  225. cmd.GetParameter(index++).Value = job.ParentId;
  226. cmd.GetParameter(index++).Value = job.UnwatchedOnly;
  227. cmd.GetParameter(index++).Value = job.ItemLimit;
  228. cmd.GetParameter(index++).Value = job.SyncNewContent;
  229. cmd.GetParameter(index++).Value = job.DateCreated;
  230. cmd.GetParameter(index++).Value = job.DateLastModified;
  231. cmd.GetParameter(index++).Value = job.ItemCount;
  232. cmd.Transaction = transaction;
  233. cmd.ExecuteNonQuery();
  234. transaction.Commit();
  235. }
  236. catch (OperationCanceledException)
  237. {
  238. if (transaction != null)
  239. {
  240. transaction.Rollback();
  241. }
  242. throw;
  243. }
  244. catch (Exception e)
  245. {
  246. Logger.ErrorException("Failed to save record:", e);
  247. if (transaction != null)
  248. {
  249. transaction.Rollback();
  250. }
  251. throw;
  252. }
  253. finally
  254. {
  255. if (transaction != null)
  256. {
  257. transaction.Dispose();
  258. }
  259. }
  260. }
  261. }
  262. }
  263. public async Task DeleteJob(string id)
  264. {
  265. if (string.IsNullOrWhiteSpace(id))
  266. {
  267. throw new ArgumentNullException("id");
  268. }
  269. CheckDisposed();
  270. using (var connection = await CreateConnection().ConfigureAwait(false))
  271. {
  272. using (var deleteJobCommand = connection.CreateCommand())
  273. {
  274. using (var deleteJobItemsCommand = connection.CreateCommand())
  275. {
  276. IDbTransaction transaction = null;
  277. try
  278. {
  279. // _deleteJobCommand
  280. deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id";
  281. deleteJobCommand.Parameters.Add(deleteJobCommand, "@Id");
  282. transaction = connection.BeginTransaction();
  283. deleteJobCommand.GetParameter(0).Value = new Guid(id);
  284. deleteJobCommand.Transaction = transaction;
  285. deleteJobCommand.ExecuteNonQuery();
  286. // _deleteJobItemsCommand
  287. deleteJobItemsCommand.CommandText = "delete from SyncJobItems where JobId=@JobId";
  288. deleteJobItemsCommand.Parameters.Add(deleteJobItemsCommand, "@JobId");
  289. deleteJobItemsCommand.GetParameter(0).Value = id;
  290. deleteJobItemsCommand.Transaction = transaction;
  291. deleteJobItemsCommand.ExecuteNonQuery();
  292. transaction.Commit();
  293. }
  294. catch (OperationCanceledException)
  295. {
  296. if (transaction != null)
  297. {
  298. transaction.Rollback();
  299. }
  300. throw;
  301. }
  302. catch (Exception e)
  303. {
  304. Logger.ErrorException("Failed to save record:", e);
  305. if (transaction != null)
  306. {
  307. transaction.Rollback();
  308. }
  309. throw;
  310. }
  311. finally
  312. {
  313. if (transaction != null)
  314. {
  315. transaction.Dispose();
  316. }
  317. }
  318. }
  319. }
  320. }
  321. }
  322. public QueryResult<SyncJob> GetJobs(SyncJobQuery query)
  323. {
  324. if (query == null)
  325. {
  326. throw new ArgumentNullException("query");
  327. }
  328. CheckDisposed();
  329. using (var connection = CreateConnection(true).Result)
  330. {
  331. using (var cmd = connection.CreateCommand())
  332. {
  333. cmd.CommandText = BaseJobSelectText;
  334. var whereClauses = new List<string>();
  335. if (query.Statuses.Length > 0)
  336. {
  337. var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
  338. whereClauses.Add(string.Format("Status in ({0})", statuses));
  339. }
  340. if (!string.IsNullOrWhiteSpace(query.TargetId))
  341. {
  342. whereClauses.Add("TargetId=@TargetId");
  343. cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
  344. }
  345. if (!string.IsNullOrWhiteSpace(query.UserId))
  346. {
  347. whereClauses.Add("UserId=@UserId");
  348. cmd.Parameters.Add(cmd, "@UserId", DbType.String).Value = query.UserId;
  349. }
  350. if (query.SyncNewContent.HasValue)
  351. {
  352. whereClauses.Add("SyncNewContent=@SyncNewContent");
  353. cmd.Parameters.Add(cmd, "@SyncNewContent", DbType.Boolean).Value = query.SyncNewContent.Value;
  354. }
  355. cmd.CommandText += " mainTable";
  356. var whereTextWithoutPaging = whereClauses.Count == 0 ?
  357. string.Empty :
  358. " where " + string.Join(" AND ", whereClauses.ToArray());
  359. var startIndex = query.StartIndex ?? 0;
  360. if (startIndex > 0)
  361. {
  362. whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobs ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC LIMIT {0})",
  363. startIndex.ToString(_usCulture)));
  364. }
  365. if (whereClauses.Count > 0)
  366. {
  367. cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
  368. }
  369. cmd.CommandText += " ORDER BY (Select Max(DateLastModified) from SyncJobs where TargetId=mainTable.TargetId) DESC, DateLastModified DESC";
  370. if (query.Limit.HasValue)
  371. {
  372. cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
  373. }
  374. cmd.CommandText += "; select count (Id) from SyncJobs" + whereTextWithoutPaging;
  375. var list = new List<SyncJob>();
  376. var count = 0;
  377. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
  378. {
  379. while (reader.Read())
  380. {
  381. list.Add(GetJob(reader));
  382. }
  383. if (reader.NextResult() && reader.Read())
  384. {
  385. count = reader.GetInt32(0);
  386. }
  387. }
  388. return new QueryResult<SyncJob>()
  389. {
  390. Items = list.ToArray(),
  391. TotalRecordCount = count
  392. };
  393. }
  394. }
  395. }
  396. public SyncJobItem GetJobItem(string id)
  397. {
  398. if (string.IsNullOrEmpty(id))
  399. {
  400. throw new ArgumentNullException("id");
  401. }
  402. CheckDisposed();
  403. var guid = new Guid(id);
  404. using (var connection = CreateConnection(true).Result)
  405. {
  406. using (var cmd = connection.CreateCommand())
  407. {
  408. cmd.CommandText = BaseJobItemSelectText + " where Id=@Id";
  409. cmd.Parameters.Add(cmd, "@Id", DbType.Guid).Value = guid;
  410. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
  411. {
  412. if (reader.Read())
  413. {
  414. return GetJobItem(reader);
  415. }
  416. }
  417. }
  418. return null;
  419. }
  420. }
  421. private QueryResult<T> GetJobItemReader<T>(SyncJobItemQuery query, string baseSelectText, Func<IDataReader, T> itemFactory)
  422. {
  423. if (query == null)
  424. {
  425. throw new ArgumentNullException("query");
  426. }
  427. using (var connection = CreateConnection(true).Result)
  428. {
  429. using (var cmd = connection.CreateCommand())
  430. {
  431. cmd.CommandText = baseSelectText;
  432. var whereClauses = new List<string>();
  433. if (!string.IsNullOrWhiteSpace(query.JobId))
  434. {
  435. whereClauses.Add("JobId=@JobId");
  436. cmd.Parameters.Add(cmd, "@JobId", DbType.String).Value = query.JobId;
  437. }
  438. if (!string.IsNullOrWhiteSpace(query.ItemId))
  439. {
  440. whereClauses.Add("ItemId=@ItemId");
  441. cmd.Parameters.Add(cmd, "@ItemId", DbType.String).Value = query.ItemId;
  442. }
  443. if (!string.IsNullOrWhiteSpace(query.TargetId))
  444. {
  445. whereClauses.Add("TargetId=@TargetId");
  446. cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
  447. }
  448. if (query.Statuses.Length > 0)
  449. {
  450. var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
  451. whereClauses.Add(string.Format("Status in ({0})", statuses));
  452. }
  453. var whereTextWithoutPaging = whereClauses.Count == 0 ?
  454. string.Empty :
  455. " where " + string.Join(" AND ", whereClauses.ToArray());
  456. var startIndex = query.StartIndex ?? 0;
  457. if (startIndex > 0)
  458. {
  459. whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM SyncJobItems ORDER BY JobItemIndex, DateCreated LIMIT {0})",
  460. startIndex.ToString(_usCulture)));
  461. }
  462. if (whereClauses.Count > 0)
  463. {
  464. cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
  465. }
  466. cmd.CommandText += " ORDER BY JobItemIndex, DateCreated";
  467. if (query.Limit.HasValue)
  468. {
  469. cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(_usCulture);
  470. }
  471. cmd.CommandText += "; select count (Id) from SyncJobItems" + whereTextWithoutPaging;
  472. var list = new List<T>();
  473. var count = 0;
  474. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
  475. {
  476. while (reader.Read())
  477. {
  478. list.Add(itemFactory(reader));
  479. }
  480. if (reader.NextResult() && reader.Read())
  481. {
  482. count = reader.GetInt32(0);
  483. }
  484. }
  485. return new QueryResult<T>()
  486. {
  487. Items = list.ToArray(),
  488. TotalRecordCount = count
  489. };
  490. }
  491. }
  492. }
  493. public Dictionary<string, SyncedItemProgress> GetSyncedItemProgresses(SyncJobItemQuery query)
  494. {
  495. var result = new Dictionary<string, SyncedItemProgress>();
  496. using (var connection = CreateConnection(true).Result)
  497. {
  498. using (var cmd = connection.CreateCommand())
  499. {
  500. cmd.CommandText = "select ItemId,Status,Progress from SyncJobItems";
  501. var whereClauses = new List<string>();
  502. if (!string.IsNullOrWhiteSpace(query.TargetId))
  503. {
  504. whereClauses.Add("TargetId=@TargetId");
  505. cmd.Parameters.Add(cmd, "@TargetId", DbType.String).Value = query.TargetId;
  506. }
  507. if (query.Statuses.Length > 0)
  508. {
  509. var statuses = string.Join(",", query.Statuses.Select(i => "'" + i.ToString() + "'").ToArray());
  510. whereClauses.Add(string.Format("Status in ({0})", statuses));
  511. }
  512. if (whereClauses.Count > 0)
  513. {
  514. cmd.CommandText += " where " + string.Join(" AND ", whereClauses.ToArray());
  515. }
  516. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  517. {
  518. while (reader.Read())
  519. {
  520. var itemId = reader.GetString(0);
  521. if (!reader.IsDBNull(1))
  522. {
  523. var status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader.GetString(1), true);
  524. if (status == SyncJobItemStatus.Synced)
  525. {
  526. result[itemId] = new SyncedItemProgress
  527. {
  528. Status = SyncJobItemStatus.Synced
  529. };
  530. }
  531. else
  532. {
  533. SyncedItemProgress currentStatus;
  534. double progress = reader.IsDBNull(2) ? 0.0 : reader.GetDouble(2);
  535. if (!result.TryGetValue(itemId, out currentStatus) || (currentStatus.Status != SyncJobItemStatus.Synced && progress >= currentStatus.Progress))
  536. {
  537. result[itemId] = new SyncedItemProgress
  538. {
  539. Status = status,
  540. Progress = progress
  541. };
  542. }
  543. }
  544. }
  545. }
  546. }
  547. }
  548. }
  549. return result;
  550. }
  551. public QueryResult<SyncJobItem> GetJobItems(SyncJobItemQuery query)
  552. {
  553. return GetJobItemReader(query, BaseJobItemSelectText, GetJobItem);
  554. }
  555. public Task Create(SyncJobItem jobItem)
  556. {
  557. return InsertOrUpdate(jobItem, true);
  558. }
  559. public Task Update(SyncJobItem jobItem)
  560. {
  561. return InsertOrUpdate(jobItem, false);
  562. }
  563. private async Task InsertOrUpdate(SyncJobItem jobItem, bool insert)
  564. {
  565. if (jobItem == null)
  566. {
  567. throw new ArgumentNullException("jobItem");
  568. }
  569. CheckDisposed();
  570. using (var connection = await CreateConnection().ConfigureAwait(false))
  571. {
  572. using (var cmd = connection.CreateCommand())
  573. {
  574. if (insert)
  575. {
  576. cmd.CommandText = "insert into SyncJobItems (Id, ItemId, ItemName, MediaSourceId, JobId, TemporaryPath, OutputPath, Status, TargetId, DateCreated, Progress, AdditionalFiles, MediaSource, IsMarkedForRemoval, JobItemIndex, ItemDateModifiedTicks) values (@Id, @ItemId, @ItemName, @MediaSourceId, @JobId, @TemporaryPath, @OutputPath, @Status, @TargetId, @DateCreated, @Progress, @AdditionalFiles, @MediaSource, @IsMarkedForRemoval, @JobItemIndex, @ItemDateModifiedTicks)";
  577. cmd.Parameters.Add(cmd, "@Id");
  578. cmd.Parameters.Add(cmd, "@ItemId");
  579. cmd.Parameters.Add(cmd, "@ItemName");
  580. cmd.Parameters.Add(cmd, "@MediaSourceId");
  581. cmd.Parameters.Add(cmd, "@JobId");
  582. cmd.Parameters.Add(cmd, "@TemporaryPath");
  583. cmd.Parameters.Add(cmd, "@OutputPath");
  584. cmd.Parameters.Add(cmd, "@Status");
  585. cmd.Parameters.Add(cmd, "@TargetId");
  586. cmd.Parameters.Add(cmd, "@DateCreated");
  587. cmd.Parameters.Add(cmd, "@Progress");
  588. cmd.Parameters.Add(cmd, "@AdditionalFiles");
  589. cmd.Parameters.Add(cmd, "@MediaSource");
  590. cmd.Parameters.Add(cmd, "@IsMarkedForRemoval");
  591. cmd.Parameters.Add(cmd, "@JobItemIndex");
  592. cmd.Parameters.Add(cmd, "@ItemDateModifiedTicks");
  593. }
  594. else
  595. {
  596. // cmd
  597. cmd.CommandText = "update SyncJobItems set ItemId=@ItemId,ItemName=@ItemName,MediaSourceId=@MediaSourceId,JobId=@JobId,TemporaryPath=@TemporaryPath,OutputPath=@OutputPath,Status=@Status,TargetId=@TargetId,DateCreated=@DateCreated,Progress=@Progress,AdditionalFiles=@AdditionalFiles,MediaSource=@MediaSource,IsMarkedForRemoval=@IsMarkedForRemoval,JobItemIndex=@JobItemIndex,ItemDateModifiedTicks=@ItemDateModifiedTicks where Id=@Id";
  598. cmd.Parameters.Add(cmd, "@Id");
  599. cmd.Parameters.Add(cmd, "@ItemId");
  600. cmd.Parameters.Add(cmd, "@ItemName");
  601. cmd.Parameters.Add(cmd, "@MediaSourceId");
  602. cmd.Parameters.Add(cmd, "@JobId");
  603. cmd.Parameters.Add(cmd, "@TemporaryPath");
  604. cmd.Parameters.Add(cmd, "@OutputPath");
  605. cmd.Parameters.Add(cmd, "@Status");
  606. cmd.Parameters.Add(cmd, "@TargetId");
  607. cmd.Parameters.Add(cmd, "@DateCreated");
  608. cmd.Parameters.Add(cmd, "@Progress");
  609. cmd.Parameters.Add(cmd, "@AdditionalFiles");
  610. cmd.Parameters.Add(cmd, "@MediaSource");
  611. cmd.Parameters.Add(cmd, "@IsMarkedForRemoval");
  612. cmd.Parameters.Add(cmd, "@JobItemIndex");
  613. cmd.Parameters.Add(cmd, "@ItemDateModifiedTicks");
  614. }
  615. IDbTransaction transaction = null;
  616. try
  617. {
  618. transaction = connection.BeginTransaction();
  619. var index = 0;
  620. cmd.GetParameter(index++).Value = new Guid(jobItem.Id);
  621. cmd.GetParameter(index++).Value = jobItem.ItemId;
  622. cmd.GetParameter(index++).Value = jobItem.ItemName;
  623. cmd.GetParameter(index++).Value = jobItem.MediaSourceId;
  624. cmd.GetParameter(index++).Value = jobItem.JobId;
  625. cmd.GetParameter(index++).Value = jobItem.TemporaryPath;
  626. cmd.GetParameter(index++).Value = jobItem.OutputPath;
  627. cmd.GetParameter(index++).Value = jobItem.Status.ToString();
  628. cmd.GetParameter(index++).Value = jobItem.TargetId;
  629. cmd.GetParameter(index++).Value = jobItem.DateCreated;
  630. cmd.GetParameter(index++).Value = jobItem.Progress;
  631. cmd.GetParameter(index++).Value = _json.SerializeToString(jobItem.AdditionalFiles);
  632. cmd.GetParameter(index++).Value = jobItem.MediaSource == null ? null : _json.SerializeToString(jobItem.MediaSource);
  633. cmd.GetParameter(index++).Value = jobItem.IsMarkedForRemoval;
  634. cmd.GetParameter(index++).Value = jobItem.JobItemIndex;
  635. cmd.GetParameter(index++).Value = jobItem.ItemDateModifiedTicks;
  636. cmd.Transaction = transaction;
  637. cmd.ExecuteNonQuery();
  638. transaction.Commit();
  639. }
  640. catch (OperationCanceledException)
  641. {
  642. if (transaction != null)
  643. {
  644. transaction.Rollback();
  645. }
  646. throw;
  647. }
  648. catch (Exception e)
  649. {
  650. Logger.ErrorException("Failed to save record:", e);
  651. if (transaction != null)
  652. {
  653. transaction.Rollback();
  654. }
  655. throw;
  656. }
  657. finally
  658. {
  659. if (transaction != null)
  660. {
  661. transaction.Dispose();
  662. }
  663. }
  664. }
  665. }
  666. }
  667. private SyncJobItem GetJobItem(IDataReader reader)
  668. {
  669. var info = new SyncJobItem
  670. {
  671. Id = reader.GetGuid(0).ToString("N"),
  672. ItemId = reader.GetString(1)
  673. };
  674. if (!reader.IsDBNull(2))
  675. {
  676. info.ItemName = reader.GetString(2);
  677. }
  678. if (!reader.IsDBNull(3))
  679. {
  680. info.MediaSourceId = reader.GetString(3);
  681. }
  682. info.JobId = reader.GetString(4);
  683. if (!reader.IsDBNull(5))
  684. {
  685. info.TemporaryPath = reader.GetString(5);
  686. }
  687. if (!reader.IsDBNull(6))
  688. {
  689. info.OutputPath = reader.GetString(6);
  690. }
  691. if (!reader.IsDBNull(7))
  692. {
  693. info.Status = (SyncJobItemStatus)Enum.Parse(typeof(SyncJobItemStatus), reader.GetString(7), true);
  694. }
  695. info.TargetId = reader.GetString(8);
  696. info.DateCreated = reader.GetDateTime(9).ToUniversalTime();
  697. if (!reader.IsDBNull(10))
  698. {
  699. info.Progress = reader.GetDouble(10);
  700. }
  701. if (!reader.IsDBNull(11))
  702. {
  703. var json = reader.GetString(11);
  704. if (!string.IsNullOrWhiteSpace(json))
  705. {
  706. info.AdditionalFiles = _json.DeserializeFromString<List<ItemFileInfo>>(json);
  707. }
  708. }
  709. if (!reader.IsDBNull(12))
  710. {
  711. var json = reader.GetString(12);
  712. if (!string.IsNullOrWhiteSpace(json))
  713. {
  714. info.MediaSource = _json.DeserializeFromString<MediaSourceInfo>(json);
  715. }
  716. }
  717. info.IsMarkedForRemoval = reader.GetBoolean(13);
  718. info.JobItemIndex = reader.GetInt32(14);
  719. if (!reader.IsDBNull(15))
  720. {
  721. info.ItemDateModifiedTicks = reader.GetInt64(15);
  722. }
  723. return info;
  724. }
  725. }
  726. }