ActivityLogEntryPoint.cs 23 KB

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