SqliteUserRepository.cs 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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.Generic;
  8. using System.Data;
  9. using System.Threading;
  10. using System.Threading.Tasks;
  11. namespace MediaBrowser.Server.Implementations.Persistence
  12. {
  13. /// <summary>
  14. /// Class SQLiteUserRepository
  15. /// </summary>
  16. public class SqliteUserRepository : IUserRepository
  17. {
  18. private readonly ILogger _logger;
  19. private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
  20. private IDbConnection _connection;
  21. /// <summary>
  22. /// Gets the name of the repository
  23. /// </summary>
  24. /// <value>The name.</value>
  25. public string Name
  26. {
  27. get
  28. {
  29. return "SQLite";
  30. }
  31. }
  32. /// <summary>
  33. /// Gets the json serializer.
  34. /// </summary>
  35. /// <value>The json serializer.</value>
  36. private readonly IJsonSerializer _jsonSerializer;
  37. /// <summary>
  38. /// The _app paths
  39. /// </summary>
  40. private readonly IApplicationPaths _appPaths;
  41. /// <summary>
  42. /// Initializes a new instance of the <see cref="SqliteUserRepository" /> class.
  43. /// </summary>
  44. /// <param name="connection">The connection.</param>
  45. /// <param name="appPaths">The app paths.</param>
  46. /// <param name="jsonSerializer">The json serializer.</param>
  47. /// <param name="logManager">The log manager.</param>
  48. /// <exception cref="System.ArgumentNullException">appPaths</exception>
  49. public SqliteUserRepository(IDbConnection connection, IApplicationPaths appPaths, IJsonSerializer jsonSerializer, ILogManager logManager)
  50. {
  51. if (appPaths == null)
  52. {
  53. throw new ArgumentNullException("appPaths");
  54. }
  55. if (jsonSerializer == null)
  56. {
  57. throw new ArgumentNullException("jsonSerializer");
  58. }
  59. _connection = connection;
  60. _appPaths = appPaths;
  61. _jsonSerializer = jsonSerializer;
  62. _logger = logManager.GetLogger(GetType().Name);
  63. }
  64. /// <summary>
  65. /// Opens the connection to the database
  66. /// </summary>
  67. /// <returns>Task.</returns>
  68. public void Initialize()
  69. {
  70. string[] queries = {
  71. "create table if not exists users (guid GUID primary key, data BLOB)",
  72. "create index if not exists idx_users on users(guid)",
  73. "create table if not exists schema_version (table_name primary key, version)",
  74. //pragmas
  75. "pragma temp_store = memory"
  76. };
  77. _connection.RunQueries(queries, _logger);
  78. }
  79. /// <summary>
  80. /// Save a user in the repo
  81. /// </summary>
  82. /// <param name="user">The user.</param>
  83. /// <param name="cancellationToken">The cancellation token.</param>
  84. /// <returns>Task.</returns>
  85. /// <exception cref="System.ArgumentNullException">user</exception>
  86. public async Task SaveUser(User user, CancellationToken cancellationToken)
  87. {
  88. if (user == null)
  89. {
  90. throw new ArgumentNullException("user");
  91. }
  92. if (cancellationToken == null)
  93. {
  94. throw new ArgumentNullException("cancellationToken");
  95. }
  96. cancellationToken.ThrowIfCancellationRequested();
  97. var serialized = _jsonSerializer.SerializeToBytes(user);
  98. cancellationToken.ThrowIfCancellationRequested();
  99. await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  100. IDbTransaction transaction = null;
  101. try
  102. {
  103. transaction = _connection.BeginTransaction();
  104. using (var cmd = _connection.CreateCommand())
  105. {
  106. cmd.CommandText = "replace into users (guid, data) values (@1, @2)";
  107. cmd.Parameters.Add(cmd, "@1", DbType.Guid).Value = user.Id;
  108. cmd.Parameters.Add(cmd, "@2", DbType.Binary).Value = serialized;
  109. cmd.Transaction = transaction;
  110. cmd.ExecuteNonQuery();
  111. }
  112. transaction.Commit();
  113. }
  114. catch (OperationCanceledException)
  115. {
  116. if (transaction != null)
  117. {
  118. transaction.Rollback();
  119. }
  120. throw;
  121. }
  122. catch (Exception e)
  123. {
  124. _logger.ErrorException("Failed to save user:", e);
  125. if (transaction != null)
  126. {
  127. transaction.Rollback();
  128. }
  129. throw;
  130. }
  131. finally
  132. {
  133. if (transaction != null)
  134. {
  135. transaction.Dispose();
  136. }
  137. _writeLock.Release();
  138. }
  139. }
  140. /// <summary>
  141. /// Retrieve all users from the database
  142. /// </summary>
  143. /// <returns>IEnumerable{User}.</returns>
  144. public IEnumerable<User> RetrieveAllUsers()
  145. {
  146. using (var cmd = _connection.CreateCommand())
  147. {
  148. cmd.CommandText = "select data from users";
  149. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
  150. {
  151. while (reader.Read())
  152. {
  153. using (var stream = reader.GetMemoryStream(0))
  154. {
  155. var user = _jsonSerializer.DeserializeFromStream<User>(stream);
  156. yield return user;
  157. }
  158. }
  159. }
  160. }
  161. }
  162. /// <summary>
  163. /// Deletes the user.
  164. /// </summary>
  165. /// <param name="user">The user.</param>
  166. /// <param name="cancellationToken">The cancellation token.</param>
  167. /// <returns>Task.</returns>
  168. /// <exception cref="System.ArgumentNullException">user</exception>
  169. public async Task DeleteUser(User user, CancellationToken cancellationToken)
  170. {
  171. if (user == null)
  172. {
  173. throw new ArgumentNullException("user");
  174. }
  175. if (cancellationToken == null)
  176. {
  177. throw new ArgumentNullException("cancellationToken");
  178. }
  179. cancellationToken.ThrowIfCancellationRequested();
  180. await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
  181. IDbTransaction transaction = null;
  182. try
  183. {
  184. transaction = _connection.BeginTransaction();
  185. using (var cmd = _connection.CreateCommand())
  186. {
  187. cmd.CommandText = "delete from users where guid=@guid";
  188. cmd.Parameters.Add(cmd, "@guid", DbType.Guid).Value = user.Id;
  189. cmd.Transaction = transaction;
  190. cmd.ExecuteNonQuery();
  191. }
  192. transaction.Commit();
  193. }
  194. catch (OperationCanceledException)
  195. {
  196. if (transaction != null)
  197. {
  198. transaction.Rollback();
  199. }
  200. throw;
  201. }
  202. catch (Exception e)
  203. {
  204. _logger.ErrorException("Failed to delete user:", e);
  205. if (transaction != null)
  206. {
  207. transaction.Rollback();
  208. }
  209. throw;
  210. }
  211. finally
  212. {
  213. if (transaction != null)
  214. {
  215. transaction.Dispose();
  216. }
  217. _writeLock.Release();
  218. }
  219. }
  220. /// <summary>
  221. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  222. /// </summary>
  223. public void Dispose()
  224. {
  225. Dispose(true);
  226. GC.SuppressFinalize(this);
  227. }
  228. private readonly object _disposeLock = new object();
  229. /// <summary>
  230. /// Releases unmanaged and - optionally - managed resources.
  231. /// </summary>
  232. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  233. protected virtual void Dispose(bool dispose)
  234. {
  235. if (dispose)
  236. {
  237. try
  238. {
  239. lock (_disposeLock)
  240. {
  241. if (_connection != null)
  242. {
  243. if (_connection.IsOpen())
  244. {
  245. _connection.Close();
  246. }
  247. _connection.Dispose();
  248. _connection = null;
  249. }
  250. }
  251. }
  252. catch (Exception ex)
  253. {
  254. _logger.ErrorException("Error disposing database", ex);
  255. }
  256. }
  257. }
  258. }
  259. }