├── version.txt
├── src
├── CommonAssemblyInfo.cs
├── Workshell.Tempus
│ ├── OverlapHandling.cs
│ ├── Utils.cs
│ ├── Events
│ │ ├── JobEventArgs.cs
│ │ ├── JobStartingEventArgs.cs
│ │ ├── JobStartedEventArgs.cs
│ │ └── JobErrorEventArgs.cs
│ ├── Extensions
│ │ ├── DisposableExtensions.cs
│ │ └── JobSchedulerExtensions.cs
│ ├── Attributes
│ │ ├── NoOverlapAttribute.cs
│ │ ├── CronAttribute.cs
│ │ ├── OnceAttribute.cs
│ │ └── OverlapAttribute.cs
│ ├── ActiveJobOrder.cs
│ ├── Interfaces
│ │ ├── IJobFactory.cs
│ │ ├── IJob.cs
│ │ ├── IJobScope.cs
│ │ ├── IScheduledJobs.cs
│ │ ├── IActiveJobs.cs
│ │ ├── IScheduledJob.cs
│ │ ├── IJobRunner.cs
│ │ ├── IActiveJob.cs
│ │ └── IJobScheduler.cs
│ ├── JobFactory.cs
│ ├── TempusException.cs
│ ├── JobExecutionContext.cs
│ ├── JobScope.cs
│ ├── JobRunner.cs
│ ├── Workshell.Tempus.csproj
│ ├── Properties
│ │ └── AssemblyInfo.cs
│ ├── ActiveJob.cs
│ ├── ActiveJobs.cs
│ ├── ScheduledJobs.cs
│ ├── ScheduledJob.cs
│ └── JobScheduler.cs
└── Workshell.Tempus.AspNetCore
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── TempusOptions.cs
│ ├── AspNetJobFactory.cs
│ ├── Workshell.Tempus.AspNetCore.csproj
│ ├── AspNetJobWrapper.cs
│ ├── AspNetJobScope.cs
│ ├── TempusBackgroundService.cs
│ └── Extensions
│ └── ServiceCollectionExtensions.cs
├── tests
└── Workshell.Tempus.Tests
│ ├── Mocks
│ ├── MockImmediateJob.cs
│ ├── MockCronJob.cs
│ └── MockOnceJob.cs
│ ├── Workshell.Tempus.Tests.csproj
│ └── ScheduledJob
│ └── ScheduledJobTests.cs
├── license.txt
├── .gitignore
├── Tempus.sln
└── readme.md
/version.txt:
--------------------------------------------------------------------------------
1 | 1.2.0
--------------------------------------------------------------------------------
/src/CommonAssemblyInfo.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Workshell/tempus/HEAD/src/CommonAssemblyInfo.cs
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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.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/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/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/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 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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.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.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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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.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/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 | }
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Workshell Tempus
2 |
3 | [](https://github.com/Workshell/tempus/blob/master/license.txt)
4 | [](https://www.nuget.org/packages/Workshell.Tempus/)
5 | [](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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------