SQLiteUserDataRepository.cs 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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">
  58. /// jsonSerializer
  59. /// or
  60. /// appPaths
  61. /// </exception>
  62. public SQLiteUserDataRepository(IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
  63. : base(logManager)
  64. {
  65. if (jsonSerializer == null)
  66. {
  67. throw new ArgumentNullException("jsonSerializer");
  68. }
  69. if (appPaths == null)
  70. {
  71. throw new ArgumentNullException("appPaths");
  72. }
  73. _jsonSerializer = jsonSerializer;
  74. _appPaths = appPaths;
  75. }
  76. /// <summary>
  77. /// Opens the connection to the database
  78. /// </summary>
  79. /// <returns>Task.</returns>
  80. public async Task Initialize()
  81. {
  82. var dbFile = Path.Combine(_appPaths.DataPath, "userdata.db");
  83. await ConnectToDb(dbFile).ConfigureAwait(false);
  84. string[] queries = {
  85. "create table if not exists userdata (key nvarchar, userId GUID, data BLOB)",
  86. "create unique index if not exists userdataindex on userdata (key, userId)",
  87. "create table if not exists schema_version (table_name primary key, version)",
  88. //pragmas
  89. "pragma temp_store = memory"
  90. };
  91. RunQueries(queries);
  92. }
  93. /// <summary>
  94. /// Saves the user data.
  95. /// </summary>
  96. /// <param name="userId">The user id.</param>
  97. /// <param name="key">The key.</param>
  98. /// <param name="userData">The user data.</param>
  99. /// <param name="cancellationToken">The cancellation token.</param>
  100. /// <returns>Task.</returns>
  101. /// <exception cref="System.ArgumentNullException">userData
  102. /// or
  103. /// cancellationToken
  104. /// or
  105. /// userId
  106. /// or
  107. /// userDataId</exception>
  108. public async Task SaveUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
  109. {
  110. if (userData == null)
  111. {
  112. throw new ArgumentNullException("userData");
  113. }
  114. if (cancellationToken == null)
  115. {
  116. throw new ArgumentNullException("cancellationToken");
  117. }
  118. if (userId == Guid.Empty)
  119. {
  120. throw new ArgumentNullException("userId");
  121. }
  122. if (string.IsNullOrEmpty(key))
  123. {
  124. throw new ArgumentNullException("key");
  125. }
  126. cancellationToken.ThrowIfCancellationRequested();
  127. try
  128. {
  129. await PersistUserData(userId, key, userData, cancellationToken).ConfigureAwait(false);
  130. var newValue = Task.FromResult(userData);
  131. // Once it succeeds, put it into the dictionary to make it available to everyone else
  132. _userData.AddOrUpdate(GetInternalKey(userId, key), newValue, delegate { return newValue; });
  133. }
  134. catch (Exception ex)
  135. {
  136. Logger.ErrorException("Error saving user data", ex);
  137. throw;
  138. }
  139. }
  140. /// <summary>
  141. /// Gets the internal key.
  142. /// </summary>
  143. /// <param name="userId">The user id.</param>
  144. /// <param name="key">The key.</param>
  145. /// <returns>System.String.</returns>
  146. private string GetInternalKey(Guid userId, string key)
  147. {
  148. return userId + key;
  149. }
  150. /// <summary>
  151. /// Persists the user data.
  152. /// </summary>
  153. /// <param name="userId">The user id.</param>
  154. /// <param name="key">The key.</param>
  155. /// <param name="userData">The user data.</param>
  156. /// <param name="cancellationToken">The cancellation token.</param>
  157. /// <returns>Task.</returns>
  158. public async Task PersistUserData(Guid userId, string key, UserItemData userData, CancellationToken cancellationToken)
  159. {
  160. cancellationToken.ThrowIfCancellationRequested();
  161. var serialized = _jsonSerializer.SerializeToBytes(userData);
  162. cancellationToken.ThrowIfCancellationRequested();
  163. using (var cmd = connection.CreateCommand())
  164. {
  165. cmd.CommandText = "replace into userdata (key, userId, data) values (@1, @2, @3)";
  166. cmd.AddParam("@1", key);
  167. cmd.AddParam("@2", userId);
  168. cmd.AddParam("@3", serialized);
  169. using (var tran = connection.BeginTransaction())
  170. {
  171. try
  172. {
  173. cmd.Transaction = tran;
  174. await cmd.ExecuteNonQueryAsync(cancellationToken);
  175. tran.Commit();
  176. }
  177. catch (OperationCanceledException)
  178. {
  179. tran.Rollback();
  180. }
  181. catch (Exception e)
  182. {
  183. Logger.ErrorException("Failed to commit transaction.", e);
  184. tran.Rollback();
  185. }
  186. }
  187. }
  188. }
  189. /// <summary>
  190. /// Gets the user data.
  191. /// </summary>
  192. /// <param name="userId">The user id.</param>
  193. /// <param name="key">The key.</param>
  194. /// <returns>Task{UserItemData}.</returns>
  195. /// <exception cref="System.ArgumentNullException">
  196. /// userId
  197. /// or
  198. /// key
  199. /// </exception>
  200. public Task<UserItemData> GetUserData(Guid userId, string key)
  201. {
  202. if (userId == Guid.Empty)
  203. {
  204. throw new ArgumentNullException("userId");
  205. }
  206. if (string.IsNullOrEmpty(key))
  207. {
  208. throw new ArgumentNullException("key");
  209. }
  210. return _userData.GetOrAdd(GetInternalKey(userId, key), keyName => RetrieveUserData(userId, key));
  211. }
  212. /// <summary>
  213. /// Retrieves the user data.
  214. /// </summary>
  215. /// <param name="userId">The user id.</param>
  216. /// <param name="key">The key.</param>
  217. /// <returns>Task{UserItemData}.</returns>
  218. private async Task<UserItemData> RetrieveUserData(Guid userId, string key)
  219. {
  220. using (var cmd = connection.CreateCommand())
  221. {
  222. cmd.CommandText = "select data from userdata where key = @key and userId=@userId";
  223. var idParam = cmd.Parameters.Add("@key", DbType.String);
  224. idParam.Value = key;
  225. var userIdParam = cmd.Parameters.Add("@userId", DbType.Guid);
  226. userIdParam.Value = userId;
  227. using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow).ConfigureAwait(false))
  228. {
  229. if (reader.Read())
  230. {
  231. using (var stream = GetStream(reader, 0))
  232. {
  233. return _jsonSerializer.DeserializeFromStream<UserItemData>(stream);
  234. }
  235. }
  236. }
  237. return new UserItemData();
  238. }
  239. }
  240. }
  241. }