ActivityLogEntryPoint.cs 23 KB

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