SQLiteUserDataRepository.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. using MediaBrowser.Common.Configuration;
  2. using MediaBrowser.Controller.Entities;
  3. using MediaBrowser.Controller.Persistence;
  4. using MediaBrowser.Model.Logging;
  5. using MediaBrowser.Model.Serialization;
  6. using System;
  7. using System.Collections.Concurrent;
  8. using System.Data;
  9. using System.IO;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace MediaBrowser.Server.Implementations.Sqlite
  13. {
  14. /// <summary>
  15. /// Class SQLiteUserDataRepository
  16. /// </summary>
  17. public class SQLiteUserDataRepository : SqliteRepository, IUserDataRepository
  18. {
  19. private readonly ConcurrentDictionary<string, Task<UserItemData>> _userData = new ConcurrentDictionary<string, Task<UserItemData>>();
  20. /// <summary>
  21. /// The repository name
  22. /// </summary>
  23. public const string RepositoryName = "SQLite";
  24. /// <summary>
  25. /// Gets the name of the repository
  26. /// </summary>
  27. /// <value>The name.</value>
  28. public string Name
  29. {
  30. get
  31. {
  32. return RepositoryName;
  33. }
  34. }
  35. /// <summary>
  36. /// Gets a value indicating whether [enable delayed commands].
  37. /// </summary>
  38. /// <value><c>true</c> if [enable delayed commands]; otherwise, <c>false</c>.</value>
  39. protected override bool EnableDelayedCommands
  40. {
  41. get
  42. {
  43. return false;
  44. }
  45. }
  46. private readonly IJsonSerializer _jsonSerializer;
  47. /// <summary>
  48. /// The _app paths
  49. /// </summary>
  50. private readonly IApplicationPaths _appPaths;
  51. /// <summary>
  52. /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class.
  53. /// </summary>
  54. /// <param name="appPaths">The app paths.</param>
  55. /// <param name="jsonSerializer">The json serializer.</param>
  56. /// <param name="logManager">The log manager.</param>
  57. /// <exception cref="System.ArgumentNullException">protobufSerializer</exception>
  58. public SQLiteUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
  59. : base(logManager)
  60. {
  61. if (jsonSerializer == null)
  62. {
  63. throw new ArgumentNullException("jsonSerializer");
  64. }
  65. if (appPaths == null)
  66. {
  67. throw new ArgumentNullException("appPaths");
  68. }
  69. _jsonSerializer = jsonSerializer;
  70. _appPaths = appPaths;
  71. }
  72. /// <summary>
  73. /// Opens the connection to the database
  74. /// </summary>
  75. /// <returns>Task.</returns>
  76. public async Task Initialize()
  77. {
  78. var dbFile = Path.Combine(_appPaths.DataPath, "userdata.db");
  79. await ConnectToDB(dbFile).ConfigureAwait(false);
  80. string[] queries = {
  81. "create table if not exists userdata (key nvarchar, userId GUID, data BLOB)",
  82. "create unique index if not exists userdataindex on userdata (key, userId)",
  83. "create table if not exists schema_version (table_name primary key, version)",
  84. //pragmas
  85. "pragma temp_store = memory"
  86. };
  87. RunQueries(queries);
  88. }
  89. /// <summary>
  90. /// Saves the user data.
  91. /// </summary>
  92. /// <param name="userId">The user id.</param>
  93. /// <param name="key">The key.</param>
  94. /// <param name="userData">The user data.</param>
  95. /// <param name="cancellationToken">The cancellation token.</param>
  96. /// <returns>Task.</returns>
  97. /// <exception cref="System.ArgumentNullException">userData
  98. /// or
  99. /// cancellationToken
  100. /// or
  101. /// userId
  102. /// or
  103. /// userDataId</exception>
  104. public async Task SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
  105. {
  106. if (userData == null)
  107. {
  108. throw new ArgumentNullException("userData");
  109. }
  110. if (cancellationToken == null)
  111. {
  112. throw new ArgumentNullException("cancellationToken");
  113. }
  114. if (userId == Guid.Empty)
  115. {
  116. throw new ArgumentNullException("userId");
  117. }
  118. if (string.IsNullOrEmpty(key))
  119. {
  120. throw new ArgumentNullException("key");
  121. }
  122. cancellationToken.ThrowIfCancellationRequested();
  123. try
  124. {
  125. await PersistUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
  126. var newValue = Task.FromResult(userData);
  127. // Once it succeeds, put it into the dictionary to make it available to everyone else
  128. _userData.AddOrUpdate(key, newValue, delegate { return newValue; });
  129. }
  130. catch (Exception ex)
  131. {
  132. Logger.ErrorException("Error saving user data", ex);
  133. throw;
  134. }
  135. }
  136. /// <summary>
  137. /// Persists the user data.
  138. /// </summary>
  139. /// <param name="userId">The user id.</param>
  140. /// <param name="key">The key.</param>
  141. /// <param name="userData">The user data.</param>
  142. /// <param name="cancellationToken">The cancellation token.</param>
  143. /// <returns>Task.</returns>
  144. public async Task PersistUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
  145. {
  146. cancellationToken.ThrowIfCancellationRequested();
  147. var serialized = _jsonSerializer.SerializeToBytes(userData);
  148. cancellationToken.ThrowIfCancellationRequested();
  149. var cmd = connection.CreateCommand();
  150. cmd.CommandText = "replace into userdata (key, userId, data) values (@1, @2, @3)";
  151. cmd.AddParam("@1", key);
  152. cmd.AddParam("@2", userId);
  153. cmd.AddParam("@3", serialized);
  154. using (var tran = connection.BeginTransaction())
  155. {
  156. try
  157. {
  158. cmd.Transaction = tran;
  159. await cmd.ExecuteNonQueryAsync(cancellationToken);
  160. tran.Commit();
  161. }
  162. catch (OperationCanceledException)
  163. {
  164. tran.Rollback();
  165. }
  166. catch (Exception e)
  167. {
  168. Logger.ErrorException("Failed to commit transaction.", e);
  169. tran.Rollback();
  170. }
  171. }
  172. }
  173. /// <summary>
  174. /// Gets the user data.
  175. /// </summary>
  176. /// <param name="userId">The user id.</param>
  177. /// <param name="key">The key.</param>
  178. /// <returns>Task{UserItemData}.</returns>
  179. /// <exception cref="System.ArgumentNullException">
  180. /// userId
  181. /// or
  182. /// key
  183. /// </exception>
  184. public Task<UserItemData> GetUserData(Guid userId, string key)
  185. {
  186. if (userId == Guid.Empty)
  187. {
  188. throw new ArgumentNullException("userId");
  189. }
  190. if (string.IsNullOrEmpty(key))
  191. {
  192. throw new ArgumentNullException("key");
  193. }
  194. return _userData.GetOrAdd(key, keyName => RetrieveUserData(userId, key));
  195. }
  196. /// <summary>
  197. /// Retrieves the user data.
  198. /// </summary>
  199. /// <param name="userId">The user id.</param>
  200. /// <param name="key">The key.</param>
  201. /// <returns>Task{UserItemData}.</returns>
  202. private async Task<UserItemData> RetrieveUserData(Guid userId, string key)
  203. {
  204. var cmd = connection.CreateCommand();
  205. cmd.CommandText = "select data from userdata where key = @key and userId=@userId";
  206. var idParam = cmd.Parameters.Add("@key", DbType.Guid);
  207. idParam.Value = key;
  208. var userIdParam = cmd.Parameters.Add("@userId", DbType.Guid);
  209. userIdParam.Value = userId;
  210. using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow).ConfigureAwait(false))
  211. {
  212. if (reader.Read())
  213. {
  214. using (var stream = GetStream(reader, 0))
  215. {
  216. return _jsonSerializer.DeserializeFromStream<UserItemData>(stream);
  217. }
  218. }
  219. }
  220. return new UserItemData();
  221. }
  222. }
  223. }