ActivityLogEntryPoint.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. #pragma warning disable CS1591
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Common.Plugins;
  9. using MediaBrowser.Common.Updates;
  10. using MediaBrowser.Controller.Authentication;
  11. using MediaBrowser.Controller.Devices;
  12. using MediaBrowser.Controller.Entities;
  13. using MediaBrowser.Controller.Library;
  14. using MediaBrowser.Controller.Plugins;
  15. using MediaBrowser.Controller.Session;
  16. using MediaBrowser.Controller.Subtitles;
  17. using MediaBrowser.Model.Activity;
  18. using MediaBrowser.Model.Dto;
  19. using MediaBrowser.Model.Entities;
  20. using MediaBrowser.Model.Events;
  21. using MediaBrowser.Model.Globalization;
  22. using MediaBrowser.Model.Notifications;
  23. using MediaBrowser.Model.Tasks;
  24. using MediaBrowser.Model.Updates;
  25. using Microsoft.Extensions.Logging;
  26. namespace Emby.Server.Implementations.Activity
  27. {
  28. public sealed class ActivityLogEntryPoint : IServerEntryPoint
  29. {
  30. private readonly ILogger _logger;
  31. private readonly IInstallationManager _installationManager;
  32. private readonly ISessionManager _sessionManager;
  33. private readonly ITaskManager _taskManager;
  34. private readonly IActivityManager _activityManager;
  35. private readonly ILocalizationManager _localization;
  36. private readonly ISubtitleManager _subManager;
  37. private readonly IUserManager _userManager;
  38. private readonly IDeviceManager _deviceManager;
  39. /// <summary>
  40. /// Initializes a new instance of the <see cref="ActivityLogEntryPoint"/> class.
  41. /// </summary>
  42. /// <param name="logger"></param>
  43. /// <param name="sessionManager"></param>
  44. /// <param name="deviceManager"></param>
  45. /// <param name="taskManager"></param>
  46. /// <param name="activityManager"></param>
  47. /// <param name="localization"></param>
  48. /// <param name="installationManager"></param>
  49. /// <param name="subManager"></param>
  50. /// <param name="userManager"></param>
  51. /// <param name="appHost"></param>
  52. public ActivityLogEntryPoint(
  53. ILogger<ActivityLogEntryPoint> logger,
  54. ISessionManager sessionManager,
  55. IDeviceManager deviceManager,
  56. ITaskManager taskManager,
  57. IActivityManager activityManager,
  58. ILocalizationManager localization,
  59. IInstallationManager installationManager,
  60. ISubtitleManager subManager,
  61. IUserManager userManager)
  62. {
  63. _logger = logger;
  64. _sessionManager = sessionManager;
  65. _deviceManager = deviceManager;
  66. _taskManager = taskManager;
  67. _activityManager = activityManager;
  68. _localization = localization;
  69. _installationManager = installationManager;
  70. _subManager = subManager;
  71. _userManager = userManager;
  72. }
  73. public Task RunAsync()
  74. {
  75. _taskManager.TaskCompleted += OnTaskCompleted;
  76. _installationManager.PluginInstalled += OnPluginInstalled;
  77. _installationManager.PluginUninstalled += OnPluginUninstalled;
  78. _installationManager.PluginUpdated += OnPluginUpdated;
  79. _installationManager.PackageInstallationFailed += OnPackageInstallationFailed;
  80. _sessionManager.SessionStarted += OnSessionStarted;
  81. _sessionManager.AuthenticationFailed += OnAuthenticationFailed;
  82. _sessionManager.AuthenticationSucceeded += OnAuthenticationSucceeded;
  83. _sessionManager.SessionEnded += OnSessionEnded;
  84. _sessionManager.PlaybackStart += OnPlaybackStart;
  85. _sessionManager.PlaybackStopped += OnPlaybackStopped;
  86. _subManager.SubtitleDownloadFailure += OnSubtitleDownloadFailure;
  87. _userManager.UserCreated += OnUserCreated;
  88. _userManager.UserPasswordChanged += OnUserPasswordChanged;
  89. _userManager.UserDeleted += OnUserDeleted;
  90. _userManager.UserPolicyUpdated += OnUserPolicyUpdated;
  91. _userManager.UserLockedOut += OnUserLockedOut;
  92. _deviceManager.CameraImageUploaded += OnCameraImageUploaded;
  93. return Task.CompletedTask;
  94. }
  95. private void OnCameraImageUploaded(object sender, GenericEventArgs<CameraImageUploadInfo> e)
  96. {
  97. CreateLogEntry(new ActivityLogEntry
  98. {
  99. Name = string.Format(
  100. CultureInfo.InvariantCulture,
  101. _localization.GetLocalizedString("CameraImageUploadedFrom"),
  102. e.Argument.Device.Name),
  103. Type = NotificationType.CameraImageUploaded.ToString()
  104. });
  105. }
  106. private void OnUserLockedOut(object sender, GenericEventArgs<User> e)
  107. {
  108. CreateLogEntry(new ActivityLogEntry
  109. {
  110. Name = string.Format(
  111. CultureInfo.InvariantCulture,
  112. _localization.GetLocalizedString("UserLockedOutWithName"),
  113. e.Argument.Name),
  114. Type = NotificationType.UserLockedOut.ToString(),
  115. UserId = e.Argument.Id
  116. });
  117. }
  118. private void OnSubtitleDownloadFailure(object sender, SubtitleDownloadFailureEventArgs e)
  119. {
  120. CreateLogEntry(new ActivityLogEntry
  121. {
  122. Name = string.Format(
  123. CultureInfo.InvariantCulture,
  124. _localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
  125. e.Provider,
  126. Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
  127. Type = "SubtitleDownloadFailure",
  128. ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
  129. ShortOverview = e.Exception.Message
  130. });
  131. }
  132. private void OnPlaybackStopped(object sender, PlaybackStopEventArgs e)
  133. {
  134. var item = e.MediaInfo;
  135. if (item == null)
  136. {
  137. _logger.LogWarning("PlaybackStopped reported with null media info.");
  138. return;
  139. }
  140. if (e.Item != null && e.Item.IsThemeMedia)
  141. {
  142. // Don't report theme song or local trailer playback
  143. return;
  144. }
  145. if (e.Users.Count == 0)
  146. {
  147. return;
  148. }
  149. var user = e.Users[0];
  150. CreateLogEntry(new ActivityLogEntry
  151. {
  152. Name = string.Format(_localization.GetLocalizedString("UserStoppedPlayingItemWithValues"), user.Name, GetItemName(item), e.DeviceName),
  153. Type = GetPlaybackStoppedNotificationType(item.MediaType),
  154. UserId = user.Id
  155. });
  156. }
  157. private void OnPlaybackStart(object sender, PlaybackProgressEventArgs e)
  158. {
  159. var item = e.MediaInfo;
  160. if (item == null)
  161. {
  162. _logger.LogWarning("PlaybackStart reported with null media info.");
  163. return;
  164. }
  165. if (e.Item != null && e.Item.IsThemeMedia)
  166. {
  167. // Don't report theme song or local trailer playback
  168. return;
  169. }
  170. if (e.Users.Count == 0)
  171. {
  172. return;
  173. }
  174. var user = e.Users.First();
  175. CreateLogEntry(new ActivityLogEntry
  176. {
  177. Name = string.Format(
  178. CultureInfo.InvariantCulture,
  179. _localization.GetLocalizedString("UserStartedPlayingItemWithValues"),
  180. user.Name,
  181. GetItemName(item),
  182. e.DeviceName),
  183. Type = GetPlaybackNotificationType(item.MediaType),
  184. UserId = user.Id
  185. });
  186. }
  187. private static string GetItemName(BaseItemDto item)
  188. {
  189. var name = item.Name;
  190. if (!string.IsNullOrEmpty(item.SeriesName))
  191. {
  192. name = item.SeriesName + " - " + name;
  193. }
  194. if (item.Artists != null && item.Artists.Count > 0)
  195. {
  196. name = item.Artists[0] + " - " + name;
  197. }
  198. return name;
  199. }
  200. private static string GetPlaybackNotificationType(string mediaType)
  201. {
  202. if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
  203. {
  204. return NotificationType.AudioPlayback.ToString();
  205. }
  206. if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
  207. {
  208. return NotificationType.VideoPlayback.ToString();
  209. }
  210. return null;
  211. }
  212. private static string GetPlaybackStoppedNotificationType(string mediaType)
  213. {
  214. if (string.Equals(mediaType, MediaType.Audio, StringComparison.OrdinalIgnoreCase))
  215. {
  216. return NotificationType.AudioPlaybackStopped.ToString();
  217. }
  218. if (string.Equals(mediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase))
  219. {
  220. return NotificationType.VideoPlaybackStopped.ToString();
  221. }
  222. return null;
  223. }
  224. private void OnSessionEnded(object sender, SessionEventArgs e)
  225. {
  226. string name;
  227. var session = e.SessionInfo;
  228. if (string.IsNullOrEmpty(session.UserName))
  229. {
  230. name = string.Format(
  231. CultureInfo.InvariantCulture,
  232. _localization.GetLocalizedString("DeviceOfflineWithName"),
  233. session.DeviceName);
  234. // Causing too much spam for now
  235. return;
  236. }
  237. else
  238. {
  239. name = string.Format(
  240. CultureInfo.InvariantCulture,
  241. _localization.GetLocalizedString("UserOfflineFromDevice"),
  242. session.UserName,
  243. session.DeviceName);
  244. }
  245. CreateLogEntry(new ActivityLogEntry
  246. {
  247. Name = name,
  248. Type = "SessionEnded",
  249. ShortOverview = string.Format(
  250. CultureInfo.InvariantCulture,
  251. _localization.GetLocalizedString("LabelIpAddressValue"),
  252. session.RemoteEndPoint),
  253. UserId = session.UserId
  254. });
  255. }
  256. private void OnAuthenticationSucceeded(object sender, GenericEventArgs<AuthenticationResult> e)
  257. {
  258. var user = e.Argument.User;
  259. CreateLogEntry(new ActivityLogEntry
  260. {
  261. Name = string.Format(
  262. CultureInfo.InvariantCulture,
  263. _localization.GetLocalizedString("AuthenticationSucceededWithUserName"),
  264. user.Name),
  265. Type = "AuthenticationSucceeded",
  266. ShortOverview = string.Format(
  267. CultureInfo.InvariantCulture,
  268. _localization.GetLocalizedString("LabelIpAddressValue"),
  269. e.Argument.SessionInfo.RemoteEndPoint),
  270. UserId = user.Id
  271. });
  272. }
  273. private void OnAuthenticationFailed(object sender, GenericEventArgs<AuthenticationRequest> e)
  274. {
  275. CreateLogEntry(new ActivityLogEntry
  276. {
  277. Name = string.Format(
  278. CultureInfo.InvariantCulture,
  279. _localization.GetLocalizedString("FailedLoginAttemptWithUserName"),
  280. e.Argument.Username),
  281. Type = "AuthenticationFailed",
  282. ShortOverview = string.Format(
  283. CultureInfo.InvariantCulture,
  284. _localization.GetLocalizedString("LabelIpAddressValue"),
  285. e.Argument.RemoteEndPoint),
  286. Severity = LogLevel.Error
  287. });
  288. }
  289. private void OnUserPolicyUpdated(object sender, GenericEventArgs<User> e)
  290. {
  291. CreateLogEntry(new ActivityLogEntry
  292. {
  293. Name = string.Format(
  294. CultureInfo.InvariantCulture,
  295. _localization.GetLocalizedString("UserPolicyUpdatedWithName"),
  296. e.Argument.Name),
  297. Type = "UserPolicyUpdated",
  298. UserId = e.Argument.Id
  299. });
  300. }
  301. private void OnUserDeleted(object sender, GenericEventArgs<User> e)
  302. {
  303. CreateLogEntry(new ActivityLogEntry
  304. {
  305. Name = string.Format(
  306. CultureInfo.InvariantCulture,
  307. _localization.GetLocalizedString("UserDeletedWithName"),
  308. e.Argument.Name),
  309. Type = "UserDeleted"
  310. });
  311. }
  312. private void OnUserPasswordChanged(object sender, GenericEventArgs<User> e)
  313. {
  314. CreateLogEntry(new ActivityLogEntry
  315. {
  316. Name = string.Format(
  317. CultureInfo.InvariantCulture,
  318. _localization.GetLocalizedString("UserPasswordChangedWithName"),
  319. e.Argument.Name),
  320. Type = "UserPasswordChanged",
  321. UserId = e.Argument.Id
  322. });
  323. }
  324. private void OnUserCreated(object sender, GenericEventArgs<User> e)
  325. {
  326. CreateLogEntry(new ActivityLogEntry
  327. {
  328. Name = string.Format(
  329. CultureInfo.InvariantCulture,
  330. _localization.GetLocalizedString("UserCreatedWithName"),
  331. e.Argument.Name),
  332. Type = "UserCreated",
  333. UserId = e.Argument.Id
  334. });
  335. }
  336. private void OnSessionStarted(object sender, SessionEventArgs e)
  337. {
  338. string name;
  339. var session = e.SessionInfo;
  340. if (string.IsNullOrEmpty(session.UserName))
  341. {
  342. name = string.Format(
  343. CultureInfo.InvariantCulture,
  344. _localization.GetLocalizedString("DeviceOnlineWithName"),
  345. session.DeviceName);
  346. // Causing too much spam for now
  347. return;
  348. }
  349. else
  350. {
  351. name = string.Format(
  352. CultureInfo.InvariantCulture,
  353. _localization.GetLocalizedString("UserOnlineFromDevice"),
  354. session.UserName,
  355. session.DeviceName);
  356. }
  357. CreateLogEntry(new ActivityLogEntry
  358. {
  359. Name = name,
  360. Type = "SessionStarted",
  361. ShortOverview = string.Format(
  362. CultureInfo.InvariantCulture,
  363. _localization.GetLocalizedString("LabelIpAddressValue"),
  364. session.RemoteEndPoint),
  365. UserId = session.UserId
  366. });
  367. }
  368. private void OnPluginUpdated(object sender, GenericEventArgs<(IPlugin, PackageVersionInfo)> e)
  369. {
  370. CreateLogEntry(new ActivityLogEntry
  371. {
  372. Name = string.Format(
  373. CultureInfo.InvariantCulture,
  374. _localization.GetLocalizedString("PluginUpdatedWithName"),
  375. e.Argument.Item1.Name),
  376. Type = NotificationType.PluginUpdateInstalled.ToString(),
  377. ShortOverview = string.Format(
  378. CultureInfo.InvariantCulture,
  379. _localization.GetLocalizedString("VersionNumber"),
  380. e.Argument.Item2.versionStr),
  381. Overview = e.Argument.Item2.description
  382. });
  383. }
  384. private void OnPluginUninstalled(object sender, GenericEventArgs<IPlugin> e)
  385. {
  386. CreateLogEntry(new ActivityLogEntry
  387. {
  388. Name = string.Format(
  389. CultureInfo.InvariantCulture,
  390. _localization.GetLocalizedString("PluginUninstalledWithName"),
  391. e.Argument.Name),
  392. Type = NotificationType.PluginUninstalled.ToString()
  393. });
  394. }
  395. private void OnPluginInstalled(object sender, GenericEventArgs<PackageVersionInfo> e)
  396. {
  397. CreateLogEntry(new ActivityLogEntry
  398. {
  399. Name = string.Format(
  400. CultureInfo.InvariantCulture,
  401. _localization.GetLocalizedString("PluginInstalledWithName"),
  402. e.Argument.name),
  403. Type = NotificationType.PluginInstalled.ToString(),
  404. ShortOverview = string.Format(
  405. CultureInfo.InvariantCulture,
  406. _localization.GetLocalizedString("VersionNumber"),
  407. e.Argument.versionStr)
  408. });
  409. }
  410. private void OnPackageInstallationFailed(object sender, InstallationFailedEventArgs e)
  411. {
  412. var installationInfo = e.InstallationInfo;
  413. CreateLogEntry(new ActivityLogEntry
  414. {
  415. Name = string.Format(
  416. CultureInfo.InvariantCulture,
  417. _localization.GetLocalizedString("NameInstallFailed"),
  418. installationInfo.Name),
  419. Type = NotificationType.InstallationFailed.ToString(),
  420. ShortOverview = string.Format(
  421. CultureInfo.InvariantCulture,
  422. _localization.GetLocalizedString("VersionNumber"),
  423. installationInfo.Version),
  424. Overview = e.Exception.Message
  425. });
  426. }
  427. private void OnTaskCompleted(object sender, TaskCompletionEventArgs e)
  428. {
  429. var result = e.Result;
  430. var task = e.Task;
  431. var activityTask = task.ScheduledTask as IConfigurableScheduledTask;
  432. if (activityTask != null && !activityTask.IsLogged)
  433. {
  434. return;
  435. }
  436. var time = result.EndTimeUtc - result.StartTimeUtc;
  437. var runningTime = string.Format(
  438. CultureInfo.InvariantCulture,
  439. _localization.GetLocalizedString("LabelRunningTimeValue"),
  440. ToUserFriendlyString(time));
  441. if (result.Status == TaskCompletionStatus.Failed)
  442. {
  443. var vals = new List<string>();
  444. if (!string.IsNullOrEmpty(e.Result.ErrorMessage))
  445. {
  446. vals.Add(e.Result.ErrorMessage);
  447. }
  448. if (!string.IsNullOrEmpty(e.Result.LongErrorMessage))
  449. {
  450. vals.Add(e.Result.LongErrorMessage);
  451. }
  452. CreateLogEntry(new ActivityLogEntry
  453. {
  454. Name = string.Format(
  455. CultureInfo.InvariantCulture,
  456. _localization.GetLocalizedString("ScheduledTaskFailedWithName"),
  457. task.Name),
  458. Type = NotificationType.TaskFailed.ToString(),
  459. Overview = string.Join(Environment.NewLine, vals),
  460. ShortOverview = runningTime,
  461. Severity = LogLevel.Error
  462. });
  463. }
  464. }
  465. private void CreateLogEntry(ActivityLogEntry entry)
  466. => _activityManager.Create(entry);
  467. /// <inheritdoc />
  468. public void Dispose()
  469. {
  470. _taskManager.TaskCompleted -= OnTaskCompleted;
  471. _installationManager.PluginInstalled -= OnPluginInstalled;
  472. _installationManager.PluginUninstalled -= OnPluginUninstalled;
  473. _installationManager.PluginUpdated -= OnPluginUpdated;
  474. _installationManager.PackageInstallationFailed -= OnPackageInstallationFailed;
  475. _sessionManager.SessionStarted -= OnSessionStarted;
  476. _sessionManager.AuthenticationFailed -= OnAuthenticationFailed;
  477. _sessionManager.AuthenticationSucceeded -= OnAuthenticationSucceeded;
  478. _sessionManager.SessionEnded -= OnSessionEnded;
  479. _sessionManager.PlaybackStart -= OnPlaybackStart;
  480. _sessionManager.PlaybackStopped -= OnPlaybackStopped;
  481. _subManager.SubtitleDownloadFailure -= OnSubtitleDownloadFailure;
  482. _userManager.UserCreated -= OnUserCreated;
  483. _userManager.UserPasswordChanged -= OnUserPasswordChanged;
  484. _userManager.UserDeleted -= OnUserDeleted;
  485. _userManager.UserPolicyUpdated -= OnUserPolicyUpdated;
  486. _userManager.UserLockedOut -= OnUserLockedOut;
  487. _deviceManager.CameraImageUploaded -= OnCameraImageUploaded;
  488. }
  489. /// <summary>
  490. /// Constructs a user-friendly string for this TimeSpan instance.
  491. /// </summary>
  492. public static string ToUserFriendlyString(TimeSpan span)
  493. {
  494. const int DaysInYear = 365;
  495. const int DaysInMonth = 30;
  496. // Get each non-zero value from TimeSpan component
  497. var values = new List<string>();
  498. // Number of years
  499. int days = span.Days;
  500. if (days >= DaysInYear)
  501. {
  502. int years = days / DaysInYear;
  503. values.Add(CreateValueString(years, "year"));
  504. days = days % DaysInYear;
  505. }
  506. // Number of months
  507. if (days >= DaysInMonth)
  508. {
  509. int months = days / DaysInMonth;
  510. values.Add(CreateValueString(months, "month"));
  511. days = days % DaysInMonth;
  512. }
  513. // Number of days
  514. if (days >= 1)
  515. {
  516. values.Add(CreateValueString(days, "day"));
  517. }
  518. // Number of hours
  519. if (span.Hours >= 1)
  520. {
  521. values.Add(CreateValueString(span.Hours, "hour"));
  522. }
  523. // Number of minutes
  524. if (span.Minutes >= 1)
  525. {
  526. values.Add(CreateValueString(span.Minutes, "minute"));
  527. }
  528. // Number of seconds (include when 0 if no other components included)
  529. if (span.Seconds >= 1 || values.Count == 0)
  530. {
  531. values.Add(CreateValueString(span.Seconds, "second"));
  532. }
  533. // Combine values into string
  534. var builder = new StringBuilder();
  535. for (int i = 0; i < values.Count; i++)
  536. {
  537. if (builder.Length > 0)
  538. {
  539. builder.Append(i == values.Count - 1 ? " and " : ", ");
  540. }
  541. builder.Append(values[i]);
  542. }
  543. // Return result
  544. return builder.ToString();
  545. }
  546. /// <summary>
  547. /// Constructs a string description of a time-span value.
  548. /// </summary>
  549. /// <param name="value">The value of this item.</param>
  550. /// <param name="description">The name of this item (singular form).</param>
  551. private static string CreateValueString(int value, string description)
  552. {
  553. return string.Format(
  554. CultureInfo.InvariantCulture,
  555. "{0:#,##0} {1}",
  556. value,
  557. value == 1 ? description : string.Format(CultureInfo.InvariantCulture, "{0}s", description));
  558. }
  559. }
  560. }