BililiveRecorder/BililiveRecorder.WPF/Program.cs

363 lines
14 KiB
C#
Raw Normal View History

using System;
2021-02-23 18:03:37 +08:00
using System.CommandLine;
2022-06-07 22:41:09 +08:00
using System.CommandLine.NamingConventionBinder;
using System.IO;
2021-12-17 19:51:48 +08:00
using System.Net;
2022-05-12 17:29:34 +08:00
using System.Net.Http;
using System.Runtime.ExceptionServices;
2021-04-14 23:46:24 +08:00
using System.Runtime.InteropServices;
using System.Security;
2021-02-23 18:03:37 +08:00
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
2022-06-17 17:42:50 +08:00
using BililiveRecorder.Flv.Pipeline;
2021-04-14 23:46:24 +08:00
using BililiveRecorder.ToolBox;
2022-05-14 22:28:03 +08:00
using Esprima;
2022-05-12 17:29:34 +08:00
using Jint.Runtime;
using Sentry;
2022-05-11 00:20:57 +08:00
using Sentry.Extensibility;
2021-02-23 18:03:37 +08:00
using Serilog;
using Serilog.Core;
using Serilog.Exceptions;
using Serilog.Formatting.Compact;
2021-06-04 23:00:50 +08:00
using Serilog.Formatting.Display;
2022-07-01 18:53:05 +08:00
using Squirrel;
2021-02-23 18:03:37 +08:00
#nullable enable
namespace BililiveRecorder.WPF
{
2021-02-23 18:03:37 +08:00
internal static class Program
{
2021-02-23 18:03:37 +08:00
private const int CODE__WPF = 0x5F_57_50_46;
2021-02-23 18:03:37 +08:00
internal static readonly LoggingLevelSwitch levelSwitchGlobal;
internal static readonly LoggingLevelSwitch levelSwitchConsole;
internal static readonly Logger logger;
internal static readonly Update update;
internal static Task? updateTask;
2021-06-07 22:05:36 +08:00
#if DEBUG
2022-07-01 19:25:56 +08:00
internal static readonly bool DebugMode = System.Diagnostics.Debugger.IsAttached;
2021-06-07 22:05:36 +08:00
#else
internal static readonly bool DebugMode = false;
#endif
2021-02-23 18:03:37 +08:00
static Program()
{
2021-04-14 23:46:24 +08:00
AttachConsole(-1);
2021-02-23 18:03:37 +08:00
levelSwitchGlobal = new LoggingLevelSwitch(Serilog.Events.LogEventLevel.Debug);
2021-06-07 22:05:36 +08:00
if (DebugMode)
2021-02-27 13:28:21 +08:00
levelSwitchGlobal.MinimumLevel = Serilog.Events.LogEventLevel.Verbose;
2021-02-23 18:03:37 +08:00
levelSwitchConsole = new LoggingLevelSwitch(Serilog.Events.LogEventLevel.Error);
logger = BuildLogger();
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
2021-05-02 21:02:33 +08:00
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
2021-02-23 18:03:37 +08:00
Log.Logger = logger;
SentrySdk.ConfigureScope(s =>
{
s.SetTag("fullsemver", GitVersionInformation.FullSemVer);
2021-05-17 23:29:33 +08:00
});
_ = SentrySdk.ConfigureScopeAsync(async s =>
{
var path = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location), "..", "packages", ".betaId"));
for (var i = 0; i < 10; i++)
{
2021-05-17 23:29:33 +08:00
if (i != 0)
await Task.Delay(TimeSpan.FromSeconds(5));
try
{
if (!File.Exists(path))
continue;
var content = File.ReadAllText(path);
if (Guid.TryParse(content, out var id))
{
s.User.Id = id.ToString();
return;
}
}
catch (Exception)
{ }
}
2021-02-23 18:03:37 +08:00
});
ServicePointManager.Expect100Continue = false;
2021-02-23 18:03:37 +08:00
update = new Update(logger);
}
2021-02-23 18:03:37 +08:00
[STAThread]
public static int Main(string[] args)
{
try
{
2021-05-01 00:22:27 +08:00
logger.Debug("Starting, Version: {Version}, CurrentDirectory: {CurrentDirectory}, CommandLine: {CommandLine}",
GitVersionInformation.InformationalVersion,
Environment.CurrentDirectory,
Environment.CommandLine);
2021-02-23 18:03:37 +08:00
var code = BuildCommand().Invoke(args);
2021-05-01 00:22:27 +08:00
logger.Debug("Exit code: {ExitCode}, RunWpf: {RunWpf}", code, code == CODE__WPF);
2021-02-23 18:03:37 +08:00
return code == CODE__WPF ? Commands.RunWpfReal() : code;
}
finally
{
logger.Dispose();
}
}
private static RootCommand BuildCommand()
{
var run = new Command("run", "Run BililiveRecorder at path")
{
new Argument<string?>("path", () => null, "Work directory"),
2021-07-16 21:03:07 +08:00
new Option<bool>("--ask-path", "Ask path in GUI even when \"don't ask again\" is selected before."),
new Option<bool>("--hide", "Minimize to tray")
2021-02-23 18:03:37 +08:00
};
2021-07-16 21:03:07 +08:00
run.Handler = CommandHandler.Create((string? path, bool askPath, bool hide) => Commands.RunWpfHandler(path: path, squirrelFirstrun: false, askPath: askPath, hide: hide));
2021-02-23 18:03:37 +08:00
var root = new RootCommand("")
{
new Option<bool>("--squirrel-firstrun")
{
2021-02-23 18:03:37 +08:00
IsHidden = true
2021-04-14 23:46:24 +08:00
},
2022-07-02 00:01:30 +08:00
new Option<SemanticVersion?>("--squirrel-install", getDefaultValue: () => null)
2022-07-01 18:53:05 +08:00
{
IsHidden = true,
IsRequired = false,
},
2022-07-02 00:01:30 +08:00
new Option<SemanticVersion?>("--squirrel-updated", getDefaultValue: () => null)
2022-07-01 18:53:05 +08:00
{
IsHidden = true,
IsRequired = false,
},
2022-07-02 00:01:30 +08:00
new Option<SemanticVersion?>("--squirrel-obsolete", getDefaultValue: () => null)
2022-07-01 18:53:05 +08:00
{
IsHidden = true,
IsRequired = false,
},
2022-07-02 00:01:30 +08:00
new Option<SemanticVersion?>("--squirrel-uninstall", getDefaultValue: () => null)
2022-07-01 18:53:05 +08:00
{
IsHidden = true,
IsRequired = false,
},
run,
2021-04-14 23:46:24 +08:00
new ToolCommand(),
2021-02-23 18:03:37 +08:00
};
2022-07-01 18:53:05 +08:00
root.Handler = CommandHandler.Create<bool, SemanticVersion?, SemanticVersion?, SemanticVersion?, SemanticVersion?>(Commands.RunRootCommandHandler);
2021-02-23 18:03:37 +08:00
return root;
}
private static class Commands
{
2022-07-01 18:53:05 +08:00
private static IAppTools GetSquirrelAppTools()
{
var m = new UpdateManager(updateSource: null, applicationIdOverride: null, localAppDataDirectoryOverride: null);
m.Dispose();
return m;
}
internal static int RunRootCommandHandler(bool squirrelFirstrun, SemanticVersion? squirrelInstall, SemanticVersion? squirrelUpdated, SemanticVersion? squirrelObsolete, SemanticVersion? squirrelUninstall)
{
var tools = GetSquirrelAppTools();
if (squirrelInstall is not null)
{
tools.CreateShortcutForThisExe();
Environment.Exit(0);
return 0;
}
else if (squirrelUpdated is not null)
{
Environment.Exit(0);
return 0;
}
else if (squirrelObsolete is not null)
{
Environment.Exit(0);
return 0;
}
else if (squirrelUninstall is not null)
{
tools.RemoveShortcutForThisExe();
Environment.Exit(0);
return 0;
}
else
{
tools.SetProcessAppUserModelId();
return RunWpfHandler(path: null, squirrelFirstrun: squirrelFirstrun, askPath: false, hide: false);
}
}
2021-07-16 21:03:07 +08:00
internal static int RunWpfHandler(string? path, bool squirrelFirstrun, bool askPath, bool hide)
2021-02-23 18:03:37 +08:00
{
Pages.RootPage.CommandArgumentRecorderPath = path;
Pages.RootPage.CommandArgumentFirstRun = squirrelFirstrun;
Pages.RootPage.CommandArgumentAskPath = askPath;
2021-07-16 21:03:07 +08:00
Pages.RootPage.CommandArgumentHide = hide;
2021-02-23 18:03:37 +08:00
return CODE__WPF;
}
internal static int RunWpfReal()
{
2021-02-23 18:03:37 +08:00
var cancel = new CancellationTokenSource();
var token = cancel.Token;
try
{
2021-06-16 18:33:45 +08:00
SleepBlocker.Start();
2021-02-23 18:03:37 +08:00
var app = new App();
app.InitializeComponent();
app.DispatcherUnhandledException += App_DispatcherUnhandledException;
updateTask = Task.Run(async () =>
{
2021-02-23 18:03:37 +08:00
while (!token.IsCancellationRequested)
{
await update.UpdateAsync().ConfigureAwait(false);
await Task.Delay(TimeSpan.FromDays(1), token).ConfigureAwait(false);
}
});
return app.Run();
}
finally
{
cancel.Cancel();
StreamStartedNotification.Cleanup();
2021-02-23 18:03:37 +08:00
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
update.WaitForUpdatesOnShutdownAsync().GetAwaiter().GetResult();
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
}
}
}
2021-06-16 18:33:45 +08:00
private static class SleepBlocker
{
internal static void Start()
{
var t = new Thread(EntryPoint)
{
Name = "SystemSleepBlocker",
IsBackground = true,
Priority = ThreadPriority.BelowNormal
};
t.Start();
}
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
[Flags]
private enum EXECUTION_STATE : uint
{
ES_AWAYMODE_REQUIRED = 0x00000040,
ES_CONTINUOUS = 0x80000000,
ES_DISPLAY_REQUIRED = 0x00000002,
ES_SYSTEM_REQUIRED = 0x00000001
}
private static void EntryPoint()
{
try
{
2021-09-07 23:22:09 +08:00
while (true)
2021-06-16 18:33:45 +08:00
{
2021-09-07 23:22:09 +08:00
try
{
_ = SetThreadExecutionState(EXECUTION_STATE.ES_SYSTEM_REQUIRED | EXECUTION_STATE.ES_CONTINUOUS);
}
catch (Exception) { }
Thread.Sleep(millisecondsTimeout: 30 * 1000);
2021-06-16 18:33:45 +08:00
}
}
catch (Exception) { }
}
}
2022-06-07 01:52:59 +08:00
private static Logger BuildLogger()
{
var logFilePath = Environment.GetEnvironmentVariable("BILILIVERECORDER_LOG_FILE_PATH");
if (string.IsNullOrWhiteSpace(logFilePath))
logFilePath = Path.Combine(AppContext.BaseDirectory, "logs", "bilirec.txt");
return new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitchGlobal)
.Enrich.WithProcessId()
.Enrich.WithThreadId()
.Enrich.WithThreadName()
.Enrich.FromLogContext()
.Enrich.WithExceptionDetails()
.Destructure.AsScalar<IPAddress>()
2022-06-17 17:42:50 +08:00
.Destructure.AsScalar<ProcessingComment>()
2022-06-07 01:52:59 +08:00
.Destructure.ByTransforming<Flv.Xml.XmlFlvFile.XmlFlvFileMeta>(x => new
{
x.Version,
x.ExportTime,
x.FileSize,
x.FileCreationTime,
x.FileModificationTime,
})
.WriteTo.Console(levelSwitch: levelSwitchConsole)
2021-02-27 13:28:21 +08:00
#if DEBUG
2022-06-07 01:52:59 +08:00
.WriteTo.Debug()
.WriteTo.Sink<WpfLogEventSink>(Serilog.Events.LogEventLevel.Debug)
2021-02-27 13:28:21 +08:00
#else
2022-06-07 01:52:59 +08:00
.WriteTo.Sink<WpfLogEventSink>(Serilog.Events.LogEventLevel.Information)
2021-02-27 13:28:21 +08:00
#endif
2022-06-07 01:52:59 +08:00
.WriteTo.File(new CompactJsonFormatter(), logFilePath, shared: true, rollingInterval: RollingInterval.Day, rollOnFileSizeLimit: true)
.WriteTo.Sentry(o =>
{
o.Dsn = "https://6f92720d5ce84b2dba5db75ab5a5014d@o210546.ingest.sentry.io/5556540";
2022-06-07 01:52:59 +08:00
o.SendDefaultPii = true;
o.IsGlobalModeEnabled = true;
o.DisableAppDomainUnhandledExceptionCapture();
o.DisableTaskUnobservedTaskExceptionCapture();
o.AddExceptionFilterForType<HttpRequestException>();
o.AddExceptionFilterForType<OutOfMemoryException>();
o.AddExceptionFilterForType<JintException>();
o.AddExceptionFilterForType<ParserException>();
o.AddEventProcessor(new SentryEventProcessor());
2021-02-23 18:03:37 +08:00
2022-06-07 01:52:59 +08:00
o.TextFormatter = new MessageTemplateTextFormatter("[{RoomId}] {Message}{NewLine}{Exception}{@ExceptionDetail:j}");
2021-06-04 23:00:50 +08:00
2022-06-07 01:52:59 +08:00
o.MinimumBreadcrumbLevel = Serilog.Events.LogEventLevel.Debug;
o.MinimumEventLevel = Serilog.Events.LogEventLevel.Error;
2021-03-01 22:46:16 +08:00
#if DEBUG
2022-06-07 01:52:59 +08:00
o.Environment = "debug-build";
2021-03-01 22:46:16 +08:00
#else
2022-06-07 01:52:59 +08:00
o.Environment = "release-build";
2021-03-01 22:46:16 +08:00
#endif
2022-06-07 01:52:59 +08:00
})
.CreateLogger();
}
2021-02-23 18:03:37 +08:00
2021-04-14 23:46:24 +08:00
[DllImport("kernel32")]
private static extern bool AttachConsole(int pid);
[HandleProcessCorruptedStateExceptions, SecurityCritical]
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
if (e.ExceptionObject is Exception ex)
2021-05-18 22:36:37 +08:00
logger.Fatal(ex, "Unhandled exception from AppDomain.UnhandledException");
}
2021-05-02 21:02:33 +08:00
[HandleProcessCorruptedStateExceptions, SecurityCritical]
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) =>
logger.Error(e.Exception, "Unobserved exception from TaskScheduler.UnobservedTaskException");
[HandleProcessCorruptedStateExceptions, SecurityCritical]
2021-02-23 18:03:37 +08:00
private static void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) =>
2021-05-18 22:36:37 +08:00
logger.Fatal(e.Exception, "Unhandled exception from Application.DispatcherUnhandledException");
2022-05-11 00:20:57 +08:00
private class SentryEventProcessor : ISentryEventProcessor
{
private const string JintConsole = "BililiveRecorder.Core.Scripting.Runtime.JintConsole";
2022-05-12 17:29:34 +08:00
private static readonly string UserScriptRunner = typeof(Core.Scripting.UserScriptRunner).FullName;
public SentryEvent? Process(SentryEvent e) => (e?.Logger == JintConsole || e?.Logger == UserScriptRunner) ? null : e;
2022-05-11 00:20:57 +08:00
}
}
}