diff --git a/src/Jackett.Server/Initialisation.cs b/src/Jackett.Server/Initialisation.cs new file mode 100644 index 000000000..9c1aa4e9f --- /dev/null +++ b/src/Jackett.Server/Initialisation.cs @@ -0,0 +1,154 @@ +using Autofac; +using AutoMapper; +using Jackett.Common; +using Jackett.Common.Models; +using Jackett.Common.Models.Config; +using Jackett.Common.Services; +using Jackett.Common.Services.Interfaces; +using Jackett.Common.Utils.Clients; +using NLog; +using NLog.Config; +using NLog.Targets; +using System.IO; +using System.Linq; +using System.Text; + +namespace Jackett.Server +{ + public class Initialisation + { + public static IContainer ApplicationContainer { get; set; } + + private static bool _automapperInitialised = false; + + public static void Initialize() + { + if (_automapperInitialised == false) + { + //Automapper only likes being initialized once per app domain. + //Since we can restart Jackett from the command line it's possible that we'll build the container more than once. (tests do this too) + InitAutomapper(); + _automapperInitialised = true; + } + + //Load the indexers + ServerService.Initalize(); + } + + private static void InitAutomapper() + { + Mapper.Initialize(cfg => + { + cfg.CreateMap().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((be, str) => + { + var encoding = be.Request.Encoding ?? Encoding.UTF8; + str.Content = encoding.GetString(be.Content); + }); + + cfg.CreateMap().ForMember(x => x.Content, opt => opt.Ignore()).AfterMap((str, be) => + { + if (!string.IsNullOrEmpty(str.Content)) + { + var encoding = str.Request.Encoding ?? Encoding.UTF8; + be.Content = encoding.GetBytes(str.Content); + } + }); + + cfg.CreateMap(); + cfg.CreateMap(); + cfg.CreateMap(); + + cfg.CreateMap().AfterMap((r, t) => + { + if (r.Category != null) + { + var CategoryDesc = string.Join(", ", r.Category.Select(x => TorznabCatType.GetCatDesc(x)).Where(x => !string.IsNullOrEmpty(x))); + t.CategoryDesc = CategoryDesc; + } + else + { + t.CategoryDesc = ""; + } + }); + }); + } + + public static IConfigurationService ConfigService + { + get + { + return ApplicationContainer.Resolve(); + } + } + + public static IServerService ServerService + { + get + { + return ApplicationContainer.Resolve(); + } + } + + public static void SetupLogging(RuntimeSettings settings, ContainerBuilder builder) + { + var logFileName = settings.CustomLogFileName ?? "log.txt"; + var logLevel = settings.TracingEnabled ? NLog.LogLevel.Debug : NLog.LogLevel.Info; + // Add custom date time format renderer as the default is too long + ConfigurationItemFactory.Default.LayoutRenderers.RegisterDefinition("simpledatetime", typeof(SimpleDateTimeRenderer)); + + var logConfig = new LoggingConfiguration(); + var logFile = new FileTarget(); + logConfig.AddTarget("file", logFile); + logFile.Layout = "${longdate} ${level} ${message} ${exception:format=ToString}"; + logFile.FileName = Path.Combine(settings.DataFolder, logFileName); + logFile.ArchiveFileName = "log.{#####}.txt"; + logFile.ArchiveAboveSize = 500000; + logFile.MaxArchiveFiles = 5; + logFile.KeepFileOpen = false; + logFile.ArchiveNumbering = ArchiveNumberingMode.DateAndSequence; + var logFileRule = new LoggingRule("*", logLevel, logFile); + logConfig.LoggingRules.Add(logFileRule); + + var logConsole = new ColoredConsoleTarget(); + logConfig.AddTarget("console", logConsole); + + logConsole.Layout = "${simpledatetime} ${level} ${message} ${exception:format=ToString}"; + var logConsoleRule = new LoggingRule("*", logLevel, logConsole); + logConfig.LoggingRules.Add(logConsoleRule); + + var logService = new LogCacheService(); + logConfig.AddTarget("service", logService); + var serviceRule = new LoggingRule("*", logLevel, logService); + logConfig.LoggingRules.Add(serviceRule); + + LogManager.Configuration = logConfig; + if (builder != null) + { + builder.RegisterInstance(LogManager.GetCurrentClassLogger()).SingleInstance(); + } + } + + public static void SetLogLevel(LogLevel level) + { + foreach (var rule in LogManager.Configuration.LoggingRules) + { + if (level == LogLevel.Debug) + { + if (!rule.Levels.Contains(LogLevel.Debug)) + { + rule.EnableLoggingForLevel(LogLevel.Debug); + } + } + else + { + if (rule.Levels.Contains(LogLevel.Debug)) + { + rule.DisableLoggingForLevel(LogLevel.Debug); + } + } + } + + LogManager.ReconfigExistingLoggers(); + } + } +} diff --git a/src/Jackett.Server/Program.cs b/src/Jackett.Server/Program.cs index adb09791b..929e70ddb 100644 --- a/src/Jackett.Server/Program.cs +++ b/src/Jackett.Server/Program.cs @@ -1,24 +1,60 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; +using CommandLine; +using CommandLine.Text; +using Jackett.Common.Models.Config; +using Jackett.Common.Utils; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; namespace Jackett.Server { public class Program { + public static IConfiguration Configuration { get; set; } + public static void Main(string[] args) { - BuildWebHost(args).Run(); + var optionsResult = Parser.Default.ParseArguments(args); + optionsResult.WithNotParsed(errors => + { + var text = HelpText.AutoBuild(optionsResult); + text.Copyright = " "; + text.Heading = "Jackett v" + EnvironmentUtil.JackettVersion + " options:"; + Console.WriteLine(text); + Environment.ExitCode = 1; + return; + }); + + var runtimeDictionary = new Dictionary(); + + optionsResult.WithParsed(options => + { + RuntimeSettings r = options.ToRunTimeSettings(); + runtimeDictionary = GetValues(r); + }); + + var builder = new ConfigurationBuilder(); + builder.AddInMemoryCollection(runtimeDictionary); + + Configuration = builder.Build(); + + BuildWebHost().Run(); } - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) + public static Dictionary GetValues(object obj) + { + return obj + .GetType() + .GetProperties() + .ToDictionary(p => "RuntimeSettings:" + p.Name, p => p.GetValue(obj) == null ? null : p.GetValue(obj).ToString()); + } + + public static IWebHost BuildWebHost() => + WebHost.CreateDefaultBuilder() + .UseConfiguration(Configuration) .UseStartup() .Build(); } diff --git a/src/Jackett.Server/Startup.cs b/src/Jackett.Server/Startup.cs index cbe0c69de..14bb4823b 100644 --- a/src/Jackett.Server/Startup.cs +++ b/src/Jackett.Server/Startup.cs @@ -1,13 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Jackett.Common.Models.Config; +using Jackett.Common.Plumbing; +using Jackett.Common.Services.Interfaces; +using Jackett.Server.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; +using Microsoft.Extensions.FileProviders; +using Newtonsoft.Json.Serialization; +using System; +using System.Text; namespace Jackett.Server { @@ -21,18 +25,47 @@ namespace Jackett.Server public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + public IServiceProvider ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services + .AddMvc() + .AddJsonOptions(options => options.SerializerSettings.ContractResolver = new DefaultContractResolver()); //Web app uses Pascal Case JSON + + RuntimeSettings runtimeSettings = new RuntimeSettings(); + Configuration.GetSection("RuntimeSettings").Bind(runtimeSettings); + + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + var builder = new ContainerBuilder(); + + Initialisation.SetupLogging(runtimeSettings, builder); + + builder.Populate(services); + builder.RegisterModule(new JackettModule(runtimeSettings)); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + + IContainer container = builder.Build(); + Initialisation.ApplicationContainer = container; + + return new AutofacServiceProvider(container); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { - if (env.IsDevelopment()) + Initialisation.Initialize(); + + app.UseDeveloperExceptionPage(); + + app.UseFileServer(new FileServerOptions { - app.UseDeveloperExceptionPage(); - } + FileProvider = new PhysicalFileProvider(Initialisation.ConfigService.GetContentFolder()), + RequestPath = "", + EnableDefaultFiles = true, + EnableDirectoryBrowsing = false + }); app.UseMvc(); }