From db28cb612102fb01f297ab7cc9d5d82d1d1a3844 Mon Sep 17 00:00:00 2001 From: da3dsoul Date: Fri, 5 Jan 2024 23:28:07 -0500 Subject: [PATCH] Quartz has persistence now --- Shoko.Server/Scheduling/QuartzStartup.cs | 849 +++++++++++++++++++++++ Shoko.Server/Server/Startup.cs | 26 +- Shoko.Server/Server/StartupExtensions.cs | 20 - Shoko.Server/Settings/IServerSettings.cs | 1 + Shoko.Server/Settings/QuartzSettings.cs | 18 + Shoko.Server/Settings/ServerSettings.cs | 1 + Shoko.Server/Shoko.Server.csproj | 3 +- 7 files changed, 873 insertions(+), 45 deletions(-) create mode 100644 Shoko.Server/Scheduling/QuartzStartup.cs create mode 100644 Shoko.Server/Settings/QuartzSettings.cs diff --git a/Shoko.Server/Scheduling/QuartzStartup.cs b/Shoko.Server/Scheduling/QuartzStartup.cs new file mode 100644 index 000000000..254580ed9 --- /dev/null +++ b/Shoko.Server/Scheduling/QuartzStartup.cs @@ -0,0 +1,849 @@ +using System; +using System.Data.SqlClient; +using Microsoft.Data.Sqlite; +using Microsoft.Extensions.DependencyInjection; +using MySqlConnector; +using Quartz; +using Quartz.AspNetCore; +using QuartzJobFactory; +using Shoko.Server.Scheduling.Jobs; +using Shoko.Server.Server; +using Shoko.Server.Utilities; + +namespace Shoko.Server.Scheduling; + +public static class QuartzStartup +{ + internal static void AddQuartz(this IServiceCollection services) + { + services.AddQuartz(q => + { + q.UseDatabase(); + // as of 3.3.2 this also injects scoped services (like EF DbContext) without problems + q.UseMicrosoftDependencyInjectionJobFactory(); + + // use the database that we have selected for quartz + //q.UseDatabase(); + + // Register the connectivity monitor job with a trigger that executes every 5 minutes + q.ScheduleJob( + trigger => trigger.WithSimpleSchedule(tr => tr.WithIntervalInMinutes(5).RepeatForever()).StartNow(), + j => j.DisallowConcurrentExecution().WithGeneratedIdentity()); + + // TODO, in the future, when commands are Jobs, we'll use a AddCommands() extension like below for those, but manual registration for scheduled tasks like above + }); + + services.AddQuartzServer(options => + { + // when shutting down we want jobs to complete gracefully + options.WaitForJobsToComplete = true; + }); + } + + private static void UseDatabase(this IServiceCollectionQuartzConfigurator q) + { + q.UsePersistentStore(options => + { + var settings = Utils.SettingsProvider.GetSettings(); + if (settings.Quartz.DatabaseType.Trim().Equals(Constants.DatabaseType.SqlServer, StringComparison.InvariantCultureIgnoreCase)) + { + EnsureQuartzDatabaseExists_SQLServer(settings.Quartz.ConnectionString); + options.UseSqlServer(c => c.ConnectionString = settings.Quartz.ConnectionString); + } + else if (settings.Quartz.DatabaseType.Trim().Equals(Constants.DatabaseType.MySQL, StringComparison.InvariantCultureIgnoreCase)) + { + EnsureQuartzDatabaseExists_MySQL(settings.Quartz.ConnectionString); + options.UseMySqlConnector(c => c.ConnectionString = settings.Quartz.ConnectionString); + } + else if (settings.Quartz.DatabaseType.Trim().Equals(Constants.DatabaseType.Sqlite, StringComparison.InvariantCultureIgnoreCase)) + { + EnsureQuartzDatabaseExists_SQLite(settings.Quartz.ConnectionString); + options.UseMicrosoftSQLite(c => c.ConnectionString = settings.Quartz.ConnectionString); + } + options.UseNewtonsoftJsonSerializer(); + }); + } + + // https://github.com/quartznet/quartznet/tree/main/database/tables + private static void EnsureQuartzDatabaseExists_SQLServer(string connectionString) + { + using var conn = new SqlConnection(connectionString); + conn.Open(); + using var existsCommand = new SqlCommand("SELECT Count(1) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'QRTZ_TRIGGERS'", conn); + var result = (int)existsCommand.ExecuteScalar()!; + if (result >= 1) return; + #region SQL Server Script + const string Script = @"-- this script is for SQL Server and Azure SQL +IF OBJECT_ID(N'[dbo].[FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS]', N'F') IS NOT NULL +ALTER TABLE [dbo].[QRTZ_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS]; +GO + +IF OBJECT_ID(N'[dbo].[FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS]', N'F') IS NOT NULL +ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS]; +GO + +IF OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS]', N'F') IS NOT NULL +ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS]; +GO + +IF OBJECT_ID(N'[dbo].[FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS]', N'F') IS NOT NULL +ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] DROP CONSTRAINT [FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS]; +GO + +IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_QRTZ_JOB_LISTENERS_QRTZ_JOB_DETAILS]') AND parent_object_id = OBJECT_ID(N'[dbo].[QRTZ_JOB_LISTENERS]')) +ALTER TABLE [dbo].[QRTZ_JOB_LISTENERS] DROP CONSTRAINT [FK_QRTZ_JOB_LISTENERS_QRTZ_JOB_DETAILS]; + +IF EXISTS (SELECT * FROM sys.foreign_keys WHERE object_id = OBJECT_ID(N'[dbo].[FK_QRTZ_TRIGGER_LISTENERS_QRTZ_TRIGGERS]') AND parent_object_id = OBJECT_ID(N'[dbo].[QRTZ_TRIGGER_LISTENERS]')) +ALTER TABLE [dbo].[QRTZ_TRIGGER_LISTENERS] DROP CONSTRAINT [FK_QRTZ_TRIGGER_LISTENERS_QRTZ_TRIGGERS]; + + +IF OBJECT_ID(N'[dbo].[QRTZ_CALENDARS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_CALENDARS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_CRON_TRIGGERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_CRON_TRIGGERS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_BLOB_TRIGGERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_BLOB_TRIGGERS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_FIRED_TRIGGERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_FIRED_TRIGGERS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_PAUSED_TRIGGER_GRPS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_JOB_LISTENERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_JOB_LISTENERS]; + +IF OBJECT_ID(N'[dbo].[QRTZ_SCHEDULER_STATE]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_SCHEDULER_STATE]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_LOCKS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_LOCKS]; +GO +IF OBJECT_ID(N'[dbo].[QRTZ_TRIGGER_LISTENERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_TRIGGER_LISTENERS]; + + +IF OBJECT_ID(N'[dbo].[QRTZ_JOB_DETAILS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_JOB_DETAILS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_SIMPLE_TRIGGERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_SIMPROP_TRIGGERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS]; +GO + +IF OBJECT_ID(N'[dbo].[QRTZ_TRIGGERS]', N'U') IS NOT NULL +DROP TABLE [dbo].[QRTZ_TRIGGERS]; +GO + +CREATE TABLE [dbo].[QRTZ_CALENDARS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [CALENDAR_NAME] nvarchar(200) NOT NULL, + [CALENDAR] varbinary(max) NOT NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_CRON_TRIGGERS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [TRIGGER_NAME] nvarchar(150) NOT NULL, + [TRIGGER_GROUP] nvarchar(150) NOT NULL, + [CRON_EXPRESSION] nvarchar(120) NOT NULL, + [TIME_ZONE_ID] nvarchar(80) +); +GO + +CREATE TABLE [dbo].[QRTZ_FIRED_TRIGGERS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [ENTRY_ID] nvarchar(140) NOT NULL, + [TRIGGER_NAME] nvarchar(150) NOT NULL, + [TRIGGER_GROUP] nvarchar(150) NOT NULL, + [INSTANCE_NAME] nvarchar(200) NOT NULL, + [FIRED_TIME] bigint NOT NULL, + [SCHED_TIME] bigint NOT NULL, + [PRIORITY] int NOT NULL, + [STATE] nvarchar(16) NOT NULL, + [JOB_NAME] nvarchar(150) NULL, + [JOB_GROUP] nvarchar(150) NULL, + [IS_NONCONCURRENT] bit NULL, + [REQUESTS_RECOVERY] bit NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [TRIGGER_GROUP] nvarchar(150) NOT NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_SCHEDULER_STATE] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [INSTANCE_NAME] nvarchar(200) NOT NULL, + [LAST_CHECKIN_TIME] bigint NOT NULL, + [CHECKIN_INTERVAL] bigint NOT NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_LOCKS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [LOCK_NAME] nvarchar(40) NOT NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_JOB_DETAILS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [JOB_NAME] nvarchar(150) NOT NULL, + [JOB_GROUP] nvarchar(150) NOT NULL, + [DESCRIPTION] nvarchar(250) NULL, + [JOB_CLASS_NAME] nvarchar(250) NOT NULL, + [IS_DURABLE] bit NOT NULL, + [IS_NONCONCURRENT] bit NOT NULL, + [IS_UPDATE_DATA] bit NOT NULL, + [REQUESTS_RECOVERY] bit NOT NULL, + [JOB_DATA] varbinary(max) NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [TRIGGER_NAME] nvarchar(150) NOT NULL, + [TRIGGER_GROUP] nvarchar(150) NOT NULL, + [REPEAT_COUNT] int NOT NULL, + [REPEAT_INTERVAL] bigint NOT NULL, + [TIMES_TRIGGERED] int NOT NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [TRIGGER_NAME] nvarchar(150) NOT NULL, + [TRIGGER_GROUP] nvarchar(150) NOT NULL, + [STR_PROP_1] nvarchar(512) NULL, + [STR_PROP_2] nvarchar(512) NULL, + [STR_PROP_3] nvarchar(512) NULL, + [INT_PROP_1] int NULL, + [INT_PROP_2] int NULL, + [LONG_PROP_1] bigint NULL, + [LONG_PROP_2] bigint NULL, + [DEC_PROP_1] numeric(13,4) NULL, + [DEC_PROP_2] numeric(13,4) NULL, + [BOOL_PROP_1] bit NULL, + [BOOL_PROP_2] bit NULL, + [TIME_ZONE_ID] nvarchar(80) NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_BLOB_TRIGGERS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [TRIGGER_NAME] nvarchar(150) NOT NULL, + [TRIGGER_GROUP] nvarchar(150) NOT NULL, + [BLOB_DATA] varbinary(max) NULL +); +GO + +CREATE TABLE [dbo].[QRTZ_TRIGGERS] ( + [SCHED_NAME] nvarchar(120) NOT NULL, + [TRIGGER_NAME] nvarchar(150) NOT NULL, + [TRIGGER_GROUP] nvarchar(150) NOT NULL, + [JOB_NAME] nvarchar(150) NOT NULL, + [JOB_GROUP] nvarchar(150) NOT NULL, + [DESCRIPTION] nvarchar(250) NULL, + [NEXT_FIRE_TIME] bigint NULL, + [PREV_FIRE_TIME] bigint NULL, + [PRIORITY] int NULL, + [TRIGGER_STATE] nvarchar(16) NOT NULL, + [TRIGGER_TYPE] nvarchar(8) NOT NULL, + [START_TIME] bigint NOT NULL, + [END_TIME] bigint NULL, + [CALENDAR_NAME] nvarchar(200) NULL, + [MISFIRE_INSTR] int NULL, + [JOB_DATA] varbinary(max) NULL +); +GO + +ALTER TABLE [dbo].[QRTZ_CALENDARS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_CALENDARS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [CALENDAR_NAME] + ); +GO + +ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_CRON_TRIGGERS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ); +GO + +ALTER TABLE [dbo].[QRTZ_FIRED_TRIGGERS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_FIRED_TRIGGERS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [ENTRY_ID] + ); +GO + +ALTER TABLE [dbo].[QRTZ_PAUSED_TRIGGER_GRPS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_PAUSED_TRIGGER_GRPS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [TRIGGER_GROUP] + ); +GO + +ALTER TABLE [dbo].[QRTZ_SCHEDULER_STATE] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_SCHEDULER_STATE] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [INSTANCE_NAME] + ); +GO + +ALTER TABLE [dbo].[QRTZ_LOCKS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_LOCKS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [LOCK_NAME] + ); +GO + +ALTER TABLE [dbo].[QRTZ_JOB_DETAILS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_JOB_DETAILS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [JOB_NAME], + [JOB_GROUP] + ); +GO + +ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_SIMPLE_TRIGGERS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ); +GO + +ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_SIMPROP_TRIGGERS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ); +GO + +ALTER TABLE [dbo].[QRTZ_TRIGGERS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_TRIGGERS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ); +GO + +ALTER TABLE [dbo].[QRTZ_BLOB_TRIGGERS] WITH NOCHECK ADD + CONSTRAINT [PK_QRTZ_BLOB_TRIGGERS] PRIMARY KEY CLUSTERED + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ); +GO + +ALTER TABLE [dbo].[QRTZ_CRON_TRIGGERS] ADD + CONSTRAINT [FK_QRTZ_CRON_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ) REFERENCES [dbo].[QRTZ_TRIGGERS] ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ) ON DELETE CASCADE; +GO + +ALTER TABLE [dbo].[QRTZ_SIMPLE_TRIGGERS] ADD + CONSTRAINT [FK_QRTZ_SIMPLE_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ) REFERENCES [dbo].[QRTZ_TRIGGERS] ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ) ON DELETE CASCADE; +GO + +ALTER TABLE [dbo].[QRTZ_SIMPROP_TRIGGERS] ADD + CONSTRAINT [FK_QRTZ_SIMPROP_TRIGGERS_QRTZ_TRIGGERS] FOREIGN KEY + ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ) REFERENCES [dbo].[QRTZ_TRIGGERS] ( + [SCHED_NAME], + [TRIGGER_NAME], + [TRIGGER_GROUP] + ) ON DELETE CASCADE; +GO + +ALTER TABLE [dbo].[QRTZ_TRIGGERS] ADD + CONSTRAINT [FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS] FOREIGN KEY + ( + [SCHED_NAME], + [JOB_NAME], + [JOB_GROUP] + ) REFERENCES [dbo].[QRTZ_JOB_DETAILS] ( + [SCHED_NAME], + [JOB_NAME], + [JOB_GROUP] + ); +GO + +-- drop indexe if they exist and rebuild if current ones +DROP INDEX IF EXISTS [IDX_QRTZ_T_J] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_JG] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_C] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_G] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_G_J] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_STATE] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_N_STATE] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_N_G_STATE] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_NEXT_FIRE_TIME] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_ST] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_MISFIRE] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_ST_MISFIRE] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_T_NFT_ST_MISFIRE_GRP] ON [dbo].[QRTZ_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_TRIG_INST_NAME] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_INST_JOB_REQ_RCVRY] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_J_G] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_JG] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_T_G] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_TG] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_G_J] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +DROP INDEX IF EXISTS [IDX_QRTZ_FT_G_T] ON [dbo].[QRTZ_FIRED_TRIGGERS]; +GO + + +CREATE INDEX [IDX_QRTZ_T_G_J] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, JOB_GROUP, JOB_NAME); +CREATE INDEX [IDX_QRTZ_T_C] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, CALENDAR_NAME); + +CREATE INDEX [IDX_QRTZ_T_N_G_STATE] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX [IDX_QRTZ_T_STATE] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, TRIGGER_STATE); +CREATE INDEX [IDX_QRTZ_T_N_STATE] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, TRIGGER_NAME, TRIGGER_GROUP, TRIGGER_STATE); +CREATE INDEX [IDX_QRTZ_T_NEXT_FIRE_TIME] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, NEXT_FIRE_TIME); +CREATE INDEX [IDX_QRTZ_T_NFT_ST] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, TRIGGER_STATE, NEXT_FIRE_TIME); +CREATE INDEX [IDX_QRTZ_T_NFT_ST_MISFIRE] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_STATE); +CREATE INDEX [IDX_QRTZ_T_NFT_ST_MISFIRE_GRP] ON [dbo].[QRTZ_TRIGGERS](SCHED_NAME, MISFIRE_INSTR, NEXT_FIRE_TIME, TRIGGER_GROUP, TRIGGER_STATE); + +CREATE INDEX [IDX_QRTZ_FT_INST_JOB_REQ_RCVRY] ON [dbo].[QRTZ_FIRED_TRIGGERS](SCHED_NAME, INSTANCE_NAME, REQUESTS_RECOVERY); +CREATE INDEX [IDX_QRTZ_FT_G_J] ON [dbo].[QRTZ_FIRED_TRIGGERS](SCHED_NAME, JOB_GROUP, JOB_NAME); +CREATE INDEX [IDX_QRTZ_FT_G_T] ON [dbo].[QRTZ_FIRED_TRIGGERS](SCHED_NAME, TRIGGER_GROUP, TRIGGER_NAME); +GO"; + #endregion + using var command = new SqlCommand(Script, conn); + command.CommandTimeout = 0; + command.ExecuteNonQuery(); + conn.Close(); + } + + private static void EnsureQuartzDatabaseExists_MySQL(string connectionString) + { + using var conn = new MySqlConnection(connectionString); + conn.Open(); + using var existsCommand = new MySqlCommand("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = 'QRTZ_TRIGGERS'", conn); + var result = (long)existsCommand.ExecuteScalar()!; + if (result >= 1) return; + #region MySQL Script + const string Script = @" +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +CREATE TABLE QRTZ_JOB_DETAILS( +SCHED_NAME VARCHAR(120) NOT NULL, +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +JOB_CLASS_NAME VARCHAR(250) NOT NULL, +IS_DURABLE BOOLEAN NOT NULL, +IS_NONCONCURRENT BOOLEAN NOT NULL, +IS_UPDATE_DATA BOOLEAN NOT NULL, +REQUESTS_RECOVERY BOOLEAN NOT NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +JOB_NAME VARCHAR(200) NOT NULL, +JOB_GROUP VARCHAR(200) NOT NULL, +DESCRIPTION VARCHAR(250) NULL, +NEXT_FIRE_TIME BIGINT(19) NULL, +PREV_FIRE_TIME BIGINT(19) NULL, +PRIORITY INTEGER NULL, +TRIGGER_STATE VARCHAR(16) NOT NULL, +TRIGGER_TYPE VARCHAR(8) NOT NULL, +START_TIME BIGINT(19) NOT NULL, +END_TIME BIGINT(19) NULL, +CALENDAR_NAME VARCHAR(200) NULL, +MISFIRE_INSTR SMALLINT(2) NULL, +JOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) +REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +REPEAT_COUNT BIGINT(7) NOT NULL, +REPEAT_INTERVAL BIGINT(12) NOT NULL, +TIMES_TRIGGERED BIGINT(10) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_CRON_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +CRON_EXPRESSION VARCHAR(120) NOT NULL, +TIME_ZONE_ID VARCHAR(80), +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS + ( + SCHED_NAME VARCHAR(120) NOT NULL, + TRIGGER_NAME VARCHAR(200) NOT NULL, + TRIGGER_GROUP VARCHAR(200) NOT NULL, + STR_PROP_1 VARCHAR(512) NULL, + STR_PROP_2 VARCHAR(512) NULL, + STR_PROP_3 VARCHAR(512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC(13,4) NULL, + DEC_PROP_2 NUMERIC(13,4) NULL, + BOOL_PROP_1 BOOLEAN NULL, + BOOL_PROP_2 BOOLEAN NULL, + TIME_ZONE_ID VARCHAR(80) NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_BLOB_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +BLOB_DATA BLOB NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), +INDEX (SCHED_NAME,TRIGGER_NAME, TRIGGER_GROUP), +FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) +REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_CALENDARS ( +SCHED_NAME VARCHAR(120) NOT NULL, +CALENDAR_NAME VARCHAR(200) NOT NULL, +CALENDAR BLOB NOT NULL, +PRIMARY KEY (SCHED_NAME,CALENDAR_NAME)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS ( +SCHED_NAME VARCHAR(120) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_FIRED_TRIGGERS ( +SCHED_NAME VARCHAR(120) NOT NULL, +ENTRY_ID VARCHAR(140) NOT NULL, +TRIGGER_NAME VARCHAR(200) NOT NULL, +TRIGGER_GROUP VARCHAR(200) NOT NULL, +INSTANCE_NAME VARCHAR(200) NOT NULL, +FIRED_TIME BIGINT(19) NOT NULL, +SCHED_TIME BIGINT(19) NOT NULL, +PRIORITY INTEGER NOT NULL, +STATE VARCHAR(16) NOT NULL, +JOB_NAME VARCHAR(200) NULL, +JOB_GROUP VARCHAR(200) NULL, +IS_NONCONCURRENT BOOLEAN NULL, +REQUESTS_RECOVERY BOOLEAN NULL, +PRIMARY KEY (SCHED_NAME,ENTRY_ID)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_SCHEDULER_STATE ( +SCHED_NAME VARCHAR(120) NOT NULL, +INSTANCE_NAME VARCHAR(200) NOT NULL, +LAST_CHECKIN_TIME BIGINT(19) NOT NULL, +CHECKIN_INTERVAL BIGINT(19) NOT NULL, +PRIMARY KEY (SCHED_NAME,INSTANCE_NAME)) +ENGINE=InnoDB; + +CREATE TABLE QRTZ_LOCKS ( +SCHED_NAME VARCHAR(120) NOT NULL, +LOCK_NAME VARCHAR(40) NOT NULL, +PRIMARY KEY (SCHED_NAME,LOCK_NAME)) +ENGINE=InnoDB; + +CREATE INDEX IDX_QRTZ_J_REQ_RECOVERY ON QRTZ_JOB_DETAILS(SCHED_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_J_GRP ON QRTZ_JOB_DETAILS(SCHED_NAME,JOB_GROUP); + +CREATE INDEX IDX_QRTZ_T_J ON QRTZ_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_JG ON QRTZ_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_T_C ON QRTZ_TRIGGERS(SCHED_NAME,CALENDAR_NAME); +CREATE INDEX IDX_QRTZ_T_G ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_T_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_N_G_STATE ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_GROUP,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NEXT_FIRE_TIME ON QRTZ_TRIGGERS(SCHED_NAME,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST ON QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_STATE,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_STATE); +CREATE INDEX IDX_QRTZ_T_NFT_ST_MISFIRE_GRP ON QRTZ_TRIGGERS(SCHED_NAME,MISFIRE_INSTR,NEXT_FIRE_TIME,TRIGGER_GROUP,TRIGGER_STATE); + +CREATE INDEX IDX_QRTZ_FT_TRIG_INST_NAME ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME); +CREATE INDEX IDX_QRTZ_FT_INST_JOB_REQ_RCVRY ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,INSTANCE_NAME,REQUESTS_RECOVERY); +CREATE INDEX IDX_QRTZ_FT_J_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_JG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,JOB_GROUP); +CREATE INDEX IDX_QRTZ_FT_T_G ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP); +CREATE INDEX IDX_QRTZ_FT_TG ON QRTZ_FIRED_TRIGGERS(SCHED_NAME,TRIGGER_GROUP); + +commit;"; + #endregion + using var command = new MySqlCommand(Script, conn); + command.CommandTimeout = 0; + command.ExecuteNonQuery(); + conn.Close(); + } + + private static void EnsureQuartzDatabaseExists_SQLite(string connectionString) + { + using var conn = new SqliteConnection(connectionString); + conn.Open(); + var existsCommand = new SqliteCommand("SELECT COUNT(name) FROM sqlite_master WHERE type='table' AND name='QRTZ_TRIGGERS'", conn); + var result = (long)existsCommand.ExecuteScalar()!; + if (result >= 1) return; + #region SQLite Script + const string Script = @"DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + + +CREATE TABLE QRTZ_JOB_DETAILS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + JOB_NAME NVARCHAR(150) NOT NULL, + JOB_GROUP NVARCHAR(150) NOT NULL, + DESCRIPTION NVARCHAR(250) NULL, + JOB_CLASS_NAME NVARCHAR(250) NOT NULL, + IS_DURABLE BIT NOT NULL, + IS_NONCONCURRENT BIT NOT NULL, + IS_UPDATE_DATA BIT NOT NULL, + REQUESTS_RECOVERY BIT NOT NULL, + JOB_DATA BLOB NULL, + PRIMARY KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) +); + +CREATE TABLE QRTZ_TRIGGERS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + TRIGGER_NAME NVARCHAR(150) NOT NULL, + TRIGGER_GROUP NVARCHAR(150) NOT NULL, + JOB_NAME NVARCHAR(150) NOT NULL, + JOB_GROUP NVARCHAR(150) NOT NULL, + DESCRIPTION NVARCHAR(250) NULL, + NEXT_FIRE_TIME BIGINT NULL, + PREV_FIRE_TIME BIGINT NULL, + PRIORITY INTEGER NULL, + TRIGGER_STATE NVARCHAR(16) NOT NULL, + TRIGGER_TYPE NVARCHAR(8) NOT NULL, + START_TIME BIGINT NOT NULL, + END_TIME BIGINT NULL, + CALENDAR_NAME NVARCHAR(200) NULL, + MISFIRE_INSTR INTEGER NULL, + JOB_DATA BLOB NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,JOB_NAME,JOB_GROUP) + REFERENCES QRTZ_JOB_DETAILS(SCHED_NAME,JOB_NAME,JOB_GROUP) +); + +CREATE TABLE QRTZ_SIMPLE_TRIGGERS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + TRIGGER_NAME NVARCHAR(150) NOT NULL, + TRIGGER_GROUP NVARCHAR(150) NOT NULL, + REPEAT_COUNT BIGINT NOT NULL, + REPEAT_INTERVAL BIGINT NOT NULL, + TIMES_TRIGGERED BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ON DELETE CASCADE +); + +CREATE TRIGGER DELETE_SIMPLE_TRIGGER DELETE ON QRTZ_TRIGGERS +BEGIN + DELETE FROM QRTZ_SIMPLE_TRIGGERS WHERE SCHED_NAME=OLD.SCHED_NAME AND TRIGGER_NAME=OLD.TRIGGER_NAME AND TRIGGER_GROUP=OLD.TRIGGER_GROUP; +END +; + +CREATE TABLE QRTZ_SIMPROP_TRIGGERS + ( + SCHED_NAME NVARCHAR (120) NOT NULL , + TRIGGER_NAME NVARCHAR (150) NOT NULL , + TRIGGER_GROUP NVARCHAR (150) NOT NULL , + STR_PROP_1 NVARCHAR (512) NULL, + STR_PROP_2 NVARCHAR (512) NULL, + STR_PROP_3 NVARCHAR (512) NULL, + INT_PROP_1 INT NULL, + INT_PROP_2 INT NULL, + LONG_PROP_1 BIGINT NULL, + LONG_PROP_2 BIGINT NULL, + DEC_PROP_1 NUMERIC NULL, + DEC_PROP_2 NUMERIC NULL, + BOOL_PROP_1 BIT NULL, + BOOL_PROP_2 BIT NULL, + TIME_ZONE_ID NVARCHAR(80) NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ON DELETE CASCADE +); + +CREATE TRIGGER DELETE_SIMPROP_TRIGGER DELETE ON QRTZ_TRIGGERS +BEGIN + DELETE FROM QRTZ_SIMPROP_TRIGGERS WHERE SCHED_NAME=OLD.SCHED_NAME AND TRIGGER_NAME=OLD.TRIGGER_NAME AND TRIGGER_GROUP=OLD.TRIGGER_GROUP; +END +; + +CREATE TABLE QRTZ_CRON_TRIGGERS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + TRIGGER_NAME NVARCHAR(150) NOT NULL, + TRIGGER_GROUP NVARCHAR(150) NOT NULL, + CRON_EXPRESSION NVARCHAR(250) NOT NULL, + TIME_ZONE_ID NVARCHAR(80), + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ON DELETE CASCADE +); + +CREATE TRIGGER DELETE_CRON_TRIGGER DELETE ON QRTZ_TRIGGERS +BEGIN + DELETE FROM QRTZ_CRON_TRIGGERS WHERE SCHED_NAME=OLD.SCHED_NAME AND TRIGGER_NAME=OLD.TRIGGER_NAME AND TRIGGER_GROUP=OLD.TRIGGER_GROUP; +END +; + +CREATE TABLE QRTZ_BLOB_TRIGGERS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + TRIGGER_NAME NVARCHAR(150) NOT NULL, + TRIGGER_GROUP NVARCHAR(150) NOT NULL, + BLOB_DATA BLOB NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP), + FOREIGN KEY (SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) + REFERENCES QRTZ_TRIGGERS(SCHED_NAME,TRIGGER_NAME,TRIGGER_GROUP) ON DELETE CASCADE +); + +CREATE TRIGGER DELETE_BLOB_TRIGGER DELETE ON QRTZ_TRIGGERS +BEGIN + DELETE FROM QRTZ_BLOB_TRIGGERS WHERE SCHED_NAME=OLD.SCHED_NAME AND TRIGGER_NAME=OLD.TRIGGER_NAME AND TRIGGER_GROUP=OLD.TRIGGER_GROUP; +END +; + +CREATE TABLE QRTZ_CALENDARS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + CALENDAR_NAME NVARCHAR(200) NOT NULL, + CALENDAR BLOB NOT NULL, + PRIMARY KEY (SCHED_NAME,CALENDAR_NAME) +); + +CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + TRIGGER_GROUP NVARCHAR(150) NOT NULL, + PRIMARY KEY (SCHED_NAME,TRIGGER_GROUP) +); + +CREATE TABLE QRTZ_FIRED_TRIGGERS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + ENTRY_ID NVARCHAR(140) NOT NULL, + TRIGGER_NAME NVARCHAR(150) NOT NULL, + TRIGGER_GROUP NVARCHAR(150) NOT NULL, + INSTANCE_NAME NVARCHAR(200) NOT NULL, + FIRED_TIME BIGINT NOT NULL, + SCHED_TIME BIGINT NOT NULL, + PRIORITY INTEGER NOT NULL, + STATE NVARCHAR(16) NOT NULL, + JOB_NAME NVARCHAR(150) NULL, + JOB_GROUP NVARCHAR(150) NULL, + IS_NONCONCURRENT BIT NULL, + REQUESTS_RECOVERY BIT NULL, + PRIMARY KEY (SCHED_NAME,ENTRY_ID) +); + +CREATE TABLE QRTZ_SCHEDULER_STATE + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + INSTANCE_NAME NVARCHAR(200) NOT NULL, + LAST_CHECKIN_TIME BIGINT NOT NULL, + CHECKIN_INTERVAL BIGINT NOT NULL, + PRIMARY KEY (SCHED_NAME,INSTANCE_NAME) +); + +CREATE TABLE QRTZ_LOCKS + ( + SCHED_NAME NVARCHAR(120) NOT NULL, + LOCK_NAME NVARCHAR(40) NOT NULL, + PRIMARY KEY (SCHED_NAME,LOCK_NAME) +);"; + #endregion + var cmd = new SqliteCommand(Script, conn); + cmd.ExecuteNonQuery(); + conn.Close(); + } +} diff --git a/Shoko.Server/Server/Startup.cs b/Shoko.Server/Server/Startup.cs index 4b09a6c38..8b008f70f 100644 --- a/Shoko.Server/Server/Startup.cs +++ b/Shoko.Server/Server/Startup.cs @@ -5,9 +5,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using NLog.Web; -using Quartz; -using Quartz.AspNetCore; -using QuartzJobFactory; using Shoko.Commons.Properties; using Shoko.Plugin.Abstractions; using Shoko.Plugin.Abstractions.Services; @@ -20,6 +17,7 @@ using Shoko.Server.Providers.MovieDB; using Shoko.Server.Providers.TraktTV; using Shoko.Server.Providers.TvDB; +using Shoko.Server.Scheduling; using Shoko.Server.Scheduling.Jobs; using Shoko.Server.Services.Connectivity; using Shoko.Server.Settings; @@ -69,27 +67,7 @@ public void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); - services.AddQuartz(q => - { - // as of 3.3.2 this also injects scoped services (like EF DbContext) without problems - q.UseMicrosoftDependencyInjectionJobFactory(); - - // use the database that we have selected for quartz - //q.UseDatabase(); - - // Register the connectivity monitor job with a trigger that executes every 5 minutes - q.ScheduleJob( - trigger => trigger.WithSimpleSchedule(tr => tr.WithIntervalInMinutes(5).RepeatForever()).StartNow(), - j => j.DisallowConcurrentExecution().WithGeneratedIdentity()); - - // TODO, in the future, when commands are Jobs, we'll use a AddCommands() extension like below for those, but manual registration for scheduled tasks like above - }); - - services.AddQuartzServer(options => - { - // when shutting down we want jobs to complete gracefully - options.WaitForJobsToComplete = true; - }); + services.AddQuartz(); services.AddAniDB(); services.AddCommands(); diff --git a/Shoko.Server/Server/StartupExtensions.cs b/Shoko.Server/Server/StartupExtensions.cs index 52bc48c4c..561516ba6 100644 --- a/Shoko.Server/Server/StartupExtensions.cs +++ b/Shoko.Server/Server/StartupExtensions.cs @@ -4,9 +4,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Quartz; -using Shoko.Server.Databases; -using Shoko.Server.Utilities; namespace Shoko.Server.Server; @@ -58,21 +55,4 @@ private static void ConfigureDefaultServiceProvider(WebHostBuilderContext contex options.ValidateScopes = isDevelopment; options.ValidateOnBuild = isDevelopment; } - - internal static void UseDatabase(this IServiceCollectionQuartzConfigurator q) - { - q.UsePersistentStore(options => - { - var settings = Utils.SettingsProvider.GetSettings(); - // TODO Make these their own settings. SQLite should support putting Quartz in its own db. Will help with locking - // TODO, take the scripts from here and execute the SQL Scripts if the Quartz tables don't exist - // https://github.com/quartznet/quartznet/tree/main/database/tables - if (settings.Database.Type.Trim().Equals(Constants.DatabaseType.SqlServer, StringComparison.InvariantCultureIgnoreCase)) - options.UseSqlServer(DatabaseFactory.Instance.GetConnectionString()); - else if (settings.Database.Type.Trim().Equals(Constants.DatabaseType.MySQL, StringComparison.InvariantCultureIgnoreCase)) - options.UseMySqlConnector(c => c.ConnectionString = DatabaseFactory.Instance.GetConnectionString()); - else if (settings.Database.Type.Trim().Equals(Constants.DatabaseType.Sqlite, StringComparison.InvariantCultureIgnoreCase)) - options.UseMicrosoftSQLite(DatabaseFactory.Instance.GetConnectionString()); - }); - } } diff --git a/Shoko.Server/Settings/IServerSettings.cs b/Shoko.Server/Settings/IServerSettings.cs index 7d0361c82..95817dda0 100644 --- a/Shoko.Server/Settings/IServerSettings.cs +++ b/Shoko.Server/Settings/IServerSettings.cs @@ -14,6 +14,7 @@ public interface IServerSettings int LegacyRenamerMaxEpisodeLength { get; set; } LogRotatorSettings LogRotator { get; set; } DatabaseSettings Database { get; set; } + QuartzSettings Quartz { get; set; } AniDbSettings AniDb { get; set; } WebCacheSettings WebCache { get; set; } TvDBSettings TvDB { get; set; } diff --git a/Shoko.Server/Settings/QuartzSettings.cs b/Shoko.Server/Settings/QuartzSettings.cs new file mode 100644 index 000000000..bb5b10af5 --- /dev/null +++ b/Shoko.Server/Settings/QuartzSettings.cs @@ -0,0 +1,18 @@ +using System.IO; +using Shoko.Server.Server; +using Shoko.Server.Utilities; + +namespace Shoko.Server.Settings; + +public class QuartzSettings +{ + /// + /// Use + /// + public string DatabaseType { get; set; } = Constants.DatabaseType.Sqlite; + + /// + /// The connection string for the database + /// + public string ConnectionString { get; set; } = $"Data Source={Path.Combine(Utils.ApplicationPath, "SQLite", "Quartz.db3")};Mode=ReadWriteCreate;"; +} diff --git a/Shoko.Server/Settings/ServerSettings.cs b/Shoko.Server/Settings/ServerSettings.cs index 82361959c..d11c89322 100644 --- a/Shoko.Server/Settings/ServerSettings.cs +++ b/Shoko.Server/Settings/ServerSettings.cs @@ -40,6 +40,7 @@ public class ServerSettings : IServerSettings public LogRotatorSettings LogRotator { get; set; } = new(); public DatabaseSettings Database { get; set; } = new(); + public QuartzSettings Quartz { get; set; } = new(); public AniDbSettings AniDb { get; set; } = new(); diff --git a/Shoko.Server/Shoko.Server.csproj b/Shoko.Server/Shoko.Server.csproj index c7a1ff1e2..141d3c771 100644 --- a/Shoko.Server/Shoko.Server.csproj +++ b/Shoko.Server/Shoko.Server.csproj @@ -91,7 +91,8 @@ - + +