diff --git a/QuartzNetWebConsole.sln b/QuartzNetWebConsole.sln index 7728612..49dce25 100644 --- a/QuartzNetWebConsole.sln +++ b/QuartzNetWebConsole.sln @@ -20,6 +20,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "SampleApp\Samp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuartzNetWebConsole.Web", "QuartzNetWebConsole.Web\QuartzNetWebConsole.Web.csproj", "{1F666487-F60B-4C8D-B632-58B24C37FEC1}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp.Owin", "SampleApp.Owin\SampleApp.Owin.csproj", "{76153C2F-C591-417B-A9D9-38E7A2DFF5F3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -46,6 +48,10 @@ Global {1F666487-F60B-4C8D-B632-58B24C37FEC1}.Debug|Any CPU.Build.0 = Debug|Any CPU {1F666487-F60B-4C8D-B632-58B24C37FEC1}.Release|Any CPU.ActiveCfg = Release|Any CPU {1F666487-F60B-4C8D-B632-58B24C37FEC1}.Release|Any CPU.Build.0 = Release|Any CPU + {76153C2F-C591-417B-A9D9-38E7A2DFF5F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {76153C2F-C591-417B-A9D9-38E7A2DFF5F3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {76153C2F-C591-417B-A9D9-38E7A2DFF5F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {76153C2F-C591-417B-A9D9-38E7A2DFF5F3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/QuartzNetWebConsole/Controllers/LogController.cs b/QuartzNetWebConsole/Controllers/LogController.cs index 35cc44c..0fc7b32 100644 --- a/QuartzNetWebConsole/Controllers/LogController.cs +++ b/QuartzNetWebConsole/Controllers/LogController.cs @@ -39,7 +39,7 @@ public static Response Execute(Uri url) { public static KeyValuePair, PaginationInfo, string, XDocument>> GetView(IEnumerable qs) { if (qs.Contains("rss")) return Helpers.KV("application/rss+xml", RSSView); - return Helpers.KV((string)null, XHTMLView); + return Helpers.KV("text/html", XHTMLView); } public static readonly Func, PaginationInfo, string, XDocument> XHTMLView = diff --git a/QuartzNetWebConsole/Setup.cs b/QuartzNetWebConsole/Setup.cs index de45484..79bc248 100644 --- a/QuartzNetWebConsole/Setup.cs +++ b/QuartzNetWebConsole/Setup.cs @@ -1,5 +1,10 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; using Quartz; +using QuartzNetWebConsole.Utils; namespace QuartzNetWebConsole { public static class Setup { @@ -37,5 +42,84 @@ public static ILogger Logger { static Setup() { Scheduler = () => { throw new Exception("Define QuartzNetWebConsole.Setup.Scheduler"); }; } + + private static Uri GetOwinUri(this IDictionary env) { + var headers = (IDictionary)env["owin.RequestHeaders"]; + var scheme = (string)env["owin.RequestScheme"]; + var hostAndPort = headers["Host"].First().Split(':'); + var host = hostAndPort[0]; + var port = hostAndPort.Length > 1 ? int.Parse(hostAndPort[1]) : (scheme == Uri.UriSchemeHttp ? 80 : 443); + var path = (string)env["owin.RequestPathBase"] + (string)env["owin.RequestPath"]; + var query = (string)env["owin.RequestQueryString"]; + + var uriBuilder = new UriBuilder(scheme: scheme, host: host, portNumber: port) { + Path = path, + Query = query, + }; + + return uriBuilder.Uri; + } + + private static Stream GetOwinResponseBody(this IDictionary env) { + return (Stream) env["owin.ResponseBody"]; + } + + private static IDictionary GetOwinResponseHeaders(this IDictionary env) { + return (IDictionary) env["owin.ResponseHeaders"]; + } + + private static void SetOwinContentType(this IDictionary env, string contentType) { + if (string.IsNullOrEmpty(contentType)) + return; + env.GetOwinResponseHeaders()["Content-Type"] = new [] {contentType}; + } + + private static void SetOwinContentLength(this IDictionary env, long length) { + env.GetOwinResponseHeaders()["Content-Length"] = new[] { length.ToString() }; + } + + private static void SetOwinStatusCode(this IDictionary env, int statusCode) { + env["owin.ResponseStatusCode"] = statusCode; + } + + public delegate Task AppFunc(IDictionary env); + + private static AppFunc EvaluateResponse(Response response) { + return env => response.Match( + content: async x => { + env.SetOwinContentType(x.ContentType); + env.SetOwinContentLength(x.Content.Length); + var sw = new StreamWriter(env.GetOwinResponseBody()); + await sw.WriteAsync(x.Content); + await sw.FlushAsync(); + }, + xdoc: async x => { + env.SetOwinContentType(x.ContentType); + var content = x.Content.ToString(); + env.SetOwinContentLength(content.Length); + var sw = new StreamWriter(env.GetOwinResponseBody()); + await sw.WriteAsync(content); + await sw.FlushAsync(); + }, + redirect: async x => { + env.SetOwinStatusCode(302); + env.GetOwinResponseHeaders()["Location"] = new [] {x.Location}; + await Task.Yield(); + }); + } + + public static Func Owin(Func scheduler) { + Setup.Scheduler = scheduler; + return app => env => { + var uri = env.GetOwinUri(); + var response = + Routing.Routes + .Where(x => uri.AbsolutePath.Split('.')[0].EndsWith(x.Key, StringComparison.InvariantCultureIgnoreCase)) + .Select(r => r.Value(uri)) + .Select(EvaluateResponse) + .FirstOrDefault(); + return response == null ? app(env) : response(env); + }; + } } } \ No newline at end of file diff --git a/SampleApp.Owin/App.config b/SampleApp.Owin/App.config new file mode 100644 index 0000000..fad249e --- /dev/null +++ b/SampleApp.Owin/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SampleApp.Owin/HelloJob.cs b/SampleApp.Owin/HelloJob.cs new file mode 100644 index 0000000..6699a6d --- /dev/null +++ b/SampleApp.Owin/HelloJob.cs @@ -0,0 +1,15 @@ +using System; +using System.Threading; +using Quartz; + +namespace SampleApp { + /// + /// A sample dummy job + /// + [DisallowConcurrentExecution] + public class HelloJob : IJob { + public void Execute(IJobExecutionContext context) { + Thread.Sleep(5000); + } + } +} \ No newline at end of file diff --git a/SampleApp.Owin/Program.cs b/SampleApp.Owin/Program.cs new file mode 100644 index 0000000..0f0968b --- /dev/null +++ b/SampleApp.Owin/Program.cs @@ -0,0 +1,50 @@ +using System; +using Microsoft.Owin.Hosting; +using Owin; +using Quartz; +using Quartz.Impl; + +namespace SampleApp.Owin { + class Program { + static void Start(IAppBuilder app) { + + // First, initialize Quartz.NET as usual. In this sample app I'll configure Quartz.NET by code. + var schedulerFactory = new StdSchedulerFactory(); + var scheduler = schedulerFactory.GetScheduler(); + scheduler.Start(); + + // I'll add some global listeners + //scheduler.ListenerManager.AddJobListener(new GlobalJobListener()); + //scheduler.ListenerManager.AddTriggerListener(new GlobalTriggerListener()); + + // A sample trigger and job + var trigger = TriggerBuilder.Create() + .WithIdentity("myTrigger") + .WithSchedule(DailyTimeIntervalScheduleBuilder.Create() + .WithIntervalInSeconds(6)) + .StartNow() + .Build(); + var job = new JobDetailImpl("myJob", null, typeof(HelloJob)); + scheduler.ScheduleJob(job, trigger); + + // A cron trigger and job + var cron = TriggerBuilder.Create() + .WithIdentity("myCronTrigger") + .ForJob(job.Key) + .WithCronSchedule("0/10 * * * * ?") // every 10 seconds + .Build(); + + scheduler.ScheduleJob(cron); + + // A dummy calendar + //scheduler.AddCalendar("myCalendar", new DummyCalendar { Description = "dummy calendar" }, false, false); + + app.Use(QuartzNetWebConsole.Setup.Owin(() => scheduler)); + } + + private static void Main(string[] args) { + using (WebApp.Start("http://localhost:12345", Start)) + Console.ReadLine(); + } + } +} \ No newline at end of file diff --git a/SampleApp.Owin/Properties/AssemblyInfo.cs b/SampleApp.Owin/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8856f8a --- /dev/null +++ b/SampleApp.Owin/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("SampleApp.Owin")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SampleApp.Owin")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("29510bce-2fc0-41b8-b16a-716ed60f8f97")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/SampleApp.Owin/SampleApp.Owin.csproj b/SampleApp.Owin/SampleApp.Owin.csproj new file mode 100644 index 0000000..bceb98f --- /dev/null +++ b/SampleApp.Owin/SampleApp.Owin.csproj @@ -0,0 +1,104 @@ + + + + + Debug + AnyCPU + {76153C2F-C591-417B-A9D9-38E7A2DFF5F3} + Exe + Properties + SampleApp.Owin + SampleApp.Owin + v4.5 + 512 + ..\ + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Common.Logging.3.0.0\lib\net40\Common.Logging.dll + + + ..\packages\Common.Logging.Core.3.0.0\lib\net40\Common.Logging.Core.dll + + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + + + False + ..\packages\Microsoft.Owin.Host.HttpListener.3.0.1\lib\net45\Microsoft.Owin.Host.HttpListener.dll + + + ..\packages\Microsoft.Owin.Hosting.3.0.1\lib\net45\Microsoft.Owin.Hosting.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + ..\packages\Quartz.2.3.2\lib\net40\Quartz.dll + + + + + + + + + + + + + + + + + + Designer + + + + + + {7961ab01-1549-4340-8d3d-f0da43b8c84f} + QuartzNetWebConsole.Views + + + {72a7a322-38dd-47dd-8948-6ed6e2f9dc5d} + QuartzNetWebConsole + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/SampleApp.Owin/job_scheduling_data_2_0.xsd b/SampleApp.Owin/job_scheduling_data_2_0.xsd new file mode 100644 index 0000000..64ab996 --- /dev/null +++ b/SampleApp.Owin/job_scheduling_data_2_0.xsd @@ -0,0 +1,361 @@ + + + + + + + Root level node + + + + + + Commands to be executed before scheduling the jobs and triggers in this file. + + + + + Directives to be followed while scheduling the jobs and triggers in this file. + + + + + + + + + + + + + + Version of the XML Schema instance + + + + + + + + + + Delete all jobs, if any, in the identified group. "*" can be used to identify all groups. Will also result in deleting all triggers related to the jobs. + + + + + Delete all triggers, if any, in the identified group. "*" can be used to identify all groups. Will also result in deletion of related jobs that are non-durable. + + + + + Delete the identified job if it exists (will also result in deleting all triggers related to it). + + + + + + + + + + + Delete the identified trigger if it exists (will also result in deletion of related jobs that are non-durable). + + + + + + + + + + + + + + + + Whether the existing scheduling data (with same identifiers) will be overwritten. If false, and ignore-duplicates is not false, and jobs or triggers with the same names already exist as those in the file, an error will occur. + + + + + If true (and overwrite-existing-data is false) then any job/triggers encountered in this file that have names that already exist in the scheduler will be ignored, and no error will be produced. + + + + + If true trigger's start time is calculated based on earlier run time instead of fixed value. Trigger's start time must be undefined for this to work. + + + + + + + + Define a JobDetail + + + + + + + + + + + + + + + + + Define a JobDataMap + + + + + + + + + Define a JobDataMap entry + + + + + + + + + + Define a Trigger + + + + + + + + + + + Common Trigger definitions + + + + + + + + + + + + + + + + + + + + + + + Define a SimpleTrigger + + + + + + + + + + + + + + + + + Define a CronTrigger + + + + + + + + + + + + + + + Define a DateIntervalTrigger + + + + + + + + + + + + + + + + Cron expression (see JavaDoc for examples) + + Special thanks to Chris Thatcher (thatcher@butterfly.net) for the regular expression! + + Regular expressions are not my strong point but I believe this is complete, + with the caveat that order for expressions like 3-0 is not legal but will pass, + and month and day names must be capitalized. + If you want to examine the correctness look for the [\s] to denote the + seperation of individual regular expressions. This is how I break them up visually + to examine them: + + SECONDS: + ( + ((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?) + | (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9])) + | ([\?]) + | ([\*]) + ) [\s] + MINUTES: + ( + ((([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?,)*([0-9]|[0-5][0-9])(-([0-9]|[0-5][0-9]))?) + | (([\*]|[0-9]|[0-5][0-9])/([0-9]|[0-5][0-9])) + | ([\?]) + | ([\*]) + ) [\s] + HOURS: + ( + ((([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?,)*([0-9]|[0-1][0-9]|[2][0-3])(-([0-9]|[0-1][0-9]|[2][0-3]))?) + | (([\*]|[0-9]|[0-1][0-9]|[2][0-3])/([0-9]|[0-1][0-9]|[2][0-3])) + | ([\?]) + | ([\*]) + ) [\s] + DAY OF MONTH: + ( + ((([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?,)*([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(-([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1]))?(C)?) + | (([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])/([1-9]|[0][1-9]|[1-2][0-9]|[3][0-1])(C)?) + | (L(-[0-9])?) + | (L(-[1-2][0-9])?) + | (L(-[3][0-1])?) + | (LW) + | ([1-9]W) + | ([1-3][0-9]W) + | ([\?]) + | ([\*]) + )[\s] + MONTH: + ( + ((([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?,)*([1-9]|0[1-9]|1[0-2])(-([1-9]|0[1-9]|1[0-2]))?) + | (([1-9]|0[1-9]|1[0-2])/([1-9]|0[1-9]|1[0-2])) + | (((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?,)*(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(-(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?) + | ((JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)/(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)) + | ([\?]) + | ([\*]) + )[\s] + DAY OF WEEK: + ( + (([1-7](-([1-7]))?,)*([1-7])(-([1-7]))?) + | ([1-7]/([1-7])) + | (((MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?,)*(MON|TUE|WED|THU|FRI|SAT|SUN)(-(MON|TUE|WED|THU|FRI|SAT|SUN))?(C)?) + | ((MON|TUE|WED|THU|FRI|SAT|SUN)/(MON|TUE|WED|THU|FRI|SAT|SUN)(C)?) + | (([1-7]|(MON|TUE|WED|THU|FRI|SAT|SUN))(L|LW)?) + | (([1-7]|MON|TUE|WED|THU|FRI|SAT|SUN)#([1-7])?) + | ([\?]) + | ([\*]) + ) + YEAR (OPTIONAL): + ( + [\s]? + ([\*])? + | ((19[7-9][0-9])|(20[0-9][0-9]))? + | (((19[7-9][0-9])|(20[0-9][0-9]))/((19[7-9][0-9])|(20[0-9][0-9])))? + | ((((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?,)*((19[7-9][0-9])|(20[0-9][0-9]))(-((19[7-9][0-9])|(20[0-9][0-9])))?)? + ) + + + + + + + + + + Number of times to repeat the Trigger (-1 for indefinite) + + + + + + + + + + Simple Trigger Misfire Instructions + + + + + + + + + + + + + + Cron Trigger Misfire Instructions + + + + + + + + + + + Date Interval Trigger Misfire Instructions + + + + + + + + + + + Interval Units + + + + + + + + + + + + + \ No newline at end of file diff --git a/SampleApp.Owin/packages.config b/SampleApp.Owin/packages.config new file mode 100644 index 0000000..71ff8d8 --- /dev/null +++ b/SampleApp.Owin/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/packages/repositories.config b/packages/repositories.config index 1580fea..a308a17 100644 --- a/packages/repositories.config +++ b/packages/repositories.config @@ -4,5 +4,6 @@ + \ No newline at end of file