DeviceManager.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading.Tasks;
  6. using MediaBrowser.Common.Configuration;
  7. using MediaBrowser.Common.Extensions;
  8. using MediaBrowser.Common.Net;
  9. using MediaBrowser.Controller.Configuration;
  10. using MediaBrowser.Controller.Devices;
  11. using MediaBrowser.Controller.Entities;
  12. using MediaBrowser.Controller.Library;
  13. using MediaBrowser.Controller.Plugins;
  14. using MediaBrowser.Controller.Security;
  15. using MediaBrowser.Model.Configuration;
  16. using MediaBrowser.Model.Devices;
  17. using MediaBrowser.Model.Entities;
  18. using MediaBrowser.Model.Events;
  19. using MediaBrowser.Model.Globalization;
  20. using MediaBrowser.Model.IO;
  21. using MediaBrowser.Model.Net;
  22. using MediaBrowser.Model.Querying;
  23. using MediaBrowser.Model.Serialization;
  24. using MediaBrowser.Model.Session;
  25. using MediaBrowser.Model.Users;
  26. using Microsoft.Extensions.Logging;
  27. namespace Emby.Server.Implementations.Devices
  28. {
  29. public class DeviceManager : IDeviceManager
  30. {
  31. private readonly IJsonSerializer _json;
  32. private readonly IUserManager _userManager;
  33. private readonly IFileSystem _fileSystem;
  34. private readonly ILibraryMonitor _libraryMonitor;
  35. private readonly IServerConfigurationManager _config;
  36. private readonly ILogger _logger;
  37. private readonly INetworkManager _network;
  38. private readonly ILibraryManager _libraryManager;
  39. private readonly ILocalizationManager _localizationManager;
  40. private readonly IAuthenticationRepository _authRepo;
  41. public event EventHandler<GenericEventArgs<Tuple<string, DeviceOptions>>> DeviceOptionsUpdated;
  42. public event EventHandler<GenericEventArgs<CameraImageUploadInfo>> CameraImageUploaded;
  43. private readonly object _cameraUploadSyncLock = new object();
  44. private readonly object _capabilitiesSyncLock = new object();
  45. public DeviceManager(
  46. IAuthenticationRepository authRepo,
  47. IJsonSerializer json,
  48. ILibraryManager libraryManager,
  49. ILocalizationManager localizationManager,
  50. IUserManager userManager,
  51. IFileSystem fileSystem,
  52. ILibraryMonitor libraryMonitor,
  53. IServerConfigurationManager config,
  54. ILoggerFactory loggerFactory,
  55. INetworkManager network)
  56. {
  57. _json = json;
  58. _userManager = userManager;
  59. _fileSystem = fileSystem;
  60. _libraryMonitor = libraryMonitor;
  61. _config = config;
  62. _logger = loggerFactory.CreateLogger(nameof(DeviceManager));
  63. _network = network;
  64. _libraryManager = libraryManager;
  65. _localizationManager = localizationManager;
  66. _authRepo = authRepo;
  67. }
  68. private Dictionary<string, ClientCapabilities> _capabilitiesCache = new Dictionary<string, ClientCapabilities>(StringComparer.OrdinalIgnoreCase);
  69. public void SaveCapabilities(string deviceId, ClientCapabilities capabilities)
  70. {
  71. var path = Path.Combine(GetDevicePath(deviceId), "capabilities.json");
  72. _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
  73. lock (_capabilitiesSyncLock)
  74. {
  75. _capabilitiesCache[deviceId] = capabilities;
  76. _json.SerializeToFile(capabilities, path);
  77. }
  78. }
  79. public void UpdateDeviceOptions(string deviceId, DeviceOptions options)
  80. {
  81. _authRepo.UpdateDeviceOptions(deviceId, options);
  82. if (DeviceOptionsUpdated != null)
  83. {
  84. DeviceOptionsUpdated(this, new GenericEventArgs<Tuple<string, DeviceOptions>>()
  85. {
  86. Argument = new Tuple<string, DeviceOptions>(deviceId, options)
  87. });
  88. }
  89. }
  90. public DeviceOptions GetDeviceOptions(string deviceId)
  91. {
  92. return _authRepo.GetDeviceOptions(deviceId);
  93. }
  94. public ClientCapabilities GetCapabilities(string id)
  95. {
  96. lock (_capabilitiesSyncLock)
  97. {
  98. if (_capabilitiesCache.TryGetValue(id, out var result))
  99. {
  100. return result;
  101. }
  102. var path = Path.Combine(GetDevicePath(id), "capabilities.json");
  103. try
  104. {
  105. return _json.DeserializeFromFile<ClientCapabilities>(path) ?? new ClientCapabilities();
  106. }
  107. catch
  108. {
  109. }
  110. }
  111. return new ClientCapabilities();
  112. }
  113. public DeviceInfo GetDevice(string id)
  114. {
  115. return GetDevice(id, true);
  116. }
  117. private DeviceInfo GetDevice(string id, bool includeCapabilities)
  118. {
  119. var session = _authRepo.Get(new AuthenticationInfoQuery
  120. {
  121. DeviceId = id
  122. }).Items.FirstOrDefault();
  123. var device = session == null ? null : ToDeviceInfo(session);
  124. return device;
  125. }
  126. public QueryResult<DeviceInfo> GetDevices(DeviceQuery query)
  127. {
  128. var sessions = _authRepo.Get(new AuthenticationInfoQuery
  129. {
  130. //UserId = query.UserId
  131. HasUser = true
  132. }).Items;
  133. // TODO: DeviceQuery doesn't seem to be used from client. Not even Swagger.
  134. if (query.SupportsSync.HasValue)
  135. {
  136. var val = query.SupportsSync.Value;
  137. sessions = sessions.Where(i => GetCapabilities(i.DeviceId).SupportsSync == val).ToArray();
  138. }
  139. if (!query.UserId.Equals(Guid.Empty))
  140. {
  141. var user = _userManager.GetUserById(query.UserId);
  142. sessions = sessions.Where(i => CanAccessDevice(user, i.DeviceId)).ToArray();
  143. }
  144. var array = sessions.Select(ToDeviceInfo).ToArray();
  145. return new QueryResult<DeviceInfo>
  146. {
  147. Items = array,
  148. TotalRecordCount = array.Length
  149. };
  150. }
  151. private DeviceInfo ToDeviceInfo(AuthenticationInfo authInfo)
  152. {
  153. var caps = GetCapabilities(authInfo.DeviceId);
  154. return new DeviceInfo
  155. {
  156. AppName = authInfo.AppName,
  157. AppVersion = authInfo.AppVersion,
  158. Id = authInfo.DeviceId,
  159. LastUserId = authInfo.UserId,
  160. LastUserName = authInfo.UserName,
  161. Name = authInfo.DeviceName,
  162. DateLastActivity = authInfo.DateLastActivity,
  163. IconUrl = caps == null ? null : caps.IconUrl
  164. };
  165. }
  166. private string GetDevicesPath()
  167. {
  168. return Path.Combine(_config.ApplicationPaths.DataPath, "devices");
  169. }
  170. private string GetDevicePath(string id)
  171. {
  172. return Path.Combine(GetDevicesPath(), id.GetMD5().ToString("N"));
  173. }
  174. public ContentUploadHistory GetCameraUploadHistory(string deviceId)
  175. {
  176. var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
  177. lock (_cameraUploadSyncLock)
  178. {
  179. try
  180. {
  181. return _json.DeserializeFromFile<ContentUploadHistory>(path);
  182. }
  183. catch (IOException)
  184. {
  185. return new ContentUploadHistory
  186. {
  187. DeviceId = deviceId
  188. };
  189. }
  190. }
  191. }
  192. public async Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file)
  193. {
  194. var device = GetDevice(deviceId, false);
  195. var uploadPathInfo = GetUploadPath(device);
  196. var path = uploadPathInfo.Item1;
  197. if (!string.IsNullOrWhiteSpace(file.Album))
  198. {
  199. path = Path.Combine(path, _fileSystem.GetValidFilename(file.Album));
  200. }
  201. path = Path.Combine(path, file.Name);
  202. path = Path.ChangeExtension(path, MimeTypes.ToExtension(file.MimeType) ?? "jpg");
  203. _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
  204. await EnsureLibraryFolder(uploadPathInfo.Item2, uploadPathInfo.Item3).ConfigureAwait(false);
  205. _libraryMonitor.ReportFileSystemChangeBeginning(path);
  206. try
  207. {
  208. using (var fs = _fileSystem.GetFileStream(path, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read))
  209. {
  210. await stream.CopyToAsync(fs).ConfigureAwait(false);
  211. }
  212. AddCameraUpload(deviceId, file);
  213. }
  214. finally
  215. {
  216. _libraryMonitor.ReportFileSystemChangeComplete(path, true);
  217. }
  218. if (CameraImageUploaded != null)
  219. {
  220. CameraImageUploaded?.Invoke(this, new GenericEventArgs<CameraImageUploadInfo>
  221. {
  222. Argument = new CameraImageUploadInfo
  223. {
  224. Device = device,
  225. FileInfo = file
  226. }
  227. });
  228. }
  229. }
  230. private void AddCameraUpload(string deviceId, LocalFileInfo file)
  231. {
  232. var path = Path.Combine(GetDevicePath(deviceId), "camerauploads.json");
  233. _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path));
  234. lock (_cameraUploadSyncLock)
  235. {
  236. ContentUploadHistory history;
  237. try
  238. {
  239. history = _json.DeserializeFromFile<ContentUploadHistory>(path);
  240. }
  241. catch (IOException)
  242. {
  243. history = new ContentUploadHistory
  244. {
  245. DeviceId = deviceId
  246. };
  247. }
  248. history.DeviceId = deviceId;
  249. var list = history.FilesUploaded.ToList();
  250. list.Add(file);
  251. history.FilesUploaded = list.ToArray();
  252. _json.SerializeToFile(history, path);
  253. }
  254. }
  255. internal Task EnsureLibraryFolder(string path, string name)
  256. {
  257. var existingFolders = _libraryManager
  258. .RootFolder
  259. .Children
  260. .OfType<Folder>()
  261. .Where(i => _fileSystem.AreEqual(path, i.Path) || _fileSystem.ContainsSubPath(i.Path, path))
  262. .ToList();
  263. if (existingFolders.Count > 0)
  264. {
  265. return Task.CompletedTask;
  266. }
  267. _fileSystem.CreateDirectory(path);
  268. var libraryOptions = new LibraryOptions
  269. {
  270. PathInfos = new[] { new MediaPathInfo { Path = path } },
  271. EnablePhotos = true,
  272. EnableRealtimeMonitor = false,
  273. SaveLocalMetadata = true
  274. };
  275. if (string.IsNullOrWhiteSpace(name))
  276. {
  277. name = _localizationManager.GetLocalizedString("HeaderCameraUploads");
  278. }
  279. return _libraryManager.AddVirtualFolder(name, CollectionType.HomeVideos, libraryOptions, true);
  280. }
  281. private Tuple<string, string, string> GetUploadPath(DeviceInfo device)
  282. {
  283. var config = _config.GetUploadOptions();
  284. var path = config.CameraUploadPath;
  285. if (string.IsNullOrWhiteSpace(path))
  286. {
  287. path = DefaultCameraUploadsPath;
  288. }
  289. var topLibraryPath = path;
  290. if (config.EnableCameraUploadSubfolders)
  291. {
  292. path = Path.Combine(path, _fileSystem.GetValidFilename(device.Name));
  293. }
  294. return new Tuple<string, string, string>(path, topLibraryPath, null);
  295. }
  296. internal string GetUploadsPath()
  297. {
  298. var config = _config.GetUploadOptions();
  299. var path = config.CameraUploadPath;
  300. if (string.IsNullOrWhiteSpace(path))
  301. {
  302. path = DefaultCameraUploadsPath;
  303. }
  304. return path;
  305. }
  306. private string DefaultCameraUploadsPath => Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
  307. public bool CanAccessDevice(User user, string deviceId)
  308. {
  309. if (user == null)
  310. {
  311. throw new ArgumentException("user not found");
  312. }
  313. if (string.IsNullOrEmpty(deviceId))
  314. {
  315. throw new ArgumentNullException(nameof(deviceId));
  316. }
  317. if (!CanAccessDevice(user.Policy, deviceId))
  318. {
  319. var capabilities = GetCapabilities(deviceId);
  320. if (capabilities != null && capabilities.SupportsPersistentIdentifier)
  321. {
  322. return false;
  323. }
  324. }
  325. return true;
  326. }
  327. private static bool CanAccessDevice(UserPolicy policy, string id)
  328. {
  329. if (policy.EnableAllDevices)
  330. {
  331. return true;
  332. }
  333. if (policy.IsAdministrator)
  334. {
  335. return true;
  336. }
  337. return policy.EnabledDevices.Contains(id, StringComparer.OrdinalIgnoreCase);
  338. }
  339. }
  340. public class DeviceManagerEntryPoint : IServerEntryPoint
  341. {
  342. private readonly DeviceManager _deviceManager;
  343. private readonly IServerConfigurationManager _config;
  344. private readonly IFileSystem _fileSystem;
  345. private ILogger _logger;
  346. public DeviceManagerEntryPoint(IDeviceManager deviceManager, IServerConfigurationManager config, IFileSystem fileSystem, ILogger logger)
  347. {
  348. _deviceManager = (DeviceManager)deviceManager;
  349. _config = config;
  350. _fileSystem = fileSystem;
  351. _logger = logger;
  352. }
  353. public async void Run()
  354. {
  355. if (!_config.Configuration.CameraUploadUpgraded && _config.Configuration.IsStartupWizardCompleted)
  356. {
  357. var path = _deviceManager.GetUploadsPath();
  358. if (_fileSystem.DirectoryExists(path))
  359. {
  360. try
  361. {
  362. await _deviceManager.EnsureLibraryFolder(path, null).ConfigureAwait(false);
  363. }
  364. catch (Exception ex)
  365. {
  366. _logger.LogError(ex, "Error creating camera uploads library");
  367. }
  368. _config.Configuration.CameraUploadUpgraded = true;
  369. _config.SaveConfiguration();
  370. }
  371. }
  372. }
  373. #region IDisposable Support
  374. private bool disposedValue = false; // To detect redundant calls
  375. protected virtual void Dispose(bool disposing)
  376. {
  377. if (!disposedValue)
  378. {
  379. if (disposing)
  380. {
  381. // TODO: dispose managed state (managed objects).
  382. }
  383. // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
  384. // TODO: set large fields to null.
  385. disposedValue = true;
  386. }
  387. }
  388. // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
  389. // ~DeviceManagerEntryPoint() {
  390. // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  391. // Dispose(false);
  392. // }
  393. // This code added to correctly implement the disposable pattern.
  394. public void Dispose()
  395. {
  396. // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
  397. Dispose(true);
  398. // TODO: uncomment the following line if the finalizer is overridden above.
  399. // GC.SuppressFinalize(this);
  400. }
  401. #endregion
  402. }
  403. public class DevicesConfigStore : IConfigurationFactory
  404. {
  405. public IEnumerable<ConfigurationStore> GetConfigurations()
  406. {
  407. return new ConfigurationStore[]
  408. {
  409. new ConfigurationStore
  410. {
  411. Key = "devices",
  412. ConfigurationType = typeof(DevicesOptions)
  413. }
  414. };
  415. }
  416. }
  417. public static class UploadConfigExtension
  418. {
  419. public static DevicesOptions GetUploadOptions(this IConfigurationManager config)
  420. {
  421. return config.GetConfiguration<DevicesOptions>("devices");
  422. }
  423. }
  424. }