diff --git a/Kyoo.WindowsHost/Kyoo.WindowsHost.csproj b/Kyoo.WindowsHost/Kyoo.WindowsHost.csproj
new file mode 100644
index 00000000..fe167974
--- /dev/null
+++ b/Kyoo.WindowsHost/Kyoo.WindowsHost.csproj
@@ -0,0 +1,22 @@
+
+
+
+ WinExe
+ net5.0-windows
+ enable
+ true
+ Kyoo.Host.Windows
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
\ No newline at end of file
diff --git a/Kyoo.WindowsHost/Program.cs b/Kyoo.WindowsHost/Program.cs
new file mode 100644
index 00000000..ace92e24
--- /dev/null
+++ b/Kyoo.WindowsHost/Program.cs
@@ -0,0 +1,28 @@
+using System.Threading.Tasks;
+using Autofac;
+using Microsoft.Extensions.Hosting;
+
+namespace Kyoo.Host.Windows
+{
+ public static class Program
+ {
+ ///
+ /// The main entry point for the application that overrides the default host ().
+ /// It adds a system trait for windows and since the host is build as a windows executable instead of a console
+ /// app, the console is not showed.
+ ///
+ public static async Task Main(string[] args)
+ {
+ Kyoo.Program.SetupDataDir(args);
+
+ IHost host = Kyoo.Program.CreateWebHostBuilder(args)
+ .ConfigureContainer(builder =>
+ {
+ builder.RegisterType().As().SingleInstance();
+ })
+ .Build();
+
+ await Kyoo.Program.StartWithHost(host);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Kyoo.WindowsHost/SystemTrait.cs b/Kyoo.WindowsHost/SystemTrait.cs
new file mode 100644
index 00000000..22009ab1
--- /dev/null
+++ b/Kyoo.WindowsHost/SystemTrait.cs
@@ -0,0 +1,135 @@
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using System.IO;
+using System.Threading;
+using System.Windows.Forms;
+using Autofac;
+using Kyoo.Models.Options;
+using Microsoft.Extensions.Options;
+
+namespace Kyoo.Host.Windows
+{
+ ///
+ /// A singleton that add an notification icon on the window's toolbar.
+ ///
+ public sealed class SystemTrait : IStartable, IDisposable
+ {
+ ///
+ /// The options containing the .
+ ///
+ private readonly IOptions _options;
+
+ ///
+ /// The thread where the trait is running.
+ ///
+ private Thread? _thread;
+
+
+ ///
+ /// Create a new .
+ ///
+ /// The options to use.
+ public SystemTrait(IOptions options)
+ {
+ _options = options;
+ }
+
+ ///
+ public void Start()
+ {
+ _thread = new Thread(() => InternalSystemTrait.Run(_options))
+ {
+ IsBackground = true
+ };
+ _thread.Start();
+ }
+
+ ///
+ public void Dispose()
+ {
+ // TODO not sure that the trait is ended and that it does shutdown but the only way to shutdown the
+ // app anyway is via the Trait's Exit or a Signal so it's fine.
+ _thread?.Join();
+ _thread = null;
+ }
+
+ ///
+ /// The internal class for . It should be invoked via
+ /// .
+ ///
+ private class InternalSystemTrait : ApplicationContext
+ {
+ ///
+ /// The options containing the .
+ ///
+ private readonly IOptions _options;
+
+ ///
+ /// The Icon that is displayed in the window's bar.
+ ///
+ private readonly NotifyIcon _icon;
+
+ ///
+ /// Create a new . Used only by .
+ ///
+ /// The option containing the public url.
+ private InternalSystemTrait(IOptions options)
+ {
+ _options = options;
+
+ Application.ApplicationExit += (_, _) => Dispose();
+
+ _icon = new NotifyIcon();
+ _icon.Text = "Kyoo";
+ _icon.Icon = new Icon(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "kyoo.ico"));
+ _icon.Visible = true;
+ _icon.MouseClick += (_, e) =>
+ {
+ if (e.Button != MouseButtons.Left)
+ return;
+ _StartBrowser();
+ };
+
+ _icon.ContextMenuStrip = new ContextMenuStrip();
+ _icon.ContextMenuStrip.Items.AddRange(new ToolStripItem[]
+ {
+ new ToolStripMenuItem("Exit", null, (_, _) => { Environment.Exit(0); })
+ });
+ }
+
+ ///
+ /// Run the trait in the current thread, this method does not return while the trait is running.
+ ///
+ /// The options to pass to .
+ public static void Run(IOptions options)
+ {
+ using InternalSystemTrait trait = new(options);
+ Application.Run(trait);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ _icon.Visible = false;
+ _icon.Dispose();
+ }
+
+ ///
+ /// Open kyoo's page in the user's default browser.
+ ///
+ private void _StartBrowser()
+ {
+ Process browser = new()
+ {
+ StartInfo = new ProcessStartInfo(_options.Value.PublicUrl.ToString())
+ {
+ UseShellExecute = true
+ }
+ };
+ browser.Start();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Kyoo.WindowsHost/kyoo.ico b/Kyoo.WindowsHost/kyoo.ico
new file mode 100644
index 00000000..61e16024
Binary files /dev/null and b/Kyoo.WindowsHost/kyoo.ico differ
diff --git a/Kyoo.sln b/Kyoo.sln
index 701d760b..6ab7d6d9 100644
--- a/Kyoo.sln
+++ b/Kyoo.sln
@@ -19,6 +19,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "tests\Kyoo.Te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.WebApp", "Kyoo.WebApp\Kyoo.WebApp.csproj", "{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.WindowsHost", "Kyoo.WindowsHost\Kyoo.WindowsHost.csproj", "{98851001-40DD-46A6-94B3-2F8D90722076}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -65,5 +67,13 @@ Global
{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2374D500-1ADB-4752-85DB-8BB0DDF5A8E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4FF1ECD9-6EEF-4440-B037-A661D78FB04D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {98851001-40DD-46A6-94B3-2F8D90722076}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {98851001-40DD-46A6-94B3-2F8D90722076}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {98851001-40DD-46A6-94B3-2F8D90722076}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {98851001-40DD-46A6-94B3-2F8D90722076}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Kyoo/CoreModule.cs b/Kyoo/CoreModule.cs
index b6445b3d..2d1e35e8 100644
--- a/Kyoo/CoreModule.cs
+++ b/Kyoo/CoreModule.cs
@@ -70,10 +70,11 @@ namespace Kyoo
builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().SingleInstance();
+ builder.RegisterType().As().As().SingleInstance();
+
builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().InstancePerLifetimeScope();
- builder.RegisterType().As().SingleInstance();
builder.RegisterType().As().InstancePerLifetimeScope();
builder.RegisterType().As().SingleInstance();
@@ -135,8 +136,6 @@ namespace Kyoo
});
services.AddHttpClient();
-
- services.AddHostedService(x => x.GetService() as TaskManager);
}
///
diff --git a/Kyoo/Kyoo.csproj b/Kyoo/Kyoo.csproj
index 28ac7ff0..d4a573f9 100644
--- a/Kyoo/Kyoo.csproj
+++ b/Kyoo/Kyoo.csproj
@@ -44,7 +44,8 @@
-
+
diff --git a/Kyoo/Program.cs b/Kyoo/Program.cs
index e9f21a0b..f905b8ba 100644
--- a/Kyoo/Program.cs
+++ b/Kyoo/Program.cs
@@ -7,6 +7,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
+using SEnvironment = System.Environment;
namespace Kyoo
{
@@ -28,20 +29,23 @@ namespace Kyoo
#else
private const string Environment = "Production";
#endif
-
+
///
/// Main function of the program
///
/// Command line arguments
- public static async Task Main(string[] args)
+ public static Task Main(string[] args)
+ {
+ SetupDataDir(args);
+ return StartWithHost(CreateWebHostBuilder(args).Build());
+ }
+
+ ///
+ /// Start the given host and log failing exceptions.
+ ///
+ /// The host to start.
+ public static async Task StartWithHost(IHost host)
{
- if (!File.Exists(JsonConfigPath))
- File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, JsonConfigPath), JsonConfigPath);
-
- IHost host = CreateWebHostBuilder(args)
- .UseEnvironment(Environment)
- .Build();
-
try
{
host.Services.GetRequiredService>()
@@ -65,6 +69,7 @@ namespace Kyoo
return builder.SetBasePath(System.Environment.CurrentDirectory)
.AddJsonFile(JsonConfigPath, false, true)
.AddEnvironmentVariables()
+ .AddEnvironmentVariables("KYOO_")
.AddCommandLine(args);
}
@@ -73,7 +78,7 @@ namespace Kyoo
///
/// The host context that contains the configuration
/// The logger builder to configure.
- private static void _ConfigureLogging(HostBuilderContext context, ILoggingBuilder builder)
+ public static void ConfigureLogging(HostBuilderContext context, ILoggingBuilder builder)
{
builder.AddConfiguration(context.Configuration.GetSection("logging"))
.AddSimpleConsole(x =>
@@ -89,18 +94,19 @@ namespace Kyoo
///
/// Command line parameters that can be handled by kestrel
///
- /// An action to configure the logging. If it is null, will be used.
+ /// An action to configure the logging. If it is null, will be used.
///
/// A new web host instance
public static IHostBuilder CreateWebHostBuilder(string[] args,
Action loggingConfiguration = null)
{
IConfiguration configuration = SetupConfig(new ConfigurationBuilder(), args).Build();
- loggingConfiguration ??= _ConfigureLogging;
+ loggingConfiguration ??= ConfigureLogging;
return new HostBuilder()
.UseServiceProviderFactory(new AutofacServiceProviderFactory())
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
+ .UseEnvironment(Environment)
.ConfigureAppConfiguration(x => SetupConfig(x, args))
.ConfigureLogging(loggingConfiguration)
.ConfigureServices(x => x.AddRouting())
@@ -113,6 +119,32 @@ namespace Kyoo
);
}
+ ///
+ /// Parse the data directory from environment variables and command line arguments, create it if necessary.
+ /// Set the current directory to said data folder and place a default configuration file if it does not already
+ /// exists.
+ ///
+ /// The command line arguments
+ public static void SetupDataDir(string[] args)
+ {
+ IConfiguration parsed = new ConfigurationBuilder()
+ .AddEnvironmentVariables()
+ .AddEnvironmentVariables("KYOO_")
+ .AddCommandLine(args)
+ .Build();
+
+ string path = parsed.GetValue("data_dir");
+ if (path == null)
+ path = Path.Combine(SEnvironment.GetFolderPath(SEnvironment.SpecialFolder.LocalApplicationData), "Kyoo");
+
+ if (!Directory.Exists(path))
+ Directory.CreateDirectory(path);
+ SEnvironment.CurrentDirectory = path;
+
+ if (!File.Exists(JsonConfigPath))
+ File.Copy(Path.Join(AppDomain.CurrentDomain.BaseDirectory, JsonConfigPath), JsonConfigPath);
+ }
+
///
/// An useless class only used to have a logger in the main.
///
diff --git a/deployment/kyoo-windows.iss b/deployment/kyoo-windows.iss
new file mode 100644
index 00000000..99d9d426
--- /dev/null
+++ b/deployment/kyoo-windows.iss
@@ -0,0 +1,39 @@
+[Setup]
+AppId={{31A61284-7939-46BC-B584-D2279A6EEEE8}
+AppName=Kyoo
+AppVersion=1.0
+AppPublisher=SDG
+AppPublisherURL=https://github.com/AnonymusRaccoon/Kyoo
+AppSupportURL=https://github.com/AnonymusRaccoon/Kyoo
+AppUpdatesURL=https://github.com/AnonymusRaccoon/Kyoo
+DefaultDirName={commonpf}\Kyoo
+DisableProgramGroupPage=yes
+LicenseFile={#kyoo}\LICENSE
+OutputBaseFilename=kyoo-windows
+SetupIconFile={#kyoo}\wwwroot\favicon.ico
+Compression=lzma
+SolidCompression=yes
+WizardStyle=modern
+AppCopyright=GPL-3.0
+
+[Languages]
+Name: "english"; MessagesFile: "compiler:Default.isl"
+
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive
+Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
+
+[Files]
+Source: "{#kyoo}\Kyoo.exe"; DestDir: "{app}"; Flags: ignoreversion
+Source: "{#kyoo}\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
+
+[UninstallDelete]
+Type: filesandordirs; Name: "{commonappdata}\Kyoo"
+
+[Icons]
+Name: "{autoprograms}\Kyoo"; Filename: "{app}\Kyoo.exe"
+Name: "{autodesktop}\Kyoo"; Filename: "{app}\Kyoo.exe"; Tasks: desktopicon
+
+[Run]
+Filename: "{app}\Kyoo.exe"; Description: "{cm:LaunchProgram,Kyoo}"; Flags: nowait postinstall skipifsilent
\ No newline at end of file