ActivityLogEntryPoint.cs 22 KB

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