2
0

ActivityLogEntryPoint.cs 21 KB

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