SqliteDeviceRepository.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Linq;
  6. using Emby.Server.Implementations.Data;
  7. using MediaBrowser.Controller;
  8. using MediaBrowser.Model.Logging;
  9. using SQLitePCL.pretty;
  10. using MediaBrowser.Model.Extensions;
  11. using MediaBrowser.Model.IO;
  12. using MediaBrowser.Common.Extensions;
  13. using MediaBrowser.Controller.Devices;
  14. using MediaBrowser.Model.Devices;
  15. using MediaBrowser.Model.Serialization;
  16. using MediaBrowser.Model.Session;
  17. using MediaBrowser.Controller.Configuration;
  18. namespace Emby.Server.Implementations.Devices
  19. {
  20. public class SqliteDeviceRepository : BaseSqliteRepository, IDeviceRepository
  21. {
  22. private readonly CultureInfo _usCulture = new CultureInfo("en-US");
  23. protected IFileSystem FileSystem { get; private set; }
  24. private readonly object _syncLock = new object();
  25. private readonly IJsonSerializer _json;
  26. private IServerApplicationPaths _appPaths;
  27. public SqliteDeviceRepository(ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem, IJsonSerializer json)
  28. : base(logger)
  29. {
  30. var appPaths = config.ApplicationPaths;
  31. DbFilePath = Path.Combine(appPaths.DataPath, "devices.db");
  32. FileSystem = fileSystem;
  33. _json = json;
  34. _appPaths = appPaths;
  35. }
  36. public void Initialize()
  37. {
  38. try
  39. {
  40. InitializeInternal();
  41. }
  42. catch (Exception ex)
  43. {
  44. Logger.ErrorException("Error loading database file. Will reset and retry.", ex);
  45. FileSystem.DeleteFile(DbFilePath);
  46. InitializeInternal();
  47. }
  48. }
  49. private void InitializeInternal()
  50. {
  51. using (var connection = CreateConnection())
  52. {
  53. RunDefaultInitialization(connection);
  54. string[] queries = {
  55. "create table if not exists Devices (Id TEXT PRIMARY KEY, Name TEXT, ReportedName TEXT, CustomName TEXT, CameraUploadPath TEXT, LastUserName TEXT, AppName TEXT, AppVersion TEXT, LastUserId TEXT, DateLastModified DATETIME, Capabilities TEXT)",
  56. "create index if not exists idx_id on Devices(Id)"
  57. };
  58. connection.RunQueries(queries);
  59. MigrateDevices();
  60. }
  61. }
  62. private void MigrateDevices()
  63. {
  64. var files = FileSystem
  65. .GetFilePaths(GetDevicesPath(), true)
  66. .Where(i => string.Equals(Path.GetFileName(i), "device.json", StringComparison.OrdinalIgnoreCase))
  67. .ToList();
  68. foreach (var file in files)
  69. {
  70. try
  71. {
  72. var device = _json.DeserializeFromFile<DeviceInfo>(file);
  73. SaveDevice(device);
  74. }
  75. catch (Exception ex)
  76. {
  77. Logger.ErrorException("Error reading {0}", ex, file);
  78. }
  79. finally
  80. {
  81. try
  82. {
  83. FileSystem.DeleteFile(file);
  84. }
  85. catch (IOException)
  86. {
  87. try
  88. {
  89. FileSystem.MoveFile(file, Path.ChangeExtension(file, ".old"));
  90. }
  91. catch (IOException)
  92. {
  93. }
  94. }
  95. }
  96. }
  97. }
  98. private const string BaseSelectText = "select Id, Name, ReportedName, CustomName, CameraUploadPath, LastUserName, AppName, AppVersion, LastUserId, DateLastModified, Capabilities from Devices";
  99. public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
  100. {
  101. using (WriteLock.Write())
  102. {
  103. using (var connection = CreateConnection())
  104. {
  105. connection.RunInTransaction(db =>
  106. {
  107. using (var statement = db.PrepareStatement("update devices set Capabilities=@Capabilities where Id=@Id"))
  108. {
  109. statement.TryBind("@Id", deviceId);
  110. if (capabilities == null)
  111. {
  112. statement.TryBindNull("@Capabilities");
  113. }
  114. else
  115. {
  116. statement.TryBind("@Capabilities", _json.SerializeToString(capabilities));
  117. }
  118. statement.MoveNext();
  119. }
  120. }, TransactionMode);
  121. }
  122. }
  123. }
  124. public void SaveDevice(DeviceInfo entry)
  125. {
  126. if (entry == null)
  127. {
  128. throw new ArgumentNullException("entry");
  129. }
  130. using (WriteLock.Write())
  131. {
  132. using (var connection = CreateConnection())
  133. {
  134. connection.RunInTransaction(db =>
  135. {
  136. using (var statement = db.PrepareStatement("replace into Devices (Id, Name, ReportedName, CustomName, CameraUploadPath, LastUserName, AppName, AppVersion, LastUserId, DateLastModified, Capabilities) values (@Id, @Name, @ReportedName, @CustomName, @CameraUploadPath, @LastUserName, @AppName, @AppVersion, @LastUserId, @DateLastModified, @Capabilities)"))
  137. {
  138. statement.TryBind("@Id", entry.Id);
  139. statement.TryBind("@Name", entry.Name);
  140. statement.TryBind("@ReportedName", entry.ReportedName);
  141. statement.TryBind("@CustomName", entry.CustomName);
  142. statement.TryBind("@CameraUploadPath", entry.CameraUploadPath);
  143. statement.TryBind("@LastUserName", entry.LastUserName);
  144. statement.TryBind("@AppName", entry.AppName);
  145. statement.TryBind("@AppVersion", entry.AppVersion);
  146. statement.TryBind("@DateLastModified", entry.DateLastModified);
  147. if (entry.Capabilities == null)
  148. {
  149. statement.TryBindNull("@Capabilities");
  150. }
  151. else
  152. {
  153. statement.TryBind("@Capabilities", _json.SerializeToString(entry.Capabilities));
  154. }
  155. statement.MoveNext();
  156. }
  157. }, TransactionMode);
  158. }
  159. }
  160. }
  161. public DeviceInfo GetDevice(string id)
  162. {
  163. using (WriteLock.Read())
  164. {
  165. using (var connection = CreateConnection(true))
  166. {
  167. var statementTexts = new List<string>();
  168. statementTexts.Add(BaseSelectText + " where Id=@Id");
  169. return connection.RunInTransaction(db =>
  170. {
  171. var statements = PrepareAllSafe(db, statementTexts).ToList();
  172. using (var statement = statements[0])
  173. {
  174. statement.TryBind("@Id", id);
  175. foreach (var row in statement.ExecuteQuery())
  176. {
  177. return GetEntry(row);
  178. }
  179. }
  180. return null;
  181. }, ReadTransactionMode);
  182. }
  183. }
  184. }
  185. public List<DeviceInfo> GetDevices()
  186. {
  187. using (WriteLock.Read())
  188. {
  189. using (var connection = CreateConnection(true))
  190. {
  191. var statementTexts = new List<string>();
  192. statementTexts.Add(BaseSelectText + " order by DateLastModified desc");
  193. return connection.RunInTransaction(db =>
  194. {
  195. var list = new List<DeviceInfo>();
  196. var statements = PrepareAllSafe(db, statementTexts).ToList();
  197. using (var statement = statements[0])
  198. {
  199. foreach (var row in statement.ExecuteQuery())
  200. {
  201. list.Add(GetEntry(row));
  202. }
  203. }
  204. return list;
  205. }, ReadTransactionMode);
  206. }
  207. }
  208. }
  209. public ClientCapabilities GetCapabilities(string id)
  210. {
  211. using (WriteLock.Read())
  212. {
  213. using (var connection = CreateConnection(true))
  214. {
  215. var statementTexts = new List<string>();
  216. statementTexts.Add("Select Capabilities from Devices where Id=@Id");
  217. return connection.RunInTransaction(db =>
  218. {
  219. var statements = PrepareAllSafe(db, statementTexts).ToList();
  220. using (var statement = statements[0])
  221. {
  222. statement.TryBind("@Id", id);
  223. foreach (var row in statement.ExecuteQuery())
  224. {
  225. if (row[0].SQLiteType != SQLiteType.Null)
  226. {
  227. return _json.DeserializeFromString<ClientCapabilities>(row.GetString(0));
  228. }
  229. }
  230. }
  231. return null;
  232. }, ReadTransactionMode);
  233. }
  234. }
  235. }
  236. private DeviceInfo GetEntry(IReadOnlyList<IResultSetValue> reader)
  237. {
  238. var index = 0;
  239. var info = new DeviceInfo
  240. {
  241. Id = reader.GetString(index)
  242. };
  243. index++;
  244. if (reader[index].SQLiteType != SQLiteType.Null)
  245. {
  246. info.Name = reader.GetString(index);
  247. }
  248. index++;
  249. if (reader[index].SQLiteType != SQLiteType.Null)
  250. {
  251. info.ReportedName = reader.GetString(index);
  252. }
  253. index++;
  254. if (reader[index].SQLiteType != SQLiteType.Null)
  255. {
  256. info.CustomName = reader.GetString(index);
  257. }
  258. index++;
  259. if (reader[index].SQLiteType != SQLiteType.Null)
  260. {
  261. info.CameraUploadPath = reader.GetString(index);
  262. }
  263. index++;
  264. if (reader[index].SQLiteType != SQLiteType.Null)
  265. {
  266. info.LastUserName = reader.GetString(index);
  267. }
  268. index++;
  269. if (reader[index].SQLiteType != SQLiteType.Null)
  270. {
  271. info.AppName = reader.GetString(index);
  272. }
  273. index++;
  274. if (reader[index].SQLiteType != SQLiteType.Null)
  275. {
  276. info.AppVersion = reader.GetString(index);
  277. }
  278. index++;
  279. if (reader[index].SQLiteType != SQLiteType.Null)
  280. {
  281. info.LastUserId = reader.GetString(index);
  282. }
  283. index++;
  284. if (reader[index].SQLiteType != SQLiteType.Null)
  285. {
  286. info.DateLastModified = reader[index].ReadDateTime();
  287. }
  288. index++;
  289. if (reader[index].SQLiteType != SQLiteType.Null)
  290. {
  291. info.Capabilities = _json.DeserializeFromString<ClientCapabilities>(reader.GetString(index));
  292. }
  293. return info;
  294. }
  295. private string GetDevicesPath()
  296. {
  297. return Path.Combine(_appPaths.DataPath, "devices");
  298. }
  299. private string GetDevicePath(string id)
  300. {
  301. return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N"));
  302. }
  303. public ContentUploadHistory GetCameraUploadHistory(string deviceId)
  304. {
  305. var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
  306. lock (_syncLock)
  307. {
  308. try
  309. {
  310. return _json.DeserializeFromFile<ContentUploadHistory>(path);
  311. }
  312. catch (IOException)
  313. {
  314. return new ContentUploadHistory
  315. {
  316. DeviceId = deviceId
  317. };
  318. }
  319. }
  320. }
  321. public void AddCameraUpload(string deviceId, LocalFileInfo file)
  322. {
  323. var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
  324. FileSystem.CreateDirectory(FileSystem.GetDirectoryName(path));
  325. lock (_syncLock)
  326. {
  327. ContentUploadHistory history;
  328. try
  329. {
  330. history = _json.DeserializeFromFile<ContentUploadHistory>(path);
  331. }
  332. catch (IOException)
  333. {
  334. history = new ContentUploadHistory
  335. {
  336. DeviceId = deviceId
  337. };
  338. }
  339. history.DeviceId = deviceId;
  340. var list = history.FilesUploaded.ToList();
  341. list.Add(file);
  342. history.FilesUploaded = list.ToArray(list.Count);
  343. _json.SerializeToFile(history, path);
  344. }
  345. }
  346. public void DeleteDevice(string id)
  347. {
  348. using (WriteLock.Write())
  349. {
  350. using (var connection = CreateConnection())
  351. {
  352. connection.RunInTransaction(db =>
  353. {
  354. using (var statement = db.PrepareStatement("delete from devices where Id=@Id"))
  355. {
  356. statement.TryBind("@Id", id);
  357. statement.MoveNext();
  358. }
  359. }, TransactionMode);
  360. }
  361. }
  362. var path = GetDevicePath(id);
  363. lock (_syncLock)
  364. {
  365. try
  366. {
  367. FileSystem.DeleteDirectory(path, true);
  368. }
  369. catch (IOException)
  370. {
  371. }
  372. }
  373. }
  374. }
  375. }