ActivityRepository.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. using MediaBrowser.Controller;
  2. using MediaBrowser.Controller.Activity;
  3. using MediaBrowser.Model.Activity;
  4. using MediaBrowser.Model.Logging;
  5. using MediaBrowser.Model.Querying;
  6. using MediaBrowser.Server.Implementations.Persistence;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Data;
  10. using System.Globalization;
  11. using System.IO;
  12. using System.Threading;
  13. using System.Threading.Tasks;
  14. namespace MediaBrowser.Server.Implementations.Activity
  15. {
  16. public class ActivityRepository : IActivityRepository, IDisposable
  17. {
  18. private IDbConnection _connection;
  19. private readonly ILogger _logger;
  20. private readonly SemaphoreSlim _writeLock = new SemaphoreSlim(1, 1);
  21. private readonly IServerApplicationPaths _appPaths;
  22. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  23. private IDbCommand _saveActivityCommand;
  24. public ActivityRepository(ILogger logger, IServerApplicationPaths appPaths)
  25. {
  26. _logger = logger;
  27. _appPaths = appPaths;
  28. }
  29. public async Task Initialize()
  30. {
  31. var dbFile = Path.Combine(_appPaths.DataPath, "activitylog.db");
  32. _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
  33. string[] queries = {
  34. "create table if not exists ActivityLogEntries (Id GUID PRIMARY KEY, Name TEXT, Overview TEXT, ShortOverview TEXT, Type TEXT, ItemId TEXT, UserId TEXT, DateCreated DATETIME, LogSeverity TEXT)",
  35. "create index if not exists idx_ActivityLogEntries on ActivityLogEntries(Id)",
  36. //pragmas
  37. "pragma temp_store = memory",
  38. "pragma shrink_memory"
  39. };
  40. _connection.RunQueries(queries, _logger);
  41. PrepareStatements();
  42. }
  43. private void PrepareStatements()
  44. {
  45. _saveActivityCommand = _connection.CreateCommand();
  46. _saveActivityCommand.CommandText = "replace into ActivityLogEntries (Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Id, @Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)";
  47. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Id");
  48. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Name");
  49. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Overview");
  50. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ShortOverview");
  51. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@Type");
  52. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@ItemId");
  53. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@UserId");
  54. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@DateCreated");
  55. _saveActivityCommand.Parameters.Add(_saveActivityCommand, "@LogSeverity");
  56. }
  57. private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLogEntries";
  58. public Task Create(ActivityLogEntry entry)
  59. {
  60. return Update(entry);
  61. }
  62. public async Task Update(ActivityLogEntry entry)
  63. {
  64. if (entry == null)
  65. {
  66. throw new ArgumentNullException("entry");
  67. }
  68. await _writeLock.WaitAsync().ConfigureAwait(false);
  69. IDbTransaction transaction = null;
  70. try
  71. {
  72. transaction = _connection.BeginTransaction();
  73. var index = 0;
  74. _saveActivityCommand.GetParameter(index++).Value = new Guid(entry.Id);
  75. _saveActivityCommand.GetParameter(index++).Value = entry.Name;
  76. _saveActivityCommand.GetParameter(index++).Value = entry.Overview;
  77. _saveActivityCommand.GetParameter(index++).Value = entry.ShortOverview;
  78. _saveActivityCommand.GetParameter(index++).Value = entry.Type;
  79. _saveActivityCommand.GetParameter(index++).Value = entry.ItemId;
  80. _saveActivityCommand.GetParameter(index++).Value = entry.UserId;
  81. _saveActivityCommand.GetParameter(index++).Value = entry.Date;
  82. _saveActivityCommand.GetParameter(index++).Value = entry.Severity.ToString();
  83. _saveActivityCommand.Transaction = transaction;
  84. _saveActivityCommand.ExecuteNonQuery();
  85. transaction.Commit();
  86. }
  87. catch (OperationCanceledException)
  88. {
  89. if (transaction != null)
  90. {
  91. transaction.Rollback();
  92. }
  93. throw;
  94. }
  95. catch (Exception e)
  96. {
  97. _logger.ErrorException("Failed to save record:", e);
  98. if (transaction != null)
  99. {
  100. transaction.Rollback();
  101. }
  102. throw;
  103. }
  104. finally
  105. {
  106. if (transaction != null)
  107. {
  108. transaction.Dispose();
  109. }
  110. _writeLock.Release();
  111. }
  112. }
  113. public QueryResult<ActivityLogEntry> GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
  114. {
  115. using (var cmd = _connection.CreateCommand())
  116. {
  117. cmd.CommandText = BaseActivitySelectText;
  118. var whereClauses = new List<string>();
  119. if (minDate.HasValue)
  120. {
  121. whereClauses.Add("DateCreated>=@DateCreated");
  122. cmd.Parameters.Add(cmd, "@DateCreated", DbType.Date).Value = minDate.Value;
  123. }
  124. var whereTextWithoutPaging = whereClauses.Count == 0 ?
  125. string.Empty :
  126. " where " + string.Join(" AND ", whereClauses.ToArray());
  127. if (startIndex.HasValue && startIndex.Value > 0)
  128. {
  129. var pagingWhereText = whereClauses.Count == 0 ?
  130. string.Empty :
  131. " where " + string.Join(" AND ", whereClauses.ToArray());
  132. whereClauses.Add(string.Format("Id NOT IN (SELECT Id FROM ActivityLogEntries {0} ORDER BY DateCreated DESC LIMIT {1})",
  133. pagingWhereText,
  134. startIndex.Value.ToString(_usCulture)));
  135. }
  136. var whereText = whereClauses.Count == 0 ?
  137. string.Empty :
  138. " where " + string.Join(" AND ", whereClauses.ToArray());
  139. cmd.CommandText += whereText;
  140. cmd.CommandText += " ORDER BY DateCreated DESC";
  141. if (limit.HasValue)
  142. {
  143. cmd.CommandText += " LIMIT " + limit.Value.ToString(_usCulture);
  144. }
  145. cmd.CommandText += "; select count (Id) from ActivityLogEntries" + whereTextWithoutPaging;
  146. var list = new List<ActivityLogEntry>();
  147. var count = 0;
  148. using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
  149. {
  150. while (reader.Read())
  151. {
  152. list.Add(GetEntry(reader));
  153. }
  154. if (reader.NextResult() && reader.Read())
  155. {
  156. count = reader.GetInt32(0);
  157. }
  158. }
  159. return new QueryResult<ActivityLogEntry>()
  160. {
  161. Items = list.ToArray(),
  162. TotalRecordCount = count
  163. };
  164. }
  165. }
  166. private ActivityLogEntry GetEntry(IDataReader reader)
  167. {
  168. var index = 0;
  169. var info = new ActivityLogEntry
  170. {
  171. Id = reader.GetGuid(index).ToString("N")
  172. };
  173. index++;
  174. if (!reader.IsDBNull(index))
  175. {
  176. info.Name = reader.GetString(index);
  177. }
  178. index++;
  179. if (!reader.IsDBNull(index))
  180. {
  181. info.Overview = reader.GetString(index);
  182. }
  183. index++;
  184. if (!reader.IsDBNull(index))
  185. {
  186. info.ShortOverview = reader.GetString(index);
  187. }
  188. index++;
  189. if (!reader.IsDBNull(index))
  190. {
  191. info.Type = reader.GetString(index);
  192. }
  193. index++;
  194. if (!reader.IsDBNull(index))
  195. {
  196. info.ItemId = reader.GetString(index);
  197. }
  198. index++;
  199. if (!reader.IsDBNull(index))
  200. {
  201. info.UserId = reader.GetString(index);
  202. }
  203. index++;
  204. info.Date = reader.GetDateTime(index).ToUniversalTime();
  205. index++;
  206. if (!reader.IsDBNull(index))
  207. {
  208. info.Severity = (LogSeverity)Enum.Parse(typeof(LogSeverity), reader.GetString(index), true);
  209. }
  210. return info;
  211. }
  212. /// <summary>
  213. /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
  214. /// </summary>
  215. public void Dispose()
  216. {
  217. Dispose(true);
  218. GC.SuppressFinalize(this);
  219. }
  220. private readonly object _disposeLock = new object();
  221. /// <summary>
  222. /// Releases unmanaged and - optionally - managed resources.
  223. /// </summary>
  224. /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
  225. protected virtual void Dispose(bool dispose)
  226. {
  227. if (dispose)
  228. {
  229. try
  230. {
  231. lock (_disposeLock)
  232. {
  233. if (_connection != null)
  234. {
  235. if (_connection.IsOpen())
  236. {
  237. _connection.Close();
  238. }
  239. _connection.Dispose();
  240. _connection = null;
  241. }
  242. }
  243. }
  244. catch (Exception ex)
  245. {
  246. _logger.ErrorException("Error disposing database", ex);
  247. }
  248. }
  249. }
  250. }
  251. }