├── .gitignore ├── Tempus.sln ├── license.txt ├── readme.md ├── src ├── CommonAssemblyInfo.cs ├── Workshell.Tempus.AspNetCore │ ├── AspNetJobFactory.cs │ ├── AspNetJobScope.cs │ ├── AspNetJobWrapper.cs │ ├── Extensions │ │ └── ServiceCollectionExtensions.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── TempusBackgroundService.cs │ ├── TempusOptions.cs │ └── Workshell.Tempus.AspNetCore.csproj └── Workshell.Tempus │ ├── ActiveJob.cs │ ├── ActiveJobOrder.cs │ ├── ActiveJobs.cs │ ├── Attributes │ ├── CronAttribute.cs │ ├── NoOverlapAttribute.cs │ ├── OnceAttribute.cs │ └── OverlapAttribute.cs │ ├── Events │ ├── JobErrorEventArgs.cs │ ├── JobEventArgs.cs │ ├── JobStartedEventArgs.cs │ └── JobStartingEventArgs.cs │ ├── Extensions │ ├── DisposableExtensions.cs │ └── JobSchedulerExtensions.cs │ ├── Interfaces │ ├── IActiveJob.cs │ ├── IActiveJobs.cs │ ├── IJob.cs │ ├── IJobFactory.cs │ ├── IJobRunner.cs │ ├── IJobScheduler.cs │ ├── IJobScope.cs │ ├── IScheduledJob.cs │ └── IScheduledJobs.cs │ ├── JobExecutionContext.cs │ ├── JobFactory.cs │ ├── JobRunner.cs │ ├── JobScheduler.cs │ ├── JobScope.cs │ ├── OverlapHandling.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ScheduledJob.cs │ ├── ScheduledJobs.cs │ ├── TempusException.cs │ ├── Utils.cs │ └── Workshell.Tempus.csproj ├── tests └── Workshell.Tempus.Tests │ ├── Mocks │ ├── MockCronJob.cs │ ├── MockImmediateJob.cs │ └── MockOnceJob.cs │ ├── ScheduledJob │ └── ScheduledJobTests.cs │ └── Workshell.Tempus.Tests.csproj └── version.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | .vs/ 3 | [Bb]in/ 4 | [Oo]bj/ 5 | [Pp]ackages/ 6 | [Tt]ools/ 7 | 8 | # mstest test results 9 | TestResults 10 | 11 | ## Ignore Visual Studio temporary files, build results, and 12 | ## files generated by popular Visual Studio add-ons. 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.sln.docstates 18 | *.snk 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Rr]elease/ 23 | x64/ 24 | *_i.c 25 | *_p.c 26 | *.ilk 27 | *.meta 28 | *.obj 29 | *.pch 30 | *.pdb 31 | *.pgc 32 | *.pgd 33 | *.rsp 34 | *.sbr 35 | *.tlb 36 | *.tli 37 | *.tlh 38 | *.tmp 39 | *.log 40 | *.vspscc 41 | *.vssscc 42 | .builds 43 | *.nupkg 44 | 45 | # Visual C++ cache files 46 | ipch/ 47 | *.aps 48 | *.ncb 49 | *.opensdf 50 | *.sdf 51 | 52 | # Visual Studio profiler 53 | *.psess 54 | *.vsp 55 | *.vspx 56 | 57 | # Guidance Automation Toolkit 58 | *.gpState 59 | 60 | # ReSharper is a .NET coding add-in 61 | _ReSharper* 62 | 63 | # NCrunch 64 | *.ncrunch* 65 | .*crunch*.local.xml 66 | 67 | # Installshield output folder 68 | [Ee]xpress 69 | 70 | # DocProject is a documentation generator add-in 71 | DocProject/buildhelp/ 72 | DocProject/Help/*.HxT 73 | DocProject/Help/*.HxC 74 | DocProject/Help/*.hhc 75 | DocProject/Help/*.hhk 76 | DocProject/Help/*.hhp 77 | DocProject/Help/Html2 78 | DocProject/Help/html 79 | 80 | # Click-Once directory 81 | publish 82 | 83 | # Publish Web Output 84 | *.Publish.xml 85 | 86 | # NuGet Packages Directory 87 | packages 88 | 89 | # Windows Azure Build Output 90 | csx 91 | *.build.csdef 92 | 93 | # Windows Store app package directory 94 | AppPackages/ 95 | 96 | # Others 97 | [Bb]in 98 | [Oo]bj 99 | sql 100 | TestResults 101 | [Tt]est[Rr]esult* 102 | *.Cache 103 | ClientBin 104 | [Ss]tyle[Cc]op.* 105 | ~$* 106 | *.dbmdl 107 | Generated_Code #added for RIA/Silverlight projects 108 | 109 | # Backup & report files from converting an old project file to a newer 110 | # Visual Studio version. Backup files are not needed, because we have git ;-) 111 | _UpgradeReport_Files/ 112 | Backup*/ 113 | UpgradeLog*.XML -------------------------------------------------------------------------------- /Tempus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30011.22 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8686DC99-FF1E-4A03-8765-5013DA333BC4}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C2B21C4E-7807-4BEF-A1C6-FCBF75A73A66}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshell.Tempus", "src\Workshell.Tempus\Workshell.Tempus.csproj", "{A6573CB7-61BE-494D-B1DD-F82F64DB1050}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshell.Tempus.AspNetCore", "src\Workshell.Tempus.AspNetCore\Workshell.Tempus.AspNetCore.csproj", "{91D11A6F-D800-4E97-93D5-3030311F7B2B}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2CF38C9F-7D88-4F60-B7F8-BD04376E8BD3}" 15 | ProjectSection(SolutionItems) = preProject 16 | src\CommonAssemblyInfo.cs = src\CommonAssemblyInfo.cs 17 | license.txt = license.txt 18 | readme.md = readme.md 19 | version.txt = version.txt 20 | EndProjectSection 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workshell.Tempus.Tests", "tests\Workshell.Tempus.Tests\Workshell.Tempus.Tests.csproj", "{DD1FDAAB-373D-44F6-B3AD-404D486F7EE4}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {A6573CB7-61BE-494D-B1DD-F82F64DB1050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {A6573CB7-61BE-494D-B1DD-F82F64DB1050}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {A6573CB7-61BE-494D-B1DD-F82F64DB1050}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {A6573CB7-61BE-494D-B1DD-F82F64DB1050}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {91D11A6F-D800-4E97-93D5-3030311F7B2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {91D11A6F-D800-4E97-93D5-3030311F7B2B}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {91D11A6F-D800-4E97-93D5-3030311F7B2B}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {91D11A6F-D800-4E97-93D5-3030311F7B2B}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {DD1FDAAB-373D-44F6-B3AD-404D486F7EE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {DD1FDAAB-373D-44F6-B3AD-404D486F7EE4}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {DD1FDAAB-373D-44F6-B3AD-404D486F7EE4}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {DD1FDAAB-373D-44F6-B3AD-404D486F7EE4}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(NestedProjects) = preSolution 47 | {A6573CB7-61BE-494D-B1DD-F82F64DB1050} = {8686DC99-FF1E-4A03-8765-5013DA333BC4} 48 | {91D11A6F-D800-4E97-93D5-3030311F7B2B} = {8686DC99-FF1E-4A03-8765-5013DA333BC4} 49 | {DD1FDAAB-373D-44F6-B3AD-404D486F7EE4} = {C2B21C4E-7807-4BEF-A1C6-FCBF75A73A66} 50 | EndGlobalSection 51 | GlobalSection(ExtensibilityGlobals) = postSolution 52 | SolutionGuid = {4CC5E1CC-A02C-4E58-B3C2-E722CA851706} 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Workshell Ltd 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Workshell Tempus 2 | 3 | [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/Workshell/tempus/blob/master/license.txt) 4 | [![NuGet](https://img.shields.io/nuget/v/Workshell.Tempus.svg)](https://www.nuget.org/packages/Workshell.Tempus/) 5 | [![Build Status](https://dev.azure.com/Workshell-DevOps/tempus/_apis/build/status/Build%20Master?branchName=master)](https://dev.azure.com/Workshell-DevOps/tempus/_build/latest?definitionId=2&branchName=master) 6 | 7 | This is a class library for scheduling and executing jobs in a similar vain to Hangfire or Quartz, although with a simpler API surface and feature set. 8 | 9 | ## Installation 10 | 11 | Stable builds are available as NuGet packages. You can install it via the Package Manager or via the Package Manager Console: 12 | 13 | ```` 14 | > Install-Package Workshell.Tempus 15 | > Install-Package Workshell.Tempus.AspNetCore // If you're using ASP.NET Core 16 | ```` 17 | 18 | ## Scheduler Setup 19 | 20 | The scheduler is a singleton instance that manages all jobs. To create it it's as simple as: 21 | 22 | ````csharp 23 | var scheduler = JobScheduler.Create(); 24 | ```` 25 | 26 | After that it's necessary to start the scheduler, again another one-liner: 27 | 28 | ````csharp 29 | scheduler.Start(); 30 | ```` 31 | 32 | The scheduler has a timing resolution of 1 second so you can't create schedules smaller than this granularity. 33 | 34 | The scheduler supports the following scheduling: 35 | 36 | * Immediately 37 | * Once 38 | * Interval (using Cron patterns with second resolution) 39 | 40 | You also have a choice of how to implement a job, you can use `Action`, `Func` (which can be used with async calls) or create a class that implements the `IJob` interface. 41 | 42 | ## Immediate Jobs 43 | 44 | An immediate job is placed in the schedule and executed as soon as possible. 45 | 46 | You can use a class and register it with the scheduler like below: 47 | 48 | ````csharp 49 | class SomeJob : IJob 50 | { 51 | public Task ExecuteAsync(JobExecutionContext context) 52 | { 53 | // Do some work 54 | 55 | return Task.CompletedTask; 56 | } 57 | } 58 | 59 | scheduler.Schedule(); 60 | ```` 61 | 62 | Using an `Action`: 63 | 64 | ````csharp 65 | scheduler.Schedule((context) => 66 | { 67 | // Do some work 68 | }); 69 | ```` 70 | 71 | Using a `Func`: 72 | 73 | ````csharp 74 | scheduler.Schedule((context) => 75 | { 76 | // Do some work 77 | 78 | return Task.CompletedTask; 79 | }); 80 | ```` 81 | 82 | Or... 83 | 84 | ````csharp 85 | scheduler.Schedule(async (context) => 86 | { 87 | // Do some work 88 | }); 89 | ```` 90 | 91 | ## One-time Jobs 92 | 93 | A one-time job has a static execution point in the future, say 00:00 Christmas Day. 94 | 95 | Using a class: 96 | 97 | ````csharp 98 | [Once("2020-12-25T00:00:00Z")] // Note the attribute 99 | class SomeJob : IJob 100 | { 101 | public Task ExecuteAsync(JobExecutionContext context) 102 | { 103 | // Do some work 104 | 105 | return Task.CompletedTask; 106 | } 107 | } 108 | 109 | scheduler.Schedule(); 110 | ```` 111 | 112 | Using an `Action`: 113 | 114 | ````csharp 115 | var when = DateTime.Parse("2020-12-25T00:00:00Z"); 116 | 117 | scheduler.Schedule(when, (context) => 118 | { 119 | // Do some work 120 | }); 121 | ```` 122 | 123 | Using a `Func`: 124 | 125 | ````csharp 126 | var when = DateTime.Parse("2020-12-25T00:00:00Z"); 127 | 128 | scheduler.Schedule(when, (context) => 129 | { 130 | // Do some work 131 | 132 | return Task.CompletedTask; 133 | }); 134 | ```` 135 | 136 | Or... 137 | 138 | ````csharp 139 | var when = DateTime.Parse("2020-12-25T00:00:00Z"); 140 | 141 | scheduler.Schedule(when, async (context) => 142 | { 143 | // Do some work 144 | }); 145 | ```` 146 | 147 | ## Interval Jobs 148 | 149 | Interval jobs run on a schedule for example, every 30 seconds. 150 | 151 | Using a class: 152 | 153 | ````csharp 154 | [Cron("*/30 * * * * *")] // Note the attribute 155 | class SomeJob : IJob 156 | { 157 | public Task ExecuteAsync(JobExecutionContext context) 158 | { 159 | // Do some work 160 | 161 | return Task.CompletedTask; 162 | } 163 | } 164 | 165 | scheduler.Schedule(); 166 | ```` 167 | 168 | Using an `Action`: 169 | 170 | ````csharp 171 | scheduler.Schedule("*/30 * * * * *", (context) => 172 | { 173 | // Do some work 174 | }); 175 | ```` 176 | 177 | Using a `Func`: 178 | 179 | ````csharp 180 | scheduler.Schedule("*/30 * * * * *", (context) => 181 | { 182 | // Do some work 183 | 184 | return Task.CompletedTask; 185 | }); 186 | ```` 187 | 188 | Or... 189 | 190 | ````csharp 191 | scheduler.Schedule("*/30 * * * * *", async (context) => 192 | { 193 | // Do some work 194 | }); 195 | ```` 196 | 197 | ## Overlapping 198 | 199 | With smaller intervals and/or longer executions there's a chance that executions can overlap each other, for example scheduling something to run every 10 seconds but each execution taking 15-20 seconds. 200 | 201 | There are three ways to deal with the overlap: 202 | 203 | 1. Allow - Allow overlapping executions 204 | 2. Wait - Wait until a previous execution finishes before executing itself 205 | 3. Skip - Don't execute if overlapped, roll over if on a schedule 206 | 207 | By default the scheduler will allow overlapped executions, however you can override this behaviour. 208 | 209 | With classes you can just add the `[Overlap]` attribute to the class that implements the IJob you want to change the overlap behaviour, and specify a value from the `OverlapHandling` enum: 210 | 211 | ````csharp 212 | [Overlap(OverlapHandling.Skip)] 213 | class SomeJob : IJob 214 | { 215 | ... 216 | } 217 | ```` 218 | 219 | For `Action` and `Func` methods that's a `overlapHandling` parameter you can set. 220 | 221 | ## ASP.NET Core 222 | 223 | We have an integration package for ASP.NET Core which makes using Tempus very easy. Instead of manually configuring the scheduler you can use the built in dependency injection to do it all for you. 224 | 225 | To add in the scheduler and everything needed, in `ConfigureServices` you can do: 226 | 227 | ````csharp 228 | services.AddTempus(); 229 | ```` 230 | 231 | This will register the `IJobScheduler` as a singleton along with support infrastructure such as an `IHostedService` which will hook into starting and stopping the scheduler as needed. 232 | 233 | Then to register jobs, for a class: 234 | 235 | ````csharp 236 | services.AddTempusJob(); 237 | ```` 238 | 239 | For an `Action` or `Func`: 240 | 241 | ````csharp 242 | services.AddTempusJob("*/10 * * * * *", () => 243 | { 244 | // Do some work 245 | }); 246 | ```` 247 | 248 | You can also then pick up the core interfaces in other classes through depedency injection, for example: 249 | 250 | ````csharp 251 | class SomeOtherClass 252 | { 253 | public SomeOtherClass(IJobScheduler scheduler) 254 | { 255 | ... 256 | } 257 | } 258 | ```` 259 | 260 | ## Cron Format 261 | 262 | Tempus currently uses the excellent [HangfireIO/Cronos](https://github.com/HangfireIO/Cronos) library, by the people who wrote Hangfire, for parsing Cron scheduling expressions. 263 | 264 | We've shamelessy copied their cron format details below. 265 | 266 | Cron expression is a mask to define fixed times, dates and intervals. The mask consists of second (optional), minute, hour, day-of-month, month and day-of-week fields. All of the fields allow you to specify multiple values, and any given date/time will satisfy the specified Cron expression, if all the fields contain a matching value. 267 | 268 | Allowed values Allowed special characters Comment 269 | 270 | ┌───────────── second (optional) 0-59 * , - / 271 | │ ┌───────────── minute 0-59 * , - / 272 | │ │ ┌───────────── hour 0-23 * , - / 273 | │ │ │ ┌───────────── day of month 1-31 * , - / L W ? 274 | │ │ │ │ ┌───────────── month 1-12 or JAN-DEC * , - / 275 | │ │ │ │ │ ┌───────────── day of week 0-6 or SUN-SAT * , - / # L ? Both 0 and 7 means SUN 276 | │ │ │ │ │ │ 277 | * * * * * * 278 | 279 | ### Base characters 280 | 281 | In all fields you can use number, `*` to mark field as *any value*, `-` to specify ranges of values. Reversed ranges like `22-1`(equivalent to `22,23,0,1,2`) are also supported. 282 | 283 | It's possible to define **step** combining `/` with `*`, numbers and ranges. For example, `*/5` in minute field describes *every 5 minute* and `1-15/3` in day-of-month field – *every 3 days from the 1st to the 15th*. Pay attention that `*/24` is just equivalent to `0,24,48` and `*/24` in minute field doesn't literally mean *every 24 minutes* it means *every 0,24,48 minute*. 284 | 285 | Concatinate values and ranges by `,`. Comma works like `OR` operator. So `3,5-11/3,12` is equivalent to `3,5,8,11,12`. 286 | 287 | In month and day-of-week fields, you can use names of months or days of weeks abbreviated to first three letters (`Jan-Dec` or `Mon-Sun`) instead of their numeric values. Full names like `JANUARY` or `MONDAY` **aren't supported**. 288 | 289 | For day of week field, both `0` and `7` stays for Sunday, 1 for Monday. 290 | 291 | | Expression | Description | 292 | |----------------------|---------------------------------------------------------------------------------------| 293 | | `* * * * *` | Every minute | 294 | | `0 0 1 * *` | At midnight, on day 1 of every month | 295 | | `*/5 * * * *` | Every 5 minutes | 296 | | `30,45-15/2 1 * * *` | Every 2 minute from 1:00 AM to 01:15 AM and from 1:45 AM to 1:59 AM and at 1:30 AM | 297 | | `0 0 * * MON-FRI` | At 00:00, Monday through Friday | 298 | 299 | ### Special characters 300 | 301 | Most expressions you can describe using base characters. If you want to deal with more complex cases like *the last day of month* or *the 2nd Saturday* use special characters: 302 | 303 | **`L`** stands for "last". When used in the day-of-week field, it allows you to specify constructs such as *the last Friday* (`5L`or `FRIL`). In the day-of-month field, it specifies the last day of the month. 304 | 305 | **`W`** in day-of-month field is the nearest weekday. Use `W` with single value (not ranges, steps or `*`) to define *the nearest weekday* to the given day. In this case there are two base rules to determine occurrence: we should shift to **the nearest weekday** and **can't shift to different month**. Thus if given day is Saturday we shift to Friday, if it is Sunday we shift to Monday. **But** if given day is **the 1st day of month** (e.g. `0 0 1W * *`) and it is Saturday we shift to the 3rd Monday, if given day is **last day of month** (`0 0 31W 0 0`) and it is Sunday we shift to that Friday. Mix `L` (optionaly with offset) and `W` characters to specify *last weekday of month* `LW` or more complex like `L-5W`. 306 | 307 | **`#`** in day-of-week field allows to specify constructs such as *second Saturday* (`6#2` or `SAT#2`). 308 | 309 | **`?`** is synonym of `*`. It's supported but not obligatory, so `0 0 5 * ?` is the same as `0 0 5 * *`. 310 | 311 | | Expression | Description | 312 | |-------------------|----------------------------------------------------------| 313 | | `0 0 L * *` | At 00:00 AM on the last day of the month | 314 | | `0 0 L-1 * *` | At 00:00 AM the day before the last day of the month | 315 | | `0 0 3W * *` | At 00:00 AM, on the 3rd weekday of every month | 316 | | `0 0 LW * *` | At 00:00 AM, on the last weekday of the month | 317 | | `0 0 * * 2L` | At 00:00 AM on the last tuesday of the month | 318 | | `0 0 * * 6#3` | At 00:00 AM on the third Saturday of the month | 319 | | `0 0 ? 1 MON#1` | At 00:00 AM on the first Monday of the January | 320 | 321 | ### Specify Day of month and Day of week 322 | 323 | You can set both **day-of-month** and **day-of-week**, it allows you to specify constructs such as **Friday the thirteenth**. Thus `0 0 13 * 5` means at 00:00, Friday the thirteenth. 324 | 325 | It differs from Unix crontab and Quartz cron implementations. Crontab handles it like `OR` operator: occurrence can happen in given day of month or given day of week. So `0 0 13 * 5` means *at 00:00 AM, every friday or every the 13th of a month*. Quartz doesn't allow specify both day-of-month and day-of-week. 326 | 327 | ### Macro 328 | 329 | A macro is a string starting with `@` and representing a shortcut for simple cases like *every day* or *every minute*. 330 | 331 | Macro | Equivalent | Comment 332 | ----------------|---------------| ------- 333 | `@every_second` | `* * * * * *` | Run once a second 334 | `@every_minute` | `* * * * *` | Run once a minute at the beginning of the minute 335 | `@hourly` | `0 * * * *` | Run once an hour at the beginning of the hour 336 | `@daily` | `0 0 * * *` | Run once a day at midnight 337 | `@midnight` | `0 0 * * *` | Run once a day at midnight 338 | `@weekly` | `0 0 * * 0` | Run once a week at midnight on Sunday morning 339 | `@monthly` | `0 0 1 * *` | Run once a month at midnight of the first day of the month 340 | `@yearly` | `0 0 1 1 *` | Run once a year at midnight of 1 January 341 | `@annually` | `0 0 1 1 *` | Run once a year at midnight of 1 January 342 | 343 | ## MIT License 344 | 345 | Copyright (c) Workshell Ltd 346 | 347 | Permission is hereby granted, free of charge, to any person obtaining a copy 348 | of this software and associated documentation files (the "Software"), to deal 349 | in the Software without restriction, including without limitation the rights 350 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 351 | copies of the Software, and to permit persons to whom the Software is 352 | furnished to do so, subject to the following conditions: 353 | 354 | The above copyright notice and this permission notice shall be included in all 355 | copies or substantial portions of the Software. 356 | 357 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 358 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 359 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 360 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 361 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 362 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 363 | SOFTWARE. 364 | -------------------------------------------------------------------------------- /src/CommonAssemblyInfo.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Workshell/tempus/43aa9026b35395df8a49099952e0f8dcb559dc43/src/CommonAssemblyInfo.cs -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/AspNetJobFactory.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | using Microsoft.Extensions.DependencyInjection; 28 | 29 | namespace Workshell.Tempus.AspNetCore 30 | { 31 | internal sealed class AspNetJobFactory : JobFactory 32 | { 33 | private readonly IServiceProvider _provider; 34 | 35 | public AspNetJobFactory(IServiceProvider provider) 36 | { 37 | _provider = provider; 38 | } 39 | 40 | #region Methods 41 | 42 | public override IJobScope CreateScope() 43 | { 44 | var scope = _provider.CreateScope(); 45 | 46 | return new AspNetJobScope(scope); 47 | } 48 | 49 | #endregion 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/AspNetJobScope.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Reflection; 26 | using System.Text; 27 | 28 | using Microsoft.Extensions.DependencyInjection; 29 | 30 | namespace Workshell.Tempus.AspNetCore 31 | { 32 | internal sealed class AspNetJobScope : JobScope 33 | { 34 | private readonly IServiceScope _scope; 35 | private volatile bool _disposed; 36 | 37 | public AspNetJobScope(IServiceScope scope) 38 | { 39 | _scope = scope; 40 | _disposed = false; 41 | } 42 | 43 | #region Methods 44 | 45 | public override void Dispose() 46 | { 47 | if (_disposed) 48 | { 49 | return; 50 | } 51 | 52 | _scope.Dispose(); 53 | GC.SuppressFinalize(this); 54 | 55 | _disposed = true; 56 | } 57 | 58 | public override IJob Create(Type type) 59 | { 60 | if (!typeof(IJob).GetTypeInfo().IsAssignableFrom(type)) 61 | { 62 | throw new ArgumentException("Specified type does not support IJob."); 63 | } 64 | 65 | var job = (IJob)_scope.ServiceProvider.GetService(type); 66 | 67 | return job; 68 | } 69 | 70 | #endregion 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/AspNetJobWrapper.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Reflection; 26 | using System.Text; 27 | using System.Threading.Tasks; 28 | 29 | namespace Workshell.Tempus.AspNetCore 30 | { 31 | internal sealed class AspNetJobWrapper 32 | { 33 | public AspNetJobWrapper(Type type, object state) 34 | { 35 | if (!typeof(IJob).GetTypeInfo().IsAssignableFrom(type)) 36 | { 37 | throw new ArgumentException("Specified type does not support IJob."); 38 | } 39 | 40 | Pattern = null; 41 | Type = type; 42 | Handler = null; 43 | State = state; 44 | } 45 | 46 | public AspNetJobWrapper(string pattern, Func handler, OverlapHandling overlapHandling, object state) 47 | { 48 | Pattern = pattern; 49 | Type = null; 50 | Handler = handler; 51 | OverlapHandling = overlapHandling; 52 | State = state; 53 | } 54 | 55 | #region Properties 56 | 57 | public string Pattern { get; } 58 | public Type Type { get; } 59 | public Func Handler { get; } 60 | public OverlapHandling OverlapHandling { get; } 61 | public object State { get; } 62 | 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading.Tasks; 27 | 28 | using Microsoft.Extensions.DependencyInjection; 29 | using Microsoft.Extensions.Hosting; 30 | 31 | namespace Workshell.Tempus.AspNetCore 32 | { 33 | public static class ServiceCollectionExtensions 34 | { 35 | #region Methods 36 | 37 | public static IServiceCollection AddTempus(this IServiceCollection services, Action configure = null) 38 | { 39 | var options = new TempusOptions(); 40 | 41 | configure?.Invoke(options); 42 | 43 | if (options.Factory == null) 44 | { 45 | services.AddSingleton(); 46 | } 47 | else 48 | { 49 | services.AddSingleton(options.Factory); 50 | } 51 | 52 | if (options.Runner == null) 53 | { 54 | services.AddSingleton(); 55 | } 56 | else 57 | { 58 | services.AddSingleton(options.Runner); 59 | } 60 | 61 | services.AddSingleton(provider => 62 | { 63 | var scheduler = JobScheduler.Create(provider.GetService(), provider.GetService()); 64 | 65 | return scheduler; 66 | }); 67 | 68 | if (options.RegisterBackgroundService) 69 | { 70 | services.AddSingleton(); 71 | } 72 | 73 | return services; 74 | } 75 | 76 | public static IServiceCollection AddTempusJob(this IServiceCollection services, object state = null) where T : class, IJob 77 | { 78 | services.AddScoped(typeof(T)); 79 | services.AddTransient(provider => new AspNetJobWrapper(typeof(T), state)); 80 | 81 | return services; 82 | } 83 | 84 | public static IServiceCollection AddTempusJob(this IServiceCollection services, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow, object state = null) 85 | { 86 | Func funcHandler = (context) => 87 | { 88 | handler(); 89 | 90 | return Task.CompletedTask; 91 | }; 92 | 93 | return AddTempusJob(services, funcHandler, overlapHandling, state); 94 | } 95 | 96 | public static IServiceCollection AddTempusJob(this IServiceCollection services, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 97 | object state = null) 98 | { 99 | Func funcHandler = (context) => 100 | { 101 | handler(context); 102 | 103 | return Task.CompletedTask; 104 | }; 105 | 106 | return AddTempusJob(services, funcHandler, overlapHandling, state); 107 | } 108 | 109 | public static IServiceCollection AddTempusJob(this IServiceCollection services, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 110 | object state = null) 111 | { 112 | Func funcHandler = (context) => handler(); 113 | 114 | return AddTempusJob(services, funcHandler, overlapHandling, state); 115 | } 116 | 117 | public static IServiceCollection AddTempusJob(this IServiceCollection services, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 118 | object state = null) 119 | { 120 | services.AddTransient(provider => new AspNetJobWrapper("@immediately", handler, overlapHandling, state)); 121 | 122 | return services; 123 | } 124 | 125 | public static IServiceCollection AddTempusJob(this IServiceCollection services, DateTime when, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 126 | object state = null) 127 | { 128 | Func funcHandler = (context) => 129 | { 130 | handler(); 131 | 132 | return Task.CompletedTask; 133 | }; 134 | 135 | return AddTempusJob(services, when, funcHandler, overlapHandling, state); 136 | } 137 | 138 | public static IServiceCollection AddTempusJob(this IServiceCollection services, DateTime when, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 139 | object state = null) 140 | { 141 | Func funcHandler = (context) => 142 | { 143 | handler(context); 144 | 145 | return Task.CompletedTask; 146 | }; 147 | 148 | return AddTempusJob(services, when, funcHandler, overlapHandling, state); 149 | } 150 | 151 | public static IServiceCollection AddTempusJob(this IServiceCollection services, DateTime when, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 152 | object state = null) 153 | { 154 | Func funcHandler = (context) => handler(); 155 | 156 | return AddTempusJob(services, when, funcHandler, overlapHandling, state); 157 | } 158 | 159 | public static IServiceCollection AddTempusJob(this IServiceCollection services, DateTime when, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 160 | object state = null) 161 | { 162 | services.AddTransient(provider => new AspNetJobWrapper($"@once {when:O}", handler, overlapHandling, state)); 163 | 164 | return services; 165 | } 166 | 167 | public static IServiceCollection AddTempusJob(this IServiceCollection services, string pattern, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 168 | object state = null) 169 | { 170 | Func funcHandler = (context) => 171 | { 172 | handler(); 173 | 174 | return Task.CompletedTask; 175 | }; 176 | 177 | return AddTempusJob(services, pattern, funcHandler, overlapHandling, state); 178 | } 179 | 180 | public static IServiceCollection AddTempusJob(this IServiceCollection services, string pattern, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 181 | object state = null) 182 | { 183 | Func funcHandler = (context) => 184 | { 185 | handler(context); 186 | 187 | return Task.CompletedTask; 188 | }; 189 | 190 | return AddTempusJob(services, pattern, funcHandler, overlapHandling, state); 191 | } 192 | 193 | public static IServiceCollection AddTempusJob(this IServiceCollection services, string pattern, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 194 | object state = null) 195 | { 196 | Func funcHandler = (context) => handler(); 197 | 198 | return AddTempusJob(services, pattern, funcHandler, overlapHandling, state); 199 | } 200 | 201 | public static IServiceCollection AddTempusJob(this IServiceCollection services, string pattern, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, 202 | object state = null) 203 | { 204 | services.AddTransient(provider => new AspNetJobWrapper(pattern, handler, overlapHandling, state)); 205 | 206 | return services; 207 | } 208 | 209 | #endregion 210 | } 211 | } 212 | 213 | -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System.Reflection; 24 | 25 | [assembly: AssemblyTitle("Workshell.Tempus.AspNetCore")] 26 | [assembly: AssemblyDescription("ASP.NET Core support for Workshell Tempus")] -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/TempusBackgroundService.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading; 27 | using System.Threading.Tasks; 28 | 29 | using Microsoft.Extensions.DependencyInjection; 30 | using Microsoft.Extensions.Hosting; 31 | 32 | namespace Workshell.Tempus.AspNetCore 33 | { 34 | internal sealed class TempusBackgroundService : IHostedService 35 | { 36 | private readonly IJobScheduler _scheduler; 37 | 38 | public TempusBackgroundService(IServiceProvider services, IJobScheduler scheduler) 39 | { 40 | var wrappers = services.GetServices(); 41 | 42 | foreach(var wrapper in wrappers) 43 | { 44 | if (wrapper.Type != null) 45 | { 46 | scheduler.Schedule(wrapper.Type); 47 | } 48 | else 49 | { 50 | scheduler.Schedule(wrapper.Pattern, wrapper.Handler, wrapper.OverlapHandling, wrapper.State); 51 | } 52 | } 53 | 54 | _scheduler = scheduler; 55 | } 56 | 57 | #region Methods 58 | 59 | public Task StartAsync(CancellationToken cancellationToken) 60 | { 61 | _scheduler.Start(); 62 | 63 | return Task.CompletedTask; 64 | } 65 | 66 | public Task StopAsync(CancellationToken cancellationToken) 67 | { 68 | _scheduler.Stop(); 69 | 70 | return Task.CompletedTask; 71 | } 72 | 73 | #endregion 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/TempusOptions.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus.AspNetCore 28 | { 29 | public sealed class TempusOptions 30 | { 31 | public TempusOptions() 32 | { 33 | Factory = null; 34 | Runner = null; 35 | RegisterBackgroundService = true; 36 | } 37 | 38 | #region Properties 39 | 40 | public IJobFactory Factory { get; set; } 41 | public IJobRunner Runner { get; set; } 42 | public bool RegisterBackgroundService { get; set; } 43 | 44 | #endregion 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Workshell.Tempus.AspNetCore/Workshell.Tempus.AspNetCore.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0;net461 5 | false 6 | false 7 | Workshell.snk 8 | Debug;Release;CI 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Workshell.Tempus.AspNetCore 18 | ASP.NET Core support for Workshell Tempus 19 | https://github.com/Workshell/tempus 20 | https://img.workshell.co.uk/logo_128.png 21 | Workshell Tempus Hangfire Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues AspNet MVC AspNetCore 22 | MIT 23 | Workshell Ltd 24 | Workshell Ltd 25 | https://github.com/Workshell/tempus 26 | git 27 | 0.0.0.1 28 | 29 | 30 | 31 | ..\..\bin\debug 32 | TRACE 33 | 34 | 35 | 36 | ..\..\bin\release 37 | TRACE 38 | 39 | 40 | 41 | ..\..\bin\ci 42 | TRACE;SIGNED 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/ActiveJob.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading; 27 | 28 | namespace Workshell.Tempus 29 | { 30 | internal sealed class ActiveJob : IDisposable, IActiveJob 31 | { 32 | private static long _executionId; 33 | 34 | private readonly CancellationTokenSource _cts; 35 | private volatile bool _disposed; 36 | 37 | static ActiveJob() 38 | { 39 | _executionId = 0; 40 | } 41 | 42 | internal ActiveJob(ScheduledJob job, CancellationToken cancellationToken) 43 | { 44 | _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); 45 | _disposed = false; 46 | 47 | ExecutionId = Interlocked.Increment(ref _executionId); 48 | Job = job; 49 | Started = DateTime.UtcNow; 50 | } 51 | 52 | #region Methods 53 | 54 | public void Dispose() 55 | { 56 | if (_disposed) 57 | { 58 | return; 59 | } 60 | 61 | Cancel(); 62 | _cts.Dispose(); 63 | GC.SuppressFinalize(this); 64 | 65 | _disposed = true; 66 | } 67 | 68 | public void Cancel() 69 | { 70 | _cts.Cancel(); 71 | } 72 | 73 | #endregion 74 | 75 | #region Properties 76 | 77 | public long ExecutionId { get; } 78 | public IScheduledJob Job { get; } 79 | public DateTime Started { get; } 80 | public TimeSpan Runtime => (DateTime.UtcNow - Started); 81 | public CancellationToken Token => _cts.Token; 82 | 83 | #endregion 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/ActiveJobOrder.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public enum ActiveJobOrder 30 | { 31 | StartedOldest, 32 | StartedYoungest, 33 | RuntimeLongest, 34 | RuntimeShortest 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/ActiveJobs.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Linq; 26 | using System.Text; 27 | using System.Threading; 28 | 29 | namespace Workshell.Tempus 30 | { 31 | internal sealed class ActiveJobs : IActiveJobs 32 | { 33 | private readonly ReaderWriterLockSlim _locker; 34 | private readonly HashSet _jobs; 35 | 36 | internal ActiveJobs() 37 | { 38 | _locker = new ReaderWriterLockSlim(); 39 | _jobs = new HashSet(); 40 | } 41 | 42 | #region Methods 43 | 44 | public bool Add(ActiveJob job) 45 | { 46 | _locker.EnterWriteLock(); 47 | 48 | try 49 | { 50 | return _jobs.Add(job); 51 | } 52 | finally 53 | { 54 | _locker.ExitWriteLock(); 55 | } 56 | } 57 | 58 | public bool Remove(long executionId) 59 | { 60 | _locker.EnterUpgradeableReadLock(); 61 | 62 | try 63 | { 64 | foreach (var job in _jobs) 65 | { 66 | if (job.ExecutionId == executionId) 67 | { 68 | _locker.EnterWriteLock(); 69 | 70 | try 71 | { 72 | _jobs.Remove(job); 73 | 74 | return true; 75 | } 76 | finally 77 | { 78 | _locker.ExitWriteLock(); 79 | } 80 | } 81 | } 82 | } 83 | finally 84 | { 85 | _locker.ExitUpgradeableReadLock(); 86 | } 87 | 88 | return false; 89 | } 90 | 91 | public bool Remove(IActiveJob job) 92 | { 93 | return Remove(job?.ExecutionId ?? -1); 94 | } 95 | 96 | public bool Contains(IScheduledJob job) 97 | { 98 | _locker.EnterReadLock(); 99 | 100 | try 101 | { 102 | foreach (var activeJob in _jobs) 103 | { 104 | if (activeJob.Job == job || activeJob.Job.Id == job.Id) 105 | { 106 | return true; 107 | } 108 | } 109 | 110 | return false; 111 | } 112 | finally 113 | { 114 | _locker.ExitReadLock(); 115 | } 116 | } 117 | 118 | public IActiveJob[] ToArray(ActiveJobOrder order = ActiveJobOrder.StartedOldest) 119 | { 120 | _locker.EnterReadLock(); 121 | 122 | try 123 | { 124 | IEnumerable results; 125 | 126 | switch (order) 127 | { 128 | case ActiveJobOrder.StartedOldest: 129 | results = _jobs.OrderBy(_ => _.Started); 130 | break; 131 | case ActiveJobOrder.StartedYoungest: 132 | results = _jobs.OrderByDescending(_ => _.Started); 133 | break; 134 | case ActiveJobOrder.RuntimeLongest: 135 | results = _jobs.OrderByDescending(_ => _.Runtime); 136 | break; 137 | case ActiveJobOrder.RuntimeShortest: 138 | results = _jobs.OrderBy(_ => _.Runtime); 139 | break; 140 | default: 141 | throw new ArgumentOutOfRangeException(nameof(order), order, null); 142 | } 143 | 144 | return results.Cast().ToArray(); 145 | } 146 | finally 147 | { 148 | _locker.ExitReadLock(); 149 | } 150 | } 151 | 152 | #endregion 153 | 154 | #region Properties 155 | 156 | public int Count 157 | { 158 | get 159 | { 160 | _locker.EnterReadLock(); 161 | 162 | try 163 | { 164 | return _jobs.Count; 165 | } 166 | finally 167 | { 168 | _locker.ExitReadLock(); 169 | } 170 | } 171 | } 172 | 173 | public IActiveJob this[long executionId] 174 | { 175 | get 176 | { 177 | _locker.EnterReadLock(); 178 | 179 | try 180 | { 181 | var job = _jobs.FirstOrDefault(_ => _.ExecutionId == executionId); 182 | 183 | return job; 184 | } 185 | finally 186 | { 187 | _locker.ExitReadLock(); 188 | } 189 | } 190 | } 191 | 192 | #endregion 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Attributes/CronAttribute.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | [AttributeUsage(AttributeTargets.Class)] 30 | public sealed class CronAttribute : Attribute 31 | { 32 | public CronAttribute(string pattern) 33 | { 34 | Pattern = pattern; 35 | } 36 | 37 | #region Properties 38 | 39 | public string Pattern { get; } 40 | 41 | #endregion 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Attributes/NoOverlapAttribute.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | [AttributeUsage(AttributeTargets.Class)] 30 | public sealed class NoOverlapAttribute : Attribute 31 | { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Attributes/OnceAttribute.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | [AttributeUsage(AttributeTargets.Class)] 30 | public sealed class OnceAttribute : Attribute 31 | { 32 | public OnceAttribute(string when) 33 | { 34 | When = DateTime.Parse(when); 35 | } 36 | 37 | #region Properties 38 | 39 | public DateTime When { get; } 40 | 41 | #endregion 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Attributes/OverlapAttribute.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | [AttributeUsage(AttributeTargets.Class)] 30 | public sealed class OverlapAttribute : Attribute 31 | { 32 | public OverlapAttribute(OverlapHandling handling) 33 | { 34 | Handling = handling; 35 | } 36 | 37 | #region Properties 38 | 39 | public OverlapHandling Handling { get; } 40 | 41 | #endregion 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Events/JobErrorEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Workshell.Tempus 6 | { 7 | public class JobErrorEventArgs : EventArgs 8 | { 9 | internal JobErrorEventArgs(JobExecutionContext context, Exception exception) 10 | { 11 | Context = context; 12 | Exception = exception; 13 | Rethrow = false; 14 | } 15 | 16 | #region Properties 17 | 18 | public JobExecutionContext Context { get; } 19 | public Exception Exception { get; } 20 | public bool Rethrow { get; set; } 21 | 22 | #endregion 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Events/JobEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Workshell.Tempus 6 | { 7 | public class JobEventArgs : EventArgs 8 | { 9 | internal JobEventArgs(IScheduledJob job) 10 | { 11 | Job = job; 12 | } 13 | 14 | #region Properties 15 | 16 | public IScheduledJob Job { get; } 17 | 18 | #endregion 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Events/JobStartedEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Workshell.Tempus 6 | { 7 | public class JobStartedEventArgs : EventArgs 8 | { 9 | internal JobStartedEventArgs(JobExecutionContext context) 10 | { 11 | Context = context; 12 | } 13 | 14 | #region Properties 15 | 16 | public JobExecutionContext Context { get; } 17 | 18 | #endregion 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Events/JobStartingEventArgs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Workshell.Tempus 6 | { 7 | public class JobStartingEventArgs : JobEventArgs 8 | { 9 | internal JobStartingEventArgs(IScheduledJob job) : base(job) 10 | { 11 | Cancel = false; 12 | } 13 | 14 | #region Properties 15 | 16 | public bool Cancel { get; set; } 17 | 18 | #endregion 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Extensions/DisposableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Workshell.Tempus 6 | { 7 | internal static class DisposableExtensions 8 | { 9 | #region Methods 10 | 11 | public static void Dispose(this IDisposable disposable, Action action) 12 | { 13 | disposable.Dispose(); 14 | action?.Invoke(); 15 | } 16 | 17 | #endregion 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Extensions/JobSchedulerExtensions.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading.Tasks; 27 | 28 | namespace Workshell.Tempus 29 | { 30 | public static class JobSchedulerExtensions 31 | { 32 | #region Methods 33 | 34 | public static Guid Schedule(this IJobScheduler scheduler) where T : IJob 35 | { 36 | return scheduler.Schedule(typeof(T)); 37 | } 38 | 39 | public static Guid Schedule(this IJobScheduler scheduler, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 40 | { 41 | return scheduler.Schedule((context) => 42 | { 43 | handler(); 44 | 45 | return Task.FromResult(0); 46 | }, overlapHandling); 47 | } 48 | 49 | public static Guid Schedule(this IJobScheduler scheduler, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 50 | { 51 | return scheduler.Schedule((context) => 52 | { 53 | handler(context); 54 | 55 | return Task.FromResult(0); 56 | }, overlapHandling); 57 | } 58 | 59 | public static Guid Schedule(this IJobScheduler scheduler, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 60 | { 61 | return scheduler.Schedule(async (context) => 62 | { 63 | await handler(); 64 | }, overlapHandling); 65 | } 66 | 67 | public static Guid Schedule(this IJobScheduler scheduler, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 68 | { 69 | return scheduler.Schedule("@immediately", async (context) => 70 | { 71 | await handler(context); 72 | }, overlapHandling); 73 | } 74 | 75 | public static Guid Schedule(this IJobScheduler scheduler, DateTime when, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 76 | { 77 | return scheduler.Schedule(when, (context) => 78 | { 79 | handler(); 80 | 81 | return Task.FromResult(0); 82 | }, overlapHandling); 83 | } 84 | 85 | public static Guid Schedule(this IJobScheduler scheduler, DateTime when, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 86 | { 87 | return scheduler.Schedule(when, (context) => 88 | { 89 | handler(context); 90 | 91 | return Task.FromResult(0); 92 | }, overlapHandling); 93 | } 94 | 95 | public static Guid Schedule(this IJobScheduler scheduler, DateTime when, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 96 | { 97 | return scheduler.Schedule(when, async (context) => 98 | { 99 | await handler(); 100 | }, overlapHandling); 101 | } 102 | 103 | public static Guid Schedule(this IJobScheduler scheduler, DateTime when, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 104 | { 105 | return scheduler.Schedule($"@once {when:O}", async (context) => 106 | { 107 | await handler(context); 108 | }, overlapHandling); 109 | } 110 | 111 | public static Guid Schedule(this IJobScheduler scheduler, string pattern, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 112 | { 113 | return scheduler.Schedule(pattern, (context) => 114 | { 115 | handler(); 116 | 117 | return Task.FromResult(0); 118 | }, overlapHandling); 119 | } 120 | 121 | public static Guid Schedule(this IJobScheduler scheduler, string pattern, Action handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 122 | { 123 | return scheduler.Schedule(pattern, (context) => 124 | { 125 | handler(context); 126 | 127 | return Task.FromResult(0); 128 | }, overlapHandling); 129 | } 130 | 131 | public static Guid Schedule(this IJobScheduler scheduler, string pattern, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 132 | { 133 | return scheduler.Schedule(pattern, async (context) => 134 | { 135 | await handler(); 136 | }, overlapHandling); 137 | } 138 | 139 | public static Guid Schedule(this IJobScheduler scheduler, string pattern, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow) 140 | { 141 | return scheduler.Schedule(pattern, async (context) => 142 | { 143 | await handler(context); 144 | }, overlapHandling); 145 | } 146 | 147 | #endregion 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IActiveJob.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading; 27 | 28 | namespace Workshell.Tempus 29 | { 30 | public interface IActiveJob 31 | { 32 | #region Methods 33 | 34 | void Cancel(); 35 | 36 | #endregion 37 | 38 | #region Properties 39 | 40 | long ExecutionId { get; } 41 | IScheduledJob Job { get; } 42 | DateTime Started { get; } 43 | TimeSpan Runtime { get; } 44 | CancellationToken Token { get; } 45 | 46 | #endregion 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IActiveJobs.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public interface IActiveJobs 30 | { 31 | #region Methods 32 | 33 | bool Contains(IScheduledJob job); 34 | IActiveJob[] ToArray(ActiveJobOrder order = ActiveJobOrder.StartedOldest); 35 | 36 | #endregion 37 | 38 | #region Properties 39 | 40 | int Count { get; } 41 | IActiveJob this[long executionId] { get; } 42 | 43 | #endregion 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IJob.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading.Tasks; 27 | 28 | namespace Workshell.Tempus 29 | { 30 | public interface IJob 31 | { 32 | #region Methods 33 | 34 | Task ExecuteAsync(JobExecutionContext context); 35 | 36 | #endregion 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IJobFactory.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public interface IJobFactory 30 | { 31 | #region Methods 32 | 33 | IJobScope CreateScope(); 34 | 35 | #endregion 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IJobRunner.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading; 27 | using System.Threading.Tasks; 28 | 29 | namespace Workshell.Tempus 30 | { 31 | public interface IJobRunner 32 | { 33 | #region Methods 34 | 35 | Task Run(Action action); 36 | Task Run(Action action, CancellationToken cancellationToken); 37 | Task Run(Func func); 38 | Task Run(Func func, CancellationToken cancellationToken); 39 | 40 | #endregion 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IJobScheduler.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading.Tasks; 27 | 28 | namespace Workshell.Tempus 29 | { 30 | public delegate void JobStartingEventHandler(object sender, JobStartingEventArgs e); 31 | public delegate void JobStartedEventHandler(object sender, JobStartedEventArgs e); 32 | public delegate void JobEventHandler(object sender, JobEventArgs e); 33 | public delegate void JobErrorEventHandler(object sender, JobErrorEventArgs e); 34 | 35 | public interface IJobScheduler : IDisposable 36 | { 37 | #region Methods 38 | 39 | void Start(); 40 | void Stop(bool wait = false); 41 | Guid Schedule(Type type, object state = null); 42 | Guid Schedule(string pattern, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, object state = null); 43 | bool Unschedule(Guid id); 44 | 45 | #endregion 46 | 47 | #region Properties 48 | 49 | IJobFactory Factory { get; set; } 50 | IJobRunner Runner { get; set; } 51 | IScheduledJobs ScheduledJobs { get; } 52 | IActiveJobs ActiveJobs { get; } 53 | 54 | #endregion 55 | 56 | #region Events 57 | 58 | event EventHandler Starting; 59 | event EventHandler Started; 60 | event EventHandler Stopping; 61 | event EventHandler Stopped; 62 | event JobStartingEventHandler JobStarting; 63 | event JobStartedEventHandler JobStarted; 64 | event JobEventHandler JobFinished; 65 | event JobErrorEventHandler JobError; 66 | 67 | #endregion 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IJobScope.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public interface IJobScope : IDisposable 30 | { 31 | #region Methods 32 | 33 | IJob Create(Type type); 34 | IJob Create() where T : IJob; 35 | 36 | #endregion 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IScheduledJob.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public interface IScheduledJob 30 | { 31 | #region Properties 32 | 33 | Guid Id { get; } 34 | string Name { get; } 35 | string Pattern { get; } 36 | bool IsImmediately { get; } 37 | bool IsOnce { get; } 38 | bool IsAnonymous { get; } 39 | OverlapHandling OverlapHandling { get; } 40 | object State { get; set; } 41 | 42 | #endregion 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Interfaces/IScheduledJobs.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public interface IScheduledJobs 30 | { 31 | #region Methods 32 | 33 | IScheduledJob[] ToArray(); 34 | 35 | #endregion 36 | 37 | #region Properties 38 | 39 | int Count { get; } 40 | IScheduledJob this[Guid id] { get; } 41 | 42 | #endregion 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/JobExecutionContext.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading; 27 | 28 | namespace Workshell.Tempus 29 | { 30 | public sealed class JobExecutionContext 31 | { 32 | private readonly CancellationToken _cancellationToken; 33 | 34 | internal JobExecutionContext(IJobScheduler scheduler, CancellationToken cancellationToken) 35 | { 36 | _cancellationToken = cancellationToken; 37 | 38 | Scheduler = scheduler; 39 | } 40 | 41 | #region Properties 42 | 43 | public IJobScheduler Scheduler { get; } 44 | public bool IsCancellationRequested => _cancellationToken.IsCancellationRequested; 45 | 46 | #endregion 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/JobFactory.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public class JobFactory : IJobFactory 30 | { 31 | #region Methods 32 | 33 | public virtual IJobScope CreateScope() 34 | { 35 | return new JobScope(); 36 | } 37 | 38 | #endregion 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/JobRunner.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | using System.Threading; 27 | using System.Threading.Tasks; 28 | 29 | namespace Workshell.Tempus 30 | { 31 | public class JobRunner : IJobRunner 32 | { 33 | public Task Run(Action action) 34 | { 35 | return Run(action, CancellationToken.None); 36 | } 37 | 38 | public virtual Task Run(Action action, CancellationToken cancellationToken) 39 | { 40 | return Task.Run(action, cancellationToken); 41 | } 42 | 43 | public Task Run(Func func) 44 | { 45 | return Run(func, CancellationToken.None); 46 | } 47 | 48 | public virtual Task Run(Func func, CancellationToken cancellationToken) 49 | { 50 | return Task.Run(func, cancellationToken); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/JobScheduler.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Reflection; 26 | using System.Text; 27 | using System.Threading; 28 | using System.Threading.Tasks; 29 | 30 | namespace Workshell.Tempus 31 | { 32 | public sealed class JobScheduler : IJobScheduler 33 | { 34 | private static readonly object _locker; 35 | private static IJobScheduler _scheduler; 36 | 37 | private readonly ScheduledJobs _jobs; 38 | private readonly ActiveJobs _activeJobs; 39 | private volatile bool _disposed; 40 | private CancellationTokenSource _cts; 41 | private Timer _timer; 42 | private IJobFactory _factory; 43 | private IJobRunner _runner; 44 | 45 | static JobScheduler() 46 | { 47 | _locker = new object(); 48 | _scheduler = null; 49 | } 50 | 51 | private JobScheduler(IJobFactory factory, IJobRunner runner) 52 | { 53 | _jobs = new ScheduledJobs(); 54 | _activeJobs = new ActiveJobs(); 55 | _disposed = false; 56 | _cts = null; 57 | _timer = null; 58 | _factory = factory; 59 | _runner = runner; 60 | } 61 | 62 | #region Static Methods 63 | 64 | public static IJobScheduler Create(IJobFactory factory = null, IJobRunner runner = null) 65 | { 66 | lock (_locker) 67 | { 68 | if (_scheduler == null) 69 | { 70 | _scheduler = new JobScheduler(factory ?? new JobFactory(), runner ?? new JobRunner()); 71 | } 72 | 73 | return _scheduler; 74 | } 75 | } 76 | 77 | #endregion 78 | 79 | #region Methods 80 | 81 | public void Dispose() 82 | { 83 | if (_disposed) 84 | { 85 | return; 86 | } 87 | 88 | Stop(); 89 | 90 | _disposed = true; 91 | } 92 | 93 | public void Start() 94 | { 95 | OnStarting(); 96 | 97 | if (Volatile.Read(ref _cts) != null) 98 | { 99 | return; 100 | } 101 | 102 | lock (_locker) 103 | { 104 | _cts = new CancellationTokenSource(); 105 | _timer = new Timer(TimerElapsed, null, 0, 1000); 106 | } 107 | 108 | OnStarted(); 109 | } 110 | 111 | public void Stop(bool wait = false) 112 | { 113 | OnStopping(); 114 | 115 | if (Volatile.Read(ref _cts) == null) 116 | { 117 | return; 118 | } 119 | 120 | lock (_locker) 121 | { 122 | _timer.Dispose(() => _timer = null); 123 | _cts.Dispose(() => _cts = null); 124 | } 125 | 126 | if (wait) 127 | { 128 | while (_activeJobs.Count > 0) 129 | { 130 | Thread.Sleep(100); 131 | } 132 | } 133 | 134 | OnStopped(); 135 | } 136 | 137 | public Guid Schedule(Type type, object state) 138 | { 139 | if (!Utils.SupportsJob(type)) 140 | { 141 | throw new ArgumentException("Type is not an IJob.", nameof(type)); 142 | } 143 | 144 | var job = _jobs.Add(type, state); 145 | 146 | return job.Id; 147 | } 148 | 149 | public Guid Schedule(string pattern, Func handler, OverlapHandling overlapHandling = OverlapHandling.Allow, object state = null) 150 | { 151 | if (handler == null) 152 | { 153 | throw new ArgumentNullException(nameof(handler)); 154 | } 155 | 156 | var job = _jobs.Add(pattern, handler, overlapHandling, state); 157 | 158 | return job.Id; 159 | } 160 | 161 | public bool Unschedule(Guid id) 162 | { 163 | return _jobs.Remove(id); 164 | } 165 | 166 | private void TimerElapsed(object state) 167 | { 168 | var factory = Volatile.Read(ref _factory); 169 | var runner = Volatile.Read(ref _runner); 170 | var jobs = _jobs.Next(DateTime.UtcNow); 171 | 172 | foreach (var job in jobs) 173 | { 174 | ExecuteJob(factory, runner, job); 175 | } 176 | } 177 | 178 | private void ExecuteJob(IJobFactory factory, IJobRunner runner, ScheduledJob job) 179 | { 180 | if (!OnJobStarting(job)) 181 | { 182 | return; 183 | } 184 | 185 | runner.Run(async () => 186 | { 187 | if (job.OverlapHandling == OverlapHandling.Skip && _activeJobs.Contains(job)) 188 | { 189 | return; 190 | } 191 | 192 | Lock(job); 193 | 194 | var activeJob = new ActiveJob(job, _cts.Token); 195 | 196 | _activeJobs.Add(activeJob); 197 | 198 | try 199 | { 200 | var context = new JobExecutionContext(this, activeJob.Token); 201 | 202 | OnJobStarted(context); 203 | 204 | try 205 | { 206 | if (!job.IsAnonymous) 207 | { 208 | using (var scope = factory.CreateScope()) 209 | { 210 | var instance = scope.Create(job.Type); 211 | 212 | await instance.ExecuteAsync(context); 213 | 214 | var supportsDispose = typeof(IDisposable).IsAssignableFrom(instance.GetType()); 215 | 216 | if (supportsDispose) 217 | { 218 | var disposable = (IDisposable)instance; 219 | 220 | disposable.Dispose(); 221 | } 222 | } 223 | } 224 | else 225 | { 226 | await job.Handler(context); 227 | } 228 | } 229 | catch (Exception ex) 230 | { 231 | if (OnJobError(context, ex)) 232 | { 233 | throw; 234 | } 235 | } 236 | 237 | OnJobFinished(job); 238 | } 239 | finally 240 | { 241 | _activeJobs.Remove(activeJob); 242 | Unlock(job); 243 | } 244 | }); 245 | } 246 | 247 | private void Lock(ScheduledJob job) 248 | { 249 | if (job.OverlapHandling != OverlapHandling.Wait) 250 | { 251 | return; 252 | } 253 | 254 | Monitor.Enter(job); 255 | } 256 | 257 | private void Unlock(ScheduledJob job) 258 | { 259 | if (job.OverlapHandling != OverlapHandling.Wait) 260 | { 261 | return; 262 | } 263 | 264 | Monitor.Exit(job); 265 | } 266 | 267 | private void OnStarting() 268 | { 269 | var handler = Volatile.Read(ref Starting); 270 | 271 | handler?.Invoke(this, EventArgs.Empty); 272 | } 273 | 274 | private void OnStarted() 275 | { 276 | var handler = Volatile.Read(ref Started); 277 | 278 | handler?.Invoke(this, EventArgs.Empty); 279 | } 280 | 281 | private void OnStopping() 282 | { 283 | var handler = Volatile.Read(ref Stopping); 284 | 285 | handler?.Invoke(this, EventArgs.Empty); 286 | } 287 | 288 | private void OnStopped() 289 | { 290 | var handler = Volatile.Read(ref Stopped); 291 | 292 | handler?.Invoke(this, EventArgs.Empty); 293 | } 294 | 295 | private bool OnJobStarting(IScheduledJob job) 296 | { 297 | var startingHandler = Volatile.Read(ref JobStarting); 298 | 299 | if (startingHandler != null) 300 | { 301 | var startingArgs = new JobStartingEventArgs(job); 302 | 303 | startingHandler.Invoke(this, startingArgs); 304 | 305 | if (startingArgs.Cancel) 306 | { 307 | return false; 308 | } 309 | } 310 | 311 | return true; 312 | } 313 | 314 | private void OnJobStarted(JobExecutionContext context) 315 | { 316 | var startedHandler = Volatile.Read(ref JobStarted); 317 | 318 | startedHandler?.Invoke(this, new JobStartedEventArgs(context)); 319 | } 320 | 321 | private void OnJobFinished(IScheduledJob job) 322 | { 323 | var finishedHandler = Volatile.Read(ref JobFinished); 324 | 325 | finishedHandler?.Invoke(this, new JobEventArgs(job)); 326 | } 327 | 328 | private bool OnJobError(JobExecutionContext context, Exception ex) 329 | { 330 | var errorHandler = Volatile.Read(ref JobError); 331 | 332 | if (errorHandler == null) 333 | { 334 | return false; 335 | } 336 | 337 | var errorArgs = new JobErrorEventArgs(context, ex); 338 | 339 | errorHandler.Invoke(this, errorArgs); 340 | 341 | if (errorArgs.Rethrow) 342 | { 343 | return false; 344 | } 345 | 346 | return true; 347 | } 348 | 349 | #endregion 350 | 351 | #region Properties 352 | 353 | public IJobFactory Factory 354 | { 355 | get => Volatile.Read(ref _factory); 356 | set 357 | { 358 | var factory = value ?? new JobFactory(); 359 | 360 | Volatile.Write(ref _factory, factory); 361 | } 362 | } 363 | 364 | public IJobRunner Runner 365 | { 366 | get => Volatile.Read(ref _runner); 367 | set 368 | { 369 | var runner = value ?? new JobRunner(); 370 | 371 | Volatile.Write(ref _runner, runner); 372 | } 373 | } 374 | 375 | public IScheduledJobs ScheduledJobs => _jobs; 376 | public IActiveJobs ActiveJobs => _activeJobs; 377 | 378 | #endregion 379 | 380 | #region Events 381 | 382 | public event EventHandler Starting; 383 | public event EventHandler Started; 384 | public event EventHandler Stopping; 385 | public event EventHandler Stopped; 386 | public event JobStartingEventHandler JobStarting; 387 | public event JobStartedEventHandler JobStarted; 388 | public event JobEventHandler JobFinished; 389 | public event JobErrorEventHandler JobError; 390 | 391 | #endregion 392 | } 393 | } -------------------------------------------------------------------------------- /src/Workshell.Tempus/JobScope.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Reflection; 26 | using System.Text; 27 | 28 | namespace Workshell.Tempus 29 | { 30 | public class JobScope : IJobScope 31 | { 32 | #region Methods 33 | 34 | public virtual void Dispose() 35 | { 36 | } 37 | 38 | public virtual IJob Create(Type type) 39 | { 40 | if (!(typeof(IJob).GetTypeInfo().IsAssignableFrom(type))) 41 | { 42 | throw new Exception("Type does not implement IJob interface."); 43 | } 44 | 45 | return (IJob)Activator.CreateInstance(type); 46 | } 47 | 48 | public IJob Create() where T : IJob 49 | { 50 | return Create(typeof(T)); 51 | } 52 | 53 | #endregion 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/OverlapHandling.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Workshell.Tempus 6 | { 7 | public enum OverlapHandling 8 | { 9 | Allow, 10 | Wait, 11 | Skip 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System.Reflection; 24 | using System.Runtime.CompilerServices; 25 | 26 | [assembly: AssemblyTitle("Workshell.Tempus")] 27 | [assembly: AssemblyDescription("Tempus - A .NET Job Scheduler")] 28 | 29 | #if !SIGNED 30 | [assembly: InternalsVisibleTo("Workshell.Tempus.AspNetCore")] 31 | [assembly: InternalsVisibleTo("Workshell.Tempus.Tests")] 32 | #else 33 | [assembly: InternalsVisibleTo("Workshell.Tempus.AspNetCore, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed0db04e6ef7cb7ae6c0dbecb36b42bc629609ae4f059d5aacd1f467be55281c480336bfd79e4c28a5304ccb6448502b5c4f5184dcf76f264ea7d2f78f6e7ab134ca12d526e2257b2ee88b8429dc7ace03ad9c21b6b2710ca2b82e770e62683382924c50f7e554402e838dd4fd90bfcc2ec730ef2cb9c9c9b9992061d37ed789")] 34 | [assembly: InternalsVisibleTo("Workshell.Tempus.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed0db04e6ef7cb7ae6c0dbecb36b42bc629609ae4f059d5aacd1f467be55281c480336bfd79e4c28a5304ccb6448502b5c4f5184dcf76f264ea7d2f78f6e7ab134ca12d526e2257b2ee88b8429dc7ace03ad9c21b6b2710ca2b82e770e62683382924c50f7e554402e838dd4fd90bfcc2ec730ef2cb9c9c9b9992061d37ed789")] 35 | #endif -------------------------------------------------------------------------------- /src/Workshell.Tempus/ScheduledJob.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Linq; 26 | using System.Reflection; 27 | using System.Text; 28 | using System.Threading.Tasks; 29 | 30 | using Cronos; 31 | 32 | namespace Workshell.Tempus 33 | { 34 | internal sealed class ScheduledJob : IScheduledJob 35 | { 36 | private readonly CronExpression _cron; 37 | private DateTime? _next; 38 | 39 | public ScheduledJob(Type type, object state) 40 | { 41 | if (!Utils.SupportsJob(type)) 42 | { 43 | throw new ArgumentException("Type does not support IJob.", nameof(type)); 44 | } 45 | 46 | var pattern = GetPattern(type); 47 | 48 | if (pattern == "@immediately") 49 | { 50 | _cron = null; 51 | _next = DateTime.UtcNow; 52 | } 53 | else if (pattern.StartsWith("@once ")) 54 | { 55 | var value = pattern.Remove(0, 6); 56 | 57 | _cron = null; 58 | _next = DateTime.Parse(value); 59 | } 60 | else 61 | { 62 | _cron = CronExpression.Parse(pattern, CronFormat.IncludeSeconds); 63 | _next = _cron.GetNextOccurrence(DateTime.UtcNow); 64 | } 65 | 66 | Id = Guid.NewGuid(); 67 | Pattern = pattern; 68 | Type = type; 69 | Handler = null; 70 | IsImmediately = (pattern == "@immediately"); 71 | IsOnce = pattern.StartsWith("@once "); 72 | IsAnonymous = false; 73 | OverlapHandling = GetOverlapHandling(type); 74 | State = state; 75 | } 76 | 77 | public ScheduledJob(string pattern, Func handler, OverlapHandling overlapHandling, object state) 78 | { 79 | if (string.IsNullOrWhiteSpace(pattern)) 80 | { 81 | throw new ArgumentException("No pattern was specified.", nameof(pattern)); 82 | } 83 | 84 | if (handler == null) 85 | { 86 | throw new ArgumentNullException(nameof(handler), "No handler was specified."); 87 | } 88 | 89 | if (pattern == "@immediately") 90 | { 91 | _cron = null; 92 | _next = DateTime.UtcNow; 93 | } 94 | else if (pattern.StartsWith("@once ")) 95 | { 96 | var value = pattern.Remove(0, 6); 97 | 98 | _cron = null; 99 | _next = DateTime.Parse(value); 100 | } 101 | else 102 | { 103 | _cron = CronExpression.Parse(pattern, CronFormat.IncludeSeconds); 104 | _next = _cron.GetNextOccurrence(DateTime.UtcNow); 105 | } 106 | 107 | Id = Guid.NewGuid(); 108 | Pattern = pattern; 109 | Type = null; 110 | Handler = handler; 111 | IsImmediately = (pattern == "@immediately"); 112 | IsOnce = pattern.StartsWith("@once "); 113 | IsAnonymous = true; 114 | OverlapHandling = overlapHandling; 115 | State = state; 116 | } 117 | 118 | #region Methods 119 | 120 | public bool? NeedsExecuting(DateTime current) 121 | { 122 | if (_next == null) 123 | { 124 | return null; 125 | } 126 | 127 | if (_next <= current) 128 | { 129 | _next = _cron?.GetNextOccurrence(_next.Value); 130 | 131 | return true; 132 | } 133 | 134 | return false; 135 | } 136 | 137 | public override string ToString() 138 | { 139 | var results = new List(); 140 | 141 | results.Add($"Id: {Id:D}"); 142 | 143 | if (IsAnonymous) 144 | { 145 | results.Add("Type: Anonymous"); 146 | } 147 | else 148 | { 149 | results.Add($"Type: {Type.FullName}"); 150 | } 151 | 152 | if (IsImmediately) 153 | { 154 | results.Add("When: Immediately"); 155 | } 156 | else if (IsOnce) 157 | { 158 | results.Add($"When: Once ({_next})"); 159 | } 160 | else 161 | { 162 | results.Add($"When: {Pattern} ({_next})"); 163 | } 164 | 165 | return string.Join("; ", results); 166 | } 167 | 168 | private OverlapHandling GetOverlapHandling(Type type) 169 | { 170 | if (type == null) 171 | { 172 | return OverlapHandling.Allow; 173 | } 174 | 175 | var attributes = type.GetTypeInfo().GetCustomAttributes(); 176 | var attribute = (OverlapAttribute)attributes.FirstOrDefault(_ => _ is OverlapAttribute); 177 | 178 | if (attribute == null) 179 | { 180 | return OverlapHandling.Allow; 181 | } 182 | 183 | return attribute.Handling; 184 | } 185 | 186 | private string GetPattern(Type type) 187 | { 188 | if (type == null) 189 | { 190 | return string.Empty; 191 | } 192 | 193 | var attributes = type.GetTypeInfo().GetCustomAttributes(); 194 | 195 | var cronAttr = (CronAttribute)attributes.FirstOrDefault(_ => _ is CronAttribute); 196 | var onceAttr = (OnceAttribute)attributes.FirstOrDefault(_ => _ is OnceAttribute); 197 | 198 | if (cronAttr == null && onceAttr == null) 199 | { 200 | return "@immediately"; 201 | } 202 | 203 | if (cronAttr != null && onceAttr != null) 204 | { 205 | throw new TempusException("Cannot have both a cron and a once attribute, they're mutually exclusive."); 206 | } 207 | 208 | if (onceAttr != null) 209 | { 210 | return $"@once {onceAttr.When:O}"; 211 | } 212 | 213 | return cronAttr.Pattern; 214 | } 215 | 216 | #endregion 217 | 218 | #region Properties 219 | 220 | public Type Type { get; } 221 | public Func Handler { get; } 222 | 223 | public Guid Id { get; } 224 | public string Name => (IsAnonymous ? "Anonymous" : Type.FullName); 225 | public string Pattern { get; } 226 | public bool IsImmediately { get; } 227 | public bool IsOnce { get; } 228 | public bool IsAnonymous { get; } 229 | public OverlapHandling OverlapHandling { get; } 230 | public object State { get; set; } 231 | 232 | #endregion 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/ScheduledJobs.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Linq; 26 | using System.Text; 27 | using System.Threading; 28 | using System.Threading.Tasks; 29 | 30 | namespace Workshell.Tempus 31 | { 32 | internal sealed class ScheduledJobs : IScheduledJobs 33 | { 34 | private readonly ReaderWriterLockSlim _locker; 35 | private readonly List _jobs; 36 | 37 | public ScheduledJobs() 38 | { 39 | _locker = new ReaderWriterLockSlim(); 40 | _jobs = new List(); 41 | } 42 | 43 | #region Methods 44 | 45 | public IScheduledJob Add(Type type, object state) 46 | { 47 | _locker.EnterUpgradeableReadLock(); 48 | 49 | try 50 | { 51 | var existingJob = _jobs.Any(_ => _.Type == type); 52 | 53 | if (existingJob) 54 | { 55 | throw new TempusException("The job is already scheduled."); 56 | } 57 | 58 | _locker.EnterWriteLock(); 59 | 60 | try 61 | { 62 | var job = new ScheduledJob(type, state); 63 | 64 | _jobs.Add(job); 65 | 66 | return job; 67 | } 68 | finally 69 | { 70 | _locker.ExitWriteLock(); 71 | } 72 | } 73 | finally 74 | { 75 | _locker.ExitUpgradeableReadLock(); 76 | } 77 | } 78 | 79 | public IScheduledJob Add(string pattern, Func handler, OverlapHandling overlapHandling, object state) 80 | { 81 | var job = new ScheduledJob(pattern, handler, overlapHandling, state); 82 | 83 | _locker.EnterWriteLock(); 84 | 85 | try 86 | { 87 | _jobs.Add(job); 88 | } 89 | finally 90 | { 91 | _locker.ExitWriteLock(); 92 | } 93 | 94 | return job; 95 | } 96 | 97 | public bool Remove(Guid id) 98 | { 99 | _locker.EnterUpgradeableReadLock(); 100 | 101 | try 102 | { 103 | var job = _jobs.FirstOrDefault(_ => _.Id == id); 104 | 105 | if (job != null) 106 | { 107 | _locker.EnterWriteLock(); 108 | 109 | try 110 | { 111 | return _jobs.Remove(job); 112 | } 113 | finally 114 | { 115 | _locker.ExitWriteLock(); 116 | } 117 | } 118 | 119 | return false; 120 | } 121 | finally 122 | { 123 | _locker.ExitUpgradeableReadLock(); 124 | } 125 | } 126 | 127 | public IScheduledJob[] ToArray() 128 | { 129 | _locker.EnterReadLock(); 130 | 131 | try 132 | { 133 | return _jobs.Cast().ToArray(); 134 | } 135 | finally 136 | { 137 | _locker.ExitReadLock(); 138 | } 139 | } 140 | 141 | public ScheduledJob[] Next() 142 | { 143 | return Next(DateTime.UtcNow); 144 | } 145 | 146 | public ScheduledJob[] Next(DateTime until) 147 | { 148 | var results = new List(_jobs.Count); 149 | 150 | _locker.EnterUpgradeableReadLock(); 151 | 152 | try 153 | { 154 | var expired = new List(_jobs.Count); 155 | 156 | foreach (var job in _jobs) 157 | { 158 | var needsExecuting = job.NeedsExecuting(until); 159 | 160 | if (needsExecuting == null) 161 | { 162 | expired.Add(job); 163 | } 164 | else if (needsExecuting.Value) 165 | { 166 | results.Add(job); 167 | } 168 | } 169 | 170 | if (expired.Count > 0) 171 | { 172 | _locker.EnterWriteLock(); 173 | 174 | try 175 | { 176 | foreach (var job in expired) 177 | { 178 | _jobs.Remove(job); 179 | } 180 | } 181 | finally 182 | { 183 | _locker.ExitWriteLock(); 184 | } 185 | } 186 | } 187 | finally 188 | { 189 | _locker.ExitUpgradeableReadLock(); 190 | } 191 | 192 | return results.ToArray(); 193 | } 194 | 195 | #endregion 196 | 197 | #region Properties 198 | 199 | public int Count 200 | { 201 | get 202 | { 203 | _locker.EnterReadLock(); 204 | 205 | try 206 | { 207 | return _jobs.Count; 208 | } 209 | finally 210 | { 211 | _locker.ExitReadLock(); 212 | } 213 | } 214 | } 215 | 216 | public IScheduledJob this[Guid id] 217 | { 218 | get 219 | { 220 | _locker.EnterReadLock(); 221 | 222 | try 223 | { 224 | var job = _jobs.FirstOrDefault(_ => _.Id == id); 225 | 226 | return job; 227 | } 228 | finally 229 | { 230 | _locker.ExitReadLock(); 231 | } 232 | } 233 | } 234 | 235 | #endregion 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/TempusException.cs: -------------------------------------------------------------------------------- 1 | #region License 2 | // Copyright(c) Workshell Ltd 3 | // 4 | // Permission is hereby granted, free of charge, to any person obtaining a copy 5 | // of this software and associated documentation files (the "Software"), to deal 6 | // in the Software without restriction, including without limitation the rights 7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | // copies of the Software, and to permit persons to whom the Software is 9 | // furnished to do so, subject to the following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included in all 12 | // copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | // SOFTWARE. 21 | #endregion 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Text; 26 | 27 | namespace Workshell.Tempus 28 | { 29 | public class TempusException : Exception 30 | { 31 | public TempusException() : base() 32 | { 33 | } 34 | 35 | public TempusException(string message) : base(message) 36 | { 37 | } 38 | 39 | public TempusException(string message, Exception innerException) : base(message, innerException) 40 | { 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | using System.Text; 5 | 6 | namespace Workshell.Tempus 7 | { 8 | internal static class Utils 9 | { 10 | #region Methods 11 | 12 | public static bool SupportsJob(Type type) 13 | { 14 | return typeof(IJob).GetTypeInfo().IsAssignableFrom(type); 15 | } 16 | 17 | #endregion 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Workshell.Tempus/Workshell.Tempus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net461 5 | false 6 | false 7 | Workshell.snk 8 | Debug;Release;CI 9 | 10 | 11 | 12 | Workshell.Tempus 13 | A simple .NET job scheduler along the lines of Hangfire or Quartz but with a reduced feature set and simpler API. 14 | https://github.com/Workshell/tempus 15 | https://img.workshell.co.uk/logo_128.png 16 | Workshell Tempus Hangfire Long-Running Background Fire-And-Forget Delayed Recurring Tasks Jobs Scheduler Threading Queues 17 | MIT 18 | Workshell Ltd 19 | Workshell Ltd 20 | https://github.com/Workshell/tempus 21 | git 22 | 0.0.0.1 23 | 24 | 25 | 26 | ..\..\bin\debug 27 | TRACE 28 | 29 | 30 | 31 | ..\..\bin\release 32 | TRACE 33 | 34 | 35 | 36 | ..\..\bin\ci 37 | TRACE;SIGNED 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tests/Workshell.Tempus.Tests/Mocks/MockCronJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Workshell.Tempus.Tests.Mocks 7 | { 8 | [Cron("*/10 * * * * *")] 9 | [Overlap(OverlapHandling.Wait)] 10 | public sealed class MockCronJob : IJob 11 | { 12 | public Task ExecuteAsync(JobExecutionContext context) 13 | { 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Workshell.Tempus.Tests/Mocks/MockImmediateJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Workshell.Tempus.Tests.Mocks 7 | { 8 | [Overlap(OverlapHandling.Wait)] 9 | public sealed class MockImmediateJob : IJob 10 | { 11 | public Task ExecuteAsync(JobExecutionContext context) 12 | { 13 | return Task.CompletedTask; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Workshell.Tempus.Tests/Mocks/MockOnceJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Workshell.Tempus.Tests.Mocks 7 | { 8 | [Once("2020-12-25T00:00:00Z")] 9 | [Overlap(OverlapHandling.Wait)] 10 | public sealed class MockOnceJob : IJob 11 | { 12 | public Task ExecuteAsync(JobExecutionContext context) 13 | { 14 | return Task.CompletedTask; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Workshell.Tempus.Tests/ScheduledJob/ScheduledJobTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | using NUnit.Framework; 7 | 8 | using Workshell.Tempus.Tests.Mocks; 9 | 10 | namespace Workshell.Tempus.Tests 11 | { 12 | [TestFixture] 13 | public sealed class ScheduledJobTests 14 | { 15 | private Func _dummyHandler; 16 | 17 | [OneTimeSetUp] 18 | public void SetUp() 19 | { 20 | _dummyHandler = (context) => Task.CompletedTask; 21 | } 22 | 23 | [Test] 24 | public void Constructor_With_Invalid_Type_Throws_Exception() 25 | { 26 | Assert.Throws(() => 27 | { 28 | new ScheduledJob(typeof(String), null); 29 | }); 30 | } 31 | 32 | [Test] 33 | public void Constructor_With_Invalid_Pattern_Throws_Exception() 34 | { 35 | Assert.Throws(() => 36 | { 37 | new ScheduledJob(string.Empty, null, OverlapHandling.Allow, null); 38 | }); 39 | } 40 | 41 | [Test] 42 | public void Constructor_With_Invalid_Handler_Throws_Exception() 43 | { 44 | Assert.Throws(() => 45 | { 46 | new ScheduledJob("@immediately", null, OverlapHandling.Allow, null); 47 | }); 48 | } 49 | 50 | /* Immediate Jobs */ 51 | 52 | [Test] 53 | public void Constructor_With_Typed_Immediate_Job_Pattern_Is_Correct() 54 | { 55 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 56 | 57 | Assert.AreEqual("@immediately", scheduledJob.Pattern); 58 | } 59 | 60 | [Test] 61 | public void Constructor_With_Typed_Immediate_Job_Type_Is_Correct() 62 | { 63 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 64 | 65 | Assert.AreEqual(typeof(MockImmediateJob), scheduledJob.Type); 66 | } 67 | 68 | [Test] 69 | public void Constructor_With_Typed_Immediate_Job_Handler_Is_Correct() 70 | { 71 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 72 | 73 | Assert.IsNull(scheduledJob.Handler); 74 | } 75 | 76 | [Test] 77 | public void Constructor_With_Typed_Immediate_Job_IsImmediately_Is_Correct() 78 | { 79 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 80 | 81 | Assert.IsTrue(scheduledJob.IsImmediately); 82 | } 83 | 84 | [Test] 85 | public void Constructor_With_Typed_Immediate_Job_IsOnce_Is_Correct() 86 | { 87 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 88 | 89 | Assert.IsFalse(scheduledJob.IsOnce); 90 | } 91 | 92 | [Test] 93 | public void Constructor_With_Typed_Immediate_Job_IsAnonymous_Is_Correct() 94 | { 95 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 96 | 97 | Assert.IsFalse(scheduledJob.IsAnonymous); 98 | } 99 | 100 | [Test] 101 | public void Constructor_With_Typed_Immediate_Job_OverlapHandling_Is_Correct() 102 | { 103 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 104 | 105 | Assert.AreEqual(OverlapHandling.Wait, scheduledJob.OverlapHandling); 106 | } 107 | 108 | [Test] 109 | public void NeedsExecuting_With_Typed_Immediate_Job_Is_True() 110 | { 111 | var christmas = new DateTime(2400, 12, 25, 0, 0, 0, DateTimeKind.Utc); 112 | var scheduledJob = new ScheduledJob(typeof(MockImmediateJob), null); 113 | var next = scheduledJob.NeedsExecuting(christmas); 114 | 115 | Assert.IsTrue(next ?? false); 116 | } 117 | 118 | [Test] 119 | public void Constructor_With_Untyped_Immediate_Job_Pattern_Is_Correct() 120 | { 121 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Allow, null); 122 | 123 | Assert.AreEqual("@immediately", scheduledJob.Pattern); 124 | } 125 | 126 | [Test] 127 | public void Constructor_With_Untyped_Immediate_Job_Type_Is_Correct() 128 | { 129 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Allow, null); 130 | 131 | Assert.IsNull(scheduledJob.Type); 132 | } 133 | 134 | [Test] 135 | public void Constructor_With_Untyped_Immediate_Job_Handler_Is_Correct() 136 | { 137 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Allow, null); 138 | 139 | Assert.IsNotNull(scheduledJob.Handler); 140 | } 141 | 142 | [Test] 143 | public void Constructor_With_Untyped_Immediate_Job_IsImmediately_Is_Correct() 144 | { 145 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Allow, null); 146 | 147 | Assert.IsTrue(scheduledJob.IsImmediately); 148 | } 149 | 150 | [Test] 151 | public void Constructor_With_Untyped_Immediate_Job_IsOnce_Is_Correct() 152 | { 153 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Allow, null); 154 | 155 | Assert.IsFalse(scheduledJob.IsOnce); 156 | } 157 | 158 | [Test] 159 | public void Constructor_With_Untyped_Immediate_Job_IsAnonymous_Is_Correct() 160 | { 161 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Allow, null); 162 | 163 | Assert.IsTrue(scheduledJob.IsAnonymous); 164 | } 165 | 166 | [Test] 167 | public void Constructor_With_Untyped_Immediate_Job_OverlapHandling_Is_Correct() 168 | { 169 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Wait, null); 170 | 171 | Assert.AreEqual(OverlapHandling.Wait, scheduledJob.OverlapHandling); 172 | } 173 | 174 | [Test] 175 | public void NeedsExecuting_With_Untyped_Immediate_Job_Is_True() 176 | { 177 | var christmas = new DateTime(2400, 12, 25, 0, 0, 0, DateTimeKind.Utc); 178 | var scheduledJob = new ScheduledJob("@immediately", _dummyHandler, OverlapHandling.Allow, null); 179 | var next = scheduledJob.NeedsExecuting(christmas); 180 | 181 | Assert.IsTrue(next ?? false); 182 | } 183 | 184 | /* One-time Jobs */ 185 | 186 | [Test] 187 | public void Constructor_With_Typed_Once_Job_Pattern_Is_Correct() 188 | { 189 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 190 | var startsWith = scheduledJob.Pattern.StartsWith("@once "); 191 | 192 | Assert.IsTrue(startsWith); 193 | } 194 | 195 | [Test] 196 | public void Constructor_With_Typed_Once_Job_Type_Is_Correct() 197 | { 198 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 199 | 200 | Assert.AreEqual(typeof(MockOnceJob), scheduledJob.Type); 201 | } 202 | 203 | [Test] 204 | public void Constructor_With_Typed_Once_Job_Handler_Is_Correct() 205 | { 206 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 207 | 208 | Assert.IsNull(scheduledJob.Handler); 209 | } 210 | 211 | [Test] 212 | public void Constructor_With_Typed_Once_Job_IsImmediately_Is_Correct() 213 | { 214 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 215 | 216 | Assert.IsFalse(scheduledJob.IsImmediately); 217 | } 218 | 219 | [Test] 220 | public void Constructor_With_Typed_Once_Job_IsOnce_Is_Correct() 221 | { 222 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 223 | 224 | Assert.IsTrue(scheduledJob.IsOnce); 225 | } 226 | 227 | [Test] 228 | public void Constructor_With_Typed_Once_Job_IsAnonymous_Is_Correct() 229 | { 230 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 231 | 232 | Assert.IsFalse(scheduledJob.IsAnonymous); 233 | } 234 | 235 | [Test] 236 | public void Constructor_With_Typed_Once_Job_OverlapHandling_Is_Correct() 237 | { 238 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 239 | 240 | Assert.AreEqual(OverlapHandling.Wait, scheduledJob.OverlapHandling); 241 | } 242 | 243 | [Test] 244 | public void NeedsExecuting_With_Typed_Once_Job_Is_True() 245 | { 246 | var christmas = new DateTime(2400, 12, 25, 0, 0, 0, DateTimeKind.Utc); 247 | var scheduledJob = new ScheduledJob(typeof(MockOnceJob), null); 248 | var next = scheduledJob.NeedsExecuting(christmas); 249 | 250 | Assert.IsTrue(next ?? false); 251 | } 252 | 253 | [Test] 254 | public void Constructor_With_Untyped_Once_Job_Pattern_Is_Correct() 255 | { 256 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Allow, null); 257 | 258 | Assert.AreEqual("@once 2400-12-25T00:00:00Z", scheduledJob.Pattern); 259 | } 260 | 261 | [Test] 262 | public void Constructor_With_Untyped_Once_Job_Type_Is_Correct() 263 | { 264 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Allow, null); 265 | 266 | Assert.IsNull(scheduledJob.Type); 267 | } 268 | 269 | [Test] 270 | public void Constructor_With_Untyped_Once_Job_Handler_Is_Correct() 271 | { 272 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Allow, null); 273 | 274 | Assert.IsNotNull(scheduledJob.Handler); 275 | } 276 | 277 | [Test] 278 | public void Constructor_With_Untyped_Once_Job_IsImmediately_Is_Correct() 279 | { 280 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Allow, null); 281 | 282 | Assert.IsFalse(scheduledJob.IsImmediately); 283 | } 284 | 285 | [Test] 286 | public void Constructor_With_Untyped_Once_Job_IsOnce_Is_Correct() 287 | { 288 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Allow, null); 289 | 290 | Assert.IsTrue(scheduledJob.IsOnce); 291 | } 292 | 293 | [Test] 294 | public void Constructor_With_Untyped_Once_Job_IsAnonymous_Is_Correct() 295 | { 296 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Allow, null); 297 | 298 | Assert.IsTrue(scheduledJob.IsAnonymous); 299 | } 300 | 301 | [Test] 302 | public void Constructor_With_Untyped_Once_Job_OverlapHandling_Is_Correct() 303 | { 304 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Wait, null); 305 | 306 | Assert.AreEqual(OverlapHandling.Wait, scheduledJob.OverlapHandling); 307 | } 308 | 309 | [Test] 310 | public void NeedsExecuting_With_Untyped_Once_Job_Is_True() 311 | { 312 | var christmas = new DateTime(2400, 12, 25, 0, 0, 0, DateTimeKind.Utc); 313 | var scheduledJob = new ScheduledJob("@once 2400-12-25T00:00:00Z", _dummyHandler, OverlapHandling.Allow, null); 314 | var next = scheduledJob.NeedsExecuting(christmas); 315 | 316 | Assert.IsTrue(next ?? false); 317 | } 318 | 319 | /* Cron Jobs */ 320 | 321 | [Test] 322 | public void Constructor_With_Typed_Cron_Job_Pattern_Is_Correct() 323 | { 324 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 325 | 326 | Assert.AreEqual("*/10 * * * * *", scheduledJob.Pattern); 327 | } 328 | 329 | [Test] 330 | public void Constructor_With_Typed_Cron_Job_Type_Is_Correct() 331 | { 332 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 333 | 334 | Assert.AreEqual(typeof(MockCronJob), scheduledJob.Type); 335 | } 336 | 337 | [Test] 338 | public void Constructor_With_Typed_Cron_Job_Handler_Is_Correct() 339 | { 340 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 341 | 342 | Assert.IsNull(scheduledJob.Handler); 343 | } 344 | 345 | [Test] 346 | public void Constructor_With_Typed_Cron_Job_IsImmediately_Is_Correct() 347 | { 348 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 349 | 350 | Assert.IsFalse(scheduledJob.IsImmediately); 351 | } 352 | 353 | [Test] 354 | public void Constructor_With_Typed_Cron_Job_IsOnce_Is_Correct() 355 | { 356 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 357 | 358 | Assert.IsFalse(scheduledJob.IsOnce); 359 | } 360 | 361 | [Test] 362 | public void Constructor_With_Typed_Cron_Job_IsAnonymous_Is_Correct() 363 | { 364 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 365 | 366 | Assert.IsFalse(scheduledJob.IsAnonymous); 367 | } 368 | 369 | [Test] 370 | public void Constructor_With_Typed_Cron_Job_OverlapHandling_Is_Correct() 371 | { 372 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 373 | 374 | Assert.AreEqual(OverlapHandling.Wait, scheduledJob.OverlapHandling); 375 | } 376 | 377 | [Test] 378 | public void NeedsExecuting_With_Typed_Cron_Job_Is_True() 379 | { 380 | var christmas = new DateTime(2400, 12, 25, 0, 0, 10, DateTimeKind.Utc); 381 | var scheduledJob = new ScheduledJob(typeof(MockCronJob), null); 382 | var next = scheduledJob.NeedsExecuting(christmas); 383 | 384 | Assert.IsTrue(next ?? false); 385 | } 386 | 387 | [Test] 388 | public void Constructor_With_Untyped_Cron_Job_Pattern_Is_Correct() 389 | { 390 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Allow, null); 391 | 392 | Assert.AreEqual("*/10 * * * * *", scheduledJob.Pattern); 393 | } 394 | 395 | [Test] 396 | public void Constructor_With_Untyped_Cron_Job_Type_Is_Correct() 397 | { 398 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Allow, null); 399 | 400 | Assert.IsNull(scheduledJob.Type); 401 | } 402 | 403 | [Test] 404 | public void Constructor_With_Untyped_Cron_Job_Handler_Is_Correct() 405 | { 406 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Allow, null); 407 | 408 | Assert.IsNotNull(scheduledJob.Handler); 409 | } 410 | 411 | [Test] 412 | public void Constructor_With_Untyped_Cron_Job_IsImmediately_Is_Correct() 413 | { 414 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Allow, null); 415 | 416 | Assert.IsFalse(scheduledJob.IsImmediately); 417 | } 418 | 419 | [Test] 420 | public void Constructor_With_Untyped_Cron_Job_IsOnce_Is_Correct() 421 | { 422 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Allow, null); 423 | 424 | Assert.IsFalse(scheduledJob.IsOnce); 425 | } 426 | 427 | [Test] 428 | public void Constructor_With_Untyped_Cron_Job_IsAnonymous_Is_Correct() 429 | { 430 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Allow, null); 431 | 432 | Assert.IsTrue(scheduledJob.IsAnonymous); 433 | } 434 | 435 | [Test] 436 | public void Constructor_With_Untyped_Cron_Job_OverlapHandling_Is_Correct() 437 | { 438 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Wait, null); 439 | 440 | Assert.AreEqual(OverlapHandling.Wait, scheduledJob.OverlapHandling); 441 | } 442 | 443 | [Test] 444 | public void NeedsExecuting_With_Untyped_Cron_Job_Is_True() 445 | { 446 | var christmas = new DateTime(2400, 12, 25, 0, 0, 10, DateTimeKind.Utc); 447 | var scheduledJob = new ScheduledJob("*/10 * * * * *", _dummyHandler, OverlapHandling.Allow, null); 448 | var next = scheduledJob.NeedsExecuting(christmas); 449 | 450 | Assert.IsTrue(next ?? false); 451 | } 452 | } 453 | } 454 | -------------------------------------------------------------------------------- /tests/Workshell.Tempus.Tests/Workshell.Tempus.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | false 6 | false 7 | false 8 | Workshell.snk 9 | Debug;Release;CI 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /version.txt: -------------------------------------------------------------------------------- 1 | 1.2.0 --------------------------------------------------------------------------------