├── .gitattributes ├── .gitignore ├── .paket ├── paket.bootstrapper.exe └── paket.targets ├── .travis.yml ├── Cronix.All.sln ├── Cronix.Web.sln ├── Cronix.sln ├── Install.ps1 ├── LICENSE.txt ├── README.md ├── RELEASE_NOTES.md ├── appveyor.yml ├── build.cmd ├── build.fsx ├── build.sh ├── docs ├── content │ ├── cronix service installation.fsx │ ├── developers guide.fsx │ ├── index.fsx │ ├── settingup WebUI.fsx │ ├── startup script.fsx │ └── the big picture.fsx ├── files │ └── img │ │ ├── logo-template.pdn │ │ ├── logo.png │ │ ├── overview.png │ │ ├── overview.pptx │ │ └── webui.PNG ├── samples │ ├── BitstampTicker │ │ ├── BitstampTicker.fsproj │ │ ├── Exchange.fs │ │ ├── Script.fsx │ │ ├── SqlCreate.txt │ │ ├── Types.fs │ │ ├── app.config │ │ └── paket.references │ ├── CSharpSample.Jobs │ │ ├── CSharpSample.Jobs.csproj │ │ ├── ExternalJobs.cs │ │ └── Properties │ │ │ └── AssemblyInfo.cs │ ├── CSharpSample │ │ ├── App.config │ │ ├── CSharpSample.csproj │ │ ├── NLog.config │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ ├── Startup.fsx │ │ └── paket.references │ ├── CryptoTicker │ │ ├── CryptoTicker.fsproj │ │ ├── Exchange.fs │ │ ├── SqlCreate.txt │ │ └── paket.references │ └── FSharpSample │ │ ├── App.config │ │ ├── AssemblyInfo.fs │ │ ├── FSharpSample.fsproj │ │ ├── NLog.config │ │ ├── Program.fs │ │ ├── Startup.fsx │ │ └── paket.references └── tools │ ├── generate.fsx │ └── templates │ └── template.cshtml ├── paket.dependencies ├── paket.lock ├── src ├── Cronix.UI │ ├── .bowerrc │ ├── .gitignore │ ├── Web.config │ ├── bower.json │ ├── gulpfile.js │ ├── package.json │ ├── readme.txt │ ├── src │ │ ├── app │ │ │ ├── base.js │ │ │ ├── bootstrap.js │ │ │ ├── excanvas.min.js │ │ │ ├── require.config.js │ │ │ ├── router.js │ │ │ └── startup.js │ │ ├── components │ │ │ ├── job-preview │ │ │ │ ├── job-preview.html │ │ │ │ └── job-preview.js │ │ │ └── nav-bar │ │ │ │ ├── nav-bar.html │ │ │ │ └── nav-bar.js │ │ ├── css │ │ │ ├── base-admin-responsive.css │ │ │ ├── bootstrap-responsive.min.css │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.min.css │ │ │ ├── dashboard.css │ │ │ ├── font-awesome-ie7.css │ │ │ ├── font-awesome-ie7.min.css │ │ │ ├── font-awesome.css │ │ │ ├── font-awesome.min.css │ │ │ ├── font │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.svgz │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfontd41d.eot │ │ │ └── style.css │ │ ├── img │ │ │ ├── body-bg.png │ │ │ ├── glyphicons-halflings-white.html │ │ │ ├── glyphicons-halflings.html │ │ │ ├── icons-sa7c41345d9.png │ │ │ ├── message_avatar1.png │ │ │ ├── message_avatar2.png │ │ │ └── signin │ │ │ │ ├── check.png │ │ │ │ ├── fb_btn.png │ │ │ │ ├── password.png │ │ │ │ ├── twitter_btn.png │ │ │ │ └── user.png │ │ └── index.html │ ├── test │ │ ├── .bowerrc │ │ ├── SpecRunner.browser.js │ │ ├── bower.json │ │ ├── components │ │ │ └── home-page.js │ │ ├── index.html │ │ └── require.config.js │ └── vwd.webinfo ├── Cronix.Web │ ├── AssemblyInfo.fs │ ├── Cronix.Web.fsproj │ ├── Hubs.fs │ ├── WebPlugin.fs │ ├── Website.fs │ ├── paket.references │ └── paket.template └── Cronix │ ├── App.config │ ├── AppSettingProvider.fs │ ├── AppSettings.fs │ ├── AssemblyInfo.fs │ ├── BootStrapper.fs │ ├── BootStrapper.fsi │ ├── Compiler.fs │ ├── Cronix.fsproj │ ├── FSharp.Core.optdata │ ├── FSharp.Core.sigdata │ ├── Installer.fs │ ├── Logging.fs │ ├── Scheduling.fs │ ├── Startup.fsx │ ├── Triggers.fs │ ├── Types.fs │ ├── paket.references │ └── paket.template ├── tests └── Cronix.Tests │ ├── App.config │ ├── AppSettingsTests.fs │ ├── Cronix.Tests.fsproj │ ├── SchedulingTests.fs │ ├── ServiceEnviroment.fs │ ├── ServiceSystemTest.fs │ ├── Startup.fsx │ ├── TriggerEnviroment.fs │ ├── TriggerTests.fs │ └── paket.references └── todo.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp text=auto eol=lf 6 | *.fs diff=csharp text=auto eol=lf 7 | *.fsi diff=csharp text=auto eol=lf 8 | *.fsx diff=csharp text=auto eol=lf 9 | *.sln text eol=crlf merge=union 10 | *.csproj merge=union 11 | *.vbproj merge=union 12 | *.fsproj merge=union 13 | *.dbproj merge=union 14 | 15 | # Standard to msysgit 16 | *.doc diff=astextplain 17 | *.DOC diff=astextplain 18 | *.docx diff=astextplain 19 | *.DOCX diff=astextplain 20 | *.dot diff=astextplain 21 | *.DOT diff=astextplain 22 | *.pdf diff=astextplain 23 | *.PDF diff=astextplain 24 | *.rtf diff=astextplain 25 | *.RTF diff=astextplain 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Xamarin Studio / monodevelop user-specific 10 | *.userprefs 11 | 12 | # Build results 13 | 14 | [Dd]ebug/ 15 | [Rr]elease/ 16 | x64/ 17 | build/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 22 | !packages/*/build/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | *_i.c 29 | *_p.c 30 | *.ilk 31 | *.meta 32 | *.obj 33 | *.pch 34 | *.pdb 35 | *.pgc 36 | *.pgd 37 | *.rsp 38 | *.sbr 39 | *.tlb 40 | *.tli 41 | *.tlh 42 | *.tmp 43 | *.tmp_proj 44 | *.log 45 | *.vspscc 46 | *.vssscc 47 | .builds 48 | *.pidb 49 | *.log 50 | *.scc 51 | 52 | # Visual C++ cache files 53 | ipch/ 54 | *.aps 55 | *.ncb 56 | *.opensdf 57 | *.sdf 58 | *.cachefile 59 | 60 | # Visual Studio profiler 61 | *.psess 62 | *.vsp 63 | *.vspx 64 | 65 | # Guidance Automation Toolkit 66 | *.gpState 67 | 68 | # ReSharper is a .NET coding add-in 69 | _ReSharper*/ 70 | *.[Rr]e[Ss]harper 71 | 72 | # TeamCity is a build add-in 73 | _TeamCity* 74 | 75 | # DotCover is a Code Coverage Tool 76 | *.dotCover 77 | 78 | # NCrunch 79 | *.ncrunch* 80 | .*crunch*.local.xml 81 | 82 | # Installshield output folder 83 | [Ee]xpress/ 84 | 85 | # DocProject is a documentation generator add-in 86 | DocProject/buildhelp/ 87 | DocProject/Help/*.HxT 88 | DocProject/Help/*.HxC 89 | DocProject/Help/*.hhc 90 | DocProject/Help/*.hhk 91 | DocProject/Help/*.hhp 92 | DocProject/Help/Html2 93 | DocProject/Help/html 94 | 95 | # Click-Once directory 96 | publish/ 97 | 98 | # Publish Web Output 99 | *.Publish.xml 100 | 101 | # Enable nuget.exe in the .nuget folder (though normally executables are not tracked) 102 | !.nuget/NuGet.exe 103 | 104 | # Windows Azure Build Output 105 | csx 106 | *.build.csdef 107 | 108 | # Windows Store app package directory 109 | AppPackages/ 110 | 111 | # Others 112 | sql/ 113 | *.Cache 114 | ClientBin/ 115 | [Ss]tyle[Cc]op.* 116 | ~$* 117 | *~ 118 | *.dbmdl 119 | *.[Pp]ublish.xml 120 | *.pfx 121 | *.publishsettings 122 | 123 | # RIA/Silverlight projects 124 | Generated_Code/ 125 | 126 | # Backup & report files from converting an old project file to a newer 127 | # Visual Studio version. Backup files are not needed, because we have git ;-) 128 | _UpgradeReport_Files/ 129 | Backup*/ 130 | UpgradeLog*.XML 131 | UpgradeLog*.htm 132 | 133 | # SQL Server files 134 | App_Data/*.mdf 135 | App_Data/*.ldf 136 | 137 | 138 | #LightSwitch generated files 139 | GeneratedArtifacts/ 140 | _Pvt_Extensions/ 141 | ModelManifest.xml 142 | 143 | # ========================= 144 | # Windows detritus 145 | # ========================= 146 | 147 | # Windows image file caches 148 | Thumbs.db 149 | ehthumbs.db 150 | 151 | # Folder config file 152 | Desktop.ini 153 | 154 | # Recycle Bin used on file shares 155 | $RECYCLE.BIN/ 156 | 157 | # Mac desktop service store files 158 | .DS_Store 159 | 160 | # =================================================== 161 | # Exclude F# project specific directories and files 162 | # =================================================== 163 | 164 | # NuGet Packages Directory 165 | packages/ 166 | 167 | # Temp folder used for publishing docs 168 | temp/ 169 | 170 | # Test results produced by build 171 | TestResults.xml 172 | 173 | # Nuget outputs 174 | nuget/*.nupkg 175 | release.cmd 176 | release.sh 177 | localpackages/ 178 | paket-files 179 | *.orig 180 | .paket/paket.exe 181 | docs/content/license.md 182 | docs/content/release-notes.md 183 | debugEnv 184 | /envs/serviceEnv 185 | password.txt 186 | /Cronix.Tests.dll.html 187 | /Cronix.Tests.dll.xml 188 | output 189 | /Cronix.Tests.dll.html 190 | /Cronix.Tests.dll.xml 191 | /Cronix.Tests.dll.html 192 | src/Cronix/webui/ 193 | _NCrunch_Cronix 194 | /_NCrunch_Cronix.All 195 | /PrecompiledWeb 196 | -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | 7 | true 8 | $(MSBuildThisFileDirectory) 9 | $(MSBuildThisFileDirectory)..\ 10 | 11 | 12 | 13 | $(PaketToolsPath)paket.exe 14 | $(PaketToolsPath)paket.bootstrapper.exe 15 | "$(PaketExePath)" 16 | mono --runtime=v4.0.30319 $(PaketExePath) 17 | "$(PaketBootStrapperExePath)" 18 | mono --runtime=v4.0.30319 $(PaketBootStrapperExePath) 19 | 20 | $(PaketCommand) restore 21 | $(PaketBootStrapperCommand) 22 | 23 | RestorePackages; $(BuildDependsOn); 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: fsharp 2 | 3 | sudo: false # use the new container-based Travis infrastructure 4 | 5 | script: 6 | - ./build.sh RunTests 7 | -------------------------------------------------------------------------------- /Cronix.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 2013 3 | VisualStudioVersion = 12.0.31101.0 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{63297B98-5CED-492C-A5B7-A5B4F73CF142}" 6 | ProjectSection(SolutionItems) = preProject 7 | paket.dependencies = paket.dependencies 8 | paket.lock = paket.lock 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1}" 12 | EndProject 13 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Cronix", "src\Cronix\Cronix.fsproj", "{1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}" 14 | EndProject 15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{BF60BC93-E09B-4E5F-9D85-95A519479D54}" 16 | ProjectSection(SolutionItems) = preProject 17 | build.fsx = build.fsx 18 | README.md = README.md 19 | RELEASE_NOTES.md = RELEASE_NOTES.md 20 | todo.txt = todo.txt 21 | EndProjectSection 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{83F16175-43B1-4C90-A1EE-8E351C33435D}" 24 | ProjectSection(SolutionItems) = preProject 25 | docs\tools\generate.fsx = docs\tools\generate.fsx 26 | docs\tools\templates\template.cshtml = docs\tools\templates\template.cshtml 27 | EndProjectSection 28 | EndProject 29 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{8E6D5255-776D-4B61-85F9-73C37AA1FB9A}" 30 | ProjectSection(SolutionItems) = preProject 31 | docs\content\cronix setup.fsx = docs\content\cronix setup.fsx 32 | docs\content\index.fsx = docs\content\index.fsx 33 | docs\content\startup script.fsx = docs\content\startup script.fsx 34 | EndProjectSection 35 | EndProject 36 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ED8079DD-2B06-4030-9F0F-DC548F98E1C4}" 37 | EndProject 38 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Cronix.Tests", "tests\Cronix.Tests\Cronix.Tests.fsproj", "{F0620B10-2EC7-426D-88F4-FBD6762BAABE}" 39 | EndProject 40 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{B6255659-79E8-4AC2-AD13-321960F13DF4}" 41 | EndProject 42 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpSample", "docs\samples\CSharpSample\CSharpSample.csproj", "{06322D90-9A28-423C-9805-AC1393A8F18D}" 43 | EndProject 44 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpSample.Jobs", "docs\samples\CSharpSample.Jobs\CSharpSample.Jobs.csproj", "{5C018694-E498-4971-AF52-4E4343DC38FC}" 45 | EndProject 46 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpSample", "docs\samples\FSharpSample\FSharpSample.fsproj", "{1CBB9793-714B-42A9-AD03-12B15FB71967}" 47 | EndProject 48 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Cronix.Web", "src\Cronix.Web\Cronix.Web.fsproj", "{14621A39-B196-41A7-A370-907C9381BC3F}" 49 | EndProject 50 | Global 51 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 52 | Debug|Any CPU = Debug|Any CPU 53 | Debug|Mixed Platforms = Debug|Mixed Platforms 54 | Debug|x86 = Debug|x86 55 | Release|Any CPU = Release|Any CPU 56 | Release|Mixed Platforms = Release|Mixed Platforms 57 | Release|x86 = Release|x86 58 | EndGlobalSection 59 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 60 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 63 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 64 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Debug|x86.ActiveCfg = Debug|Any CPU 65 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Release|Any CPU.Build.0 = Release|Any CPU 67 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 68 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Release|Mixed Platforms.Build.0 = Release|Any CPU 69 | {1AB7E0B7-C56E-4A50-B5FA-7AD04F56C17C}.Release|x86.ActiveCfg = Release|Any CPU 70 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 73 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 74 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Debug|x86.ActiveCfg = Debug|Any CPU 75 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Release|Any CPU.ActiveCfg = Release|Any CPU 76 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Release|Any CPU.Build.0 = Release|Any CPU 77 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 78 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Release|Mixed Platforms.Build.0 = Release|Any CPU 79 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE}.Release|x86.ActiveCfg = Release|Any CPU 80 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 81 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Debug|Any CPU.Build.0 = Debug|Any CPU 82 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 83 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 84 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Debug|x86.ActiveCfg = Debug|Any CPU 85 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Release|Any CPU.ActiveCfg = Release|Any CPU 86 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Release|Any CPU.Build.0 = Release|Any CPU 87 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 88 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Release|Mixed Platforms.Build.0 = Release|Any CPU 89 | {06322D90-9A28-423C-9805-AC1393A8F18D}.Release|x86.ActiveCfg = Release|Any CPU 90 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 91 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Debug|Any CPU.Build.0 = Debug|Any CPU 92 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 93 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 94 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Debug|x86.ActiveCfg = Debug|Any CPU 95 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Release|Any CPU.ActiveCfg = Release|Any CPU 96 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Release|Any CPU.Build.0 = Release|Any CPU 97 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 98 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Release|Mixed Platforms.Build.0 = Release|Any CPU 99 | {5C018694-E498-4971-AF52-4E4343DC38FC}.Release|x86.ActiveCfg = Release|Any CPU 100 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 101 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Debug|Any CPU.Build.0 = Debug|Any CPU 102 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 103 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 104 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Debug|x86.ActiveCfg = Debug|Any CPU 105 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Release|Any CPU.ActiveCfg = Release|Any CPU 106 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Release|Any CPU.Build.0 = Release|Any CPU 107 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 108 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Release|Mixed Platforms.Build.0 = Release|Any CPU 109 | {1CBB9793-714B-42A9-AD03-12B15FB71967}.Release|x86.ActiveCfg = Release|Any CPU 110 | {14621A39-B196-41A7-A370-907C9381BC3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 111 | {14621A39-B196-41A7-A370-907C9381BC3F}.Debug|Any CPU.Build.0 = Debug|Any CPU 112 | {14621A39-B196-41A7-A370-907C9381BC3F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 113 | {14621A39-B196-41A7-A370-907C9381BC3F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 114 | {14621A39-B196-41A7-A370-907C9381BC3F}.Debug|x86.ActiveCfg = Debug|Any CPU 115 | {14621A39-B196-41A7-A370-907C9381BC3F}.Release|Any CPU.ActiveCfg = Release|Any CPU 116 | {14621A39-B196-41A7-A370-907C9381BC3F}.Release|Any CPU.Build.0 = Release|Any CPU 117 | {14621A39-B196-41A7-A370-907C9381BC3F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 118 | {14621A39-B196-41A7-A370-907C9381BC3F}.Release|Mixed Platforms.Build.0 = Release|Any CPU 119 | {14621A39-B196-41A7-A370-907C9381BC3F}.Release|x86.ActiveCfg = Release|Any CPU 120 | EndGlobalSection 121 | GlobalSection(SolutionProperties) = preSolution 122 | HideSolutionNode = FALSE 123 | EndGlobalSection 124 | GlobalSection(NestedProjects) = preSolution 125 | {83F16175-43B1-4C90-A1EE-8E351C33435D} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 126 | {8E6D5255-776D-4B61-85F9-73C37AA1FB9A} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 127 | {F0620B10-2EC7-426D-88F4-FBD6762BAABE} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} 128 | {B6255659-79E8-4AC2-AD13-321960F13DF4} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 129 | {06322D90-9A28-423C-9805-AC1393A8F18D} = {B6255659-79E8-4AC2-AD13-321960F13DF4} 130 | {5C018694-E498-4971-AF52-4E4343DC38FC} = {B6255659-79E8-4AC2-AD13-321960F13DF4} 131 | {1CBB9793-714B-42A9-AD03-12B15FB71967} = {B6255659-79E8-4AC2-AD13-321960F13DF4} 132 | EndGlobalSection 133 | EndGlobal 134 | -------------------------------------------------------------------------------- /Install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | 3 | function MarkDirectoryAsCopyToOutputRecursive($item) 4 | { 5 | $item.ProjectItems | ForEach-Object { MarkFileASCopyToOutputDirectory($_) } 6 | } 7 | 8 | function MarkFileASCopyToOutputDirectory($item) 9 | { 10 | Try 11 | { 12 | Write-Host Try set $item.Name 13 | $item.Properties.Item("CopyToOutputDirectory").Value = 2 14 | } 15 | Catch 16 | { 17 | Write-Host RecurseOn $item.Name 18 | MarkDirectoryAsCopyToOutputRecursive($item) 19 | } 20 | } 21 | 22 | #Now mark everything in the a directory as "Copy to newer" 23 | MarkDirectoryAsCopyToOutputRecursive($project.ProjectItems.Item("webui")) 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Issue Stats](http://issuestats.com/github/aph5nt/cronix/badge/issue)](http://issuestats.com/github/aph5nt/cronix) 2 | [![Issue Stats](http://issuestats.com/github/aph5nt/cronix/badge/pr)](http://issuestats.com/github/aph5nt/cronix) 3 | [![Build status](https://ci.appveyor.com/api/projects/status/ceo9ycw09wfhul0d?svg=true)](https://ci.appveyor.com/project/aph5nt/cronix) 4 | 5 | # Cronix 6 | 7 | The 'CRON like' library. 8 | 9 | Documentation: http://aph5nt.github.io/cronix 10 | 11 | ## Maintainer(s) 12 | 13 | - [@aph5nt](https://github.com/aph5nt) 14 | 15 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | ### 0.3.2 - Released 2 | * Minor fixes 3 | 4 | ### 0.3.1 - Released 5 | * Package description fix 6 | 7 | ### 0.3 - Released 8 | * Added web interface (Cronix.Web package) 9 | * Trigger improvements 10 | * Code refactoring and bug fixes 11 | 12 | ### 0.2 - Released 13 | * Documentation fixes 14 | * Unit test fixes 15 | * Code refactoring 16 | * Added support for starting and stopping given job 17 | * Added Cron expression helper 18 | 19 | ### 0.1 - Released 20 | * Initial version of CRONIX service -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | init: 2 | - git config --global core.autocrlf input 3 | build_script: 4 | - cmd: build.cmd RunTests 5 | test: off 6 | version: 0.0.2.{build} 7 | artifacts: 8 | - path: bin 9 | name: bin 10 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | 4 | .paket\paket.bootstrapper.exe 5 | if errorlevel 1 ( 6 | exit /b %errorlevel% 7 | ) 8 | 9 | .paket\paket.exe restore 10 | if errorlevel 1 ( 11 | exit /b %errorlevel% 12 | ) 13 | 14 | IF NOT EXIST build.fsx ( 15 | .paket\paket.exe update 16 | packages\FAKE\tools\FAKE.exe init.fsx 17 | ) 18 | packages\FAKE\tools\FAKE.exe build.fsx %* 19 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if test "$OS" = "Windows_NT" 3 | then 4 | # use .Net 5 | 6 | .paket/paket.bootstrapper.exe 7 | exit_code=$? 8 | if [ $exit_code -ne 0 ]; then 9 | exit $exit_code 10 | fi 11 | 12 | .paket/paket.exe restore 13 | exit_code=$? 14 | if [ $exit_code -ne 0 ]; then 15 | exit $exit_code 16 | fi 17 | 18 | [ ! -e build.fsx ] && .paket/paket.exe update 19 | [ ! -e build.fsx ] && packages/FAKE/tools/FAKE.exe init.fsx 20 | packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx 21 | else 22 | # use mono 23 | mono .paket/paket.bootstrapper.exe 24 | exit_code=$? 25 | if [ $exit_code -ne 0 ]; then 26 | exit $exit_code 27 | fi 28 | 29 | mono .paket/paket.exe restore 30 | exit_code=$? 31 | if [ $exit_code -ne 0 ]; then 32 | exit $exit_code 33 | fi 34 | 35 | [ ! -e build.fsx ] && mono .paket/paket.exe update 36 | [ ! -e build.fsx ] && mono packages/FAKE/tools/FAKE.exe init.fsx 37 | mono packages/FAKE/tools/FAKE.exe $@ --fsiargs -d:MONO build.fsx 38 | fi 39 | -------------------------------------------------------------------------------- /docs/content/cronix service installation.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | Cronix service installation 3 | ====================== 4 | 5 | How to install your cronix service ? 6 | 7 |
8 |
9 |
10 |
11 | The Cronix library can be installed from NuGet: 12 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe install
13 |
14 |
15 |
16 |
17 | 18 | 19 | How to uninstall your cronix service ? 20 | 21 |
22 |
23 |
24 |
25 | The Cronix library can be installed from NuGet: 26 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe uninstall
27 |
28 |
29 |
30 |
31 | 32 | How to run your cronix service without installing it ? 33 | 34 |
35 |
36 |
37 |
38 | The Cronix library can be installed from NuGet: 39 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe debug
40 |
41 |
42 |
43 |
44 | 45 | How to display help ? 46 | 47 |
48 |
49 |
50 |
51 | The Cronix library can be installed from NuGet: 52 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe typeAnythingElseHere
53 |
54 |
55 |
56 |
57 | 58 | **) -------------------------------------------------------------------------------- /docs/content/developers guide.fsx: -------------------------------------------------------------------------------- 1 | (*** hide ***) 2 | // This block of code is omitted in the generated HTML documentation. Use 3 | // it to define helpers that you do not want to show in the documentation. 4 | #I "../../bin" 5 | 6 | (** 7 | 8 | # The developer's guide 9 | ## Creating new project 10 | 11 | The best point to get started with Cronix library is to take a look at the Generic Sample Projects. They can be found here 12 | The following developer's guide will briefly walk througth some important topics which are worth to mention. 13 | 14 | 15 | ## Configuration 16 | * Logging 17 | 18 | By deafult, Cronix uses NLOG library for logging. To enable logging the NLOG.config file should be added to your Cronix project. 19 | 20 | 21 | ## WebUI 22 | * Configuration 23 | 24 | Cronix supports self-hosted web interface. By default it uses the 8111 port. In order to change it, add the following key in the app.config file into appSettings section 25 | 26 |
<add key="host.port" value="8080" /> 
27 | 28 | If the default port value is changed, then the global variable named "connectionUrl" in the webui/index.html file must also be changed. 29 | 30 |
 31 |     <!-- GLOBAL CONFIGURATION --> 
 32 |     <script type="text/javascript">
 33 |     var connectionUrl = "http://localhost:8111/signalr";
 34 |     </script>
 35 |     
36 | 37 | 38 | * Overview 39 | 40 | webui 41 | 42 | In order to enable webui, the Cronix.Web package must be installed and the WebPlugin must be initialized while bootstraping (as on snippet bellow). 43 | 44 | F# Sample 45 | *) 46 | 47 | open System 48 | open Cronix 49 | open Chessie.ErrorHandling 50 | open System.Threading 51 | open Cronix.Web 52 | 53 | [] 54 | let main argv = 55 | 56 | let startupHandler = 57 | new StartupHandler( 58 | fun(scheduler) -> 59 | WebPlugin.InitPlugin(scheduler) |> ignore 60 | ) 61 | 62 | BootStrapper.InitService(argv, startupHandler) |> ignore 63 | 0 // return an integer exit code 64 | 65 | 66 | (** 67 | 68 | C# Sample 69 | 70 | *) 71 | 72 | 73 | public static void Main(string[] args) 74 | { 75 | BootStrapper.InitService(args, scheduler => 76 | { 77 | WebPlugin.InitPlugin(scheduler); 78 | }); 79 | } 80 | 81 | (** 82 | 83 | ## Job scheduling 84 | * Build-in schedules 85 | 86 | A Build in schedule is a hardcoded schedule definition that will create a trigger for a job at each Cronix application startup. Even if that trigger will be uncheduled, it will reschedule again after restart of your application. 87 | 88 | C# Sample - build-in schedule: 89 | *) 90 | 91 | public static void Main(string[] args) 92 | { 93 | BootStrapper.InitService(args, scheduler => 94 | { 95 | scheduler.ScheduleJob("scheduled job", "* * * * *", EmbededJobs.Callback); 96 | scheduler.ScheduleJob("scheduled job", Dsl.frequency(Dsl.CronExpr.Daily), EmbededJobs.Callback); 97 | }); 98 | } 99 | 100 | (** 101 | * StartupScript 102 | 103 | Having a startup script gives the ability to add custom jobs to your application without need od recompilation of your Cronix project. 104 | Custom dll's can be referenced, and the new schedule definitions can be freely modified. 105 | The Startup.fsx file must be placed in your Cronix project (it must have 'Copy to build directory' property set to True). 106 | It must follow the namespace, module and start method convention. 107 | 108 | Sample startup script: 109 | *) 110 | 111 | #if INTERACTIVE 112 | #I "." 113 | #I "bin\Debug" 114 | #I "bin\Release" 115 | #r "Cronix.dll" 116 | #r "Chessie.dll" 117 | #endif 118 | 119 | namespace Cronix.Startup 120 | 121 | module RunAtStartup = 122 | 123 | open System.Threading 124 | open System 125 | open Cronix 126 | 127 | /// sample job 128 | let sampleJob (token : CancellationToken) = 129 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 130 | Thread.Sleep 100 131 | 132 | let start (scheduler : IScheduleManager) = 133 | 134 | // schedules goes here! 135 | scheduler.ScheduleJob "job1" "* * * * *" <| JobCallback(sampleJob) |> ignore 136 | () 137 | 138 | 139 | (** 140 | 141 | 142 | ## Windows service 143 | In order to see what are the availiable command line options, run your Cronix application in the command line without any parameters. 144 | The sample usage will be shown as in sample bellow. 145 | 146 |
147 |
148 |
149 |
150 | C:\>YourCronixApp.exe
151 | Usage:
152 | YourCronixApp debug
153 |     - Starts 'YourCronixApp' in the interactive mode.
154 | YourCronixApp install
155 |     - Installs 'YourCronixApp' as a Windows service.
156 | YourCronixApp uninstall
157 |     - Uninstalls 'YourCronixApp' as a Windows service.
158 | 
159 |
160 |
161 |
162 | 163 | 164 | 165 | *) 166 | 167 | -------------------------------------------------------------------------------- /docs/content/index.fsx: -------------------------------------------------------------------------------- 1 | (*** hide ***) 2 | // This block of code is omitted in the generated HTML documentation. Use 3 | // it to define helpers that you do not want to show in the documentation. 4 | #I "../../bin" 5 | 6 | (** 7 | Cronix 8 | ====================== 9 | 10 | # What is Cronix? 11 | Cronix is an open source job scheduling library. It can be easily used to create window services or be embedded in your windows / console applications. Cronix supports compiled and external job registration. 12 | The external ones are written in a F# start-up script as a template. They can be changed whenever you want - no recompilation of your application will be required. Job scheduling is based on CRON expressions. 13 | Cronix works well with C# and F# applications. 14 | 15 | How to install ? 16 | 17 |
18 |
19 |
20 |
21 | The Cronix library can be installed from NuGet: 22 |
PM> Install-Package Cronix
23 |
PM> Install-Package Cronix.Web
24 |
25 |
26 |
27 |
28 | 29 | 30 | Documentation 31 | -------------------------- 32 | 33 | * [The Architecture Overview](the big picture.html) 34 | 35 | * [The developer's guide ](developers guide.html) 36 | 37 | * [Samples](https://github.com/aph5nt/cronix/tree/master/docs/samples) 38 | 39 | * [API Reference](reference/index.html) contains automatically generated documentation for all types, modules 40 | and functions in the library. This includes additional brief samples on using most of the 41 | functions. 42 | 43 | Troubleshooting 44 | -------------------------- 45 | 46 | * [If FSharpCore.dll 4.3.0 was not found](http://blog.ploeh.dk/2014/01/30/how-to-use-fsharpcore-430-when-all-you-have-is-431/) 47 | 48 | 49 | Contributing and copyright 50 | -------------------------- 51 | 52 | The project is hosted on [GitHub][gh] where you can [report issues][issues], fork 53 | the project and submit pull requests. If you're adding a new public API, please also 54 | consider adding [samples][content] that can be turned into a documentation. You might 55 | also want to read the [library design notes][readme] to understand how it works. 56 | 57 | The library is available under Public Domain license, which allows modification and 58 | redistribution for both commercial and non-commercial purposes. For more information see the 59 | [License file][license] in the GitHub repository. 60 | 61 | [content]: https://github.com/fsprojects/Cronix/tree/master/docs/content 62 | [gh]: https://github.com/aph5nt/cronix 63 | [issues]: https://github.com/aph5nt/cronix/issues 64 | [readme]: https://github.com/aph5nt/cronix/blob/master/README.md 65 | [license]: https://github.com/aph5nt/cronix/blob/master/LICENSE.txt 66 | 67 | *) 68 | -------------------------------------------------------------------------------- /docs/content/settingup WebUI.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | Cronix service installation 3 | ====================== 4 | 5 | How to install your cronix service ? 6 | 7 |
8 |
9 |
10 |
11 | The Cronix library can be installed from NuGet: 12 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe install
13 |
14 |
15 |
16 |
17 | 18 | 19 | How to uninstall your cronix service ? 20 | 21 |
22 |
23 |
24 |
25 | The Cronix library can be installed from NuGet: 26 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe uninstall
27 |
28 |
29 |
30 |
31 | 32 | How to run your cronix service without installing it ? 33 | 34 |
35 |
36 |
37 |
38 | The Cronix library can be installed from NuGet: 39 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe debug
40 |
41 |
42 |
43 |
44 | 45 | How to display help ? 46 | 47 |
48 |
49 |
50 |
51 | The Cronix library can be installed from NuGet: 52 |
C:\Workplace\cronix\samples\FSharpSample\bin\debug> FSharpSample.exe typeAnythingElseHere
53 |
54 |
55 |
56 |
57 | *) 58 | -------------------------------------------------------------------------------- /docs/content/startup script.fsx: -------------------------------------------------------------------------------- 1 | (*** hide ***) 2 | // This block of code is omitted in the generated HTML documentation. Use 3 | // it to define helpers that you do not want to show in the documentation. 4 | #I "../../bin" 5 | 6 | (** 7 | Defining and configuring jobs with startup.fsx file. 8 | 9 | The startup script manual 10 | ======================== 11 | 12 | Introduction: What is startup.fsx file for? 13 | 14 | The intention of having a seperate file for configuring jobs, is to give developers the flexibility and ease of use. 15 | In the startup.fsx file, developer can configure and schedule jobs embeded in the service assembly and also reference 16 | external assemblies and create custom jobs on fly! 17 | Startup script is compiled on the Cronix service startup time. 18 | 19 | Sample script: 20 | 21 | - Lets take a look how does the minimalistic startup.fsx file looks like. 22 | *) 23 | 24 | 25 | // We reference assemblies for Visual Studio intellisense support. 26 | #if INTERACTIVE 27 | #I "." 28 | #I "bin\Debug" 29 | #I "bin\Release" 30 | #r "Cronix.dll" 31 | #r "CSharpSample.exe" // this is our executable where we had initialized Cronix service. 32 | #r "Chessie.dll" 33 | #endif 34 | 35 | /// This namespace is required. 36 | namespace Cronix.Startup 37 | 38 | /// This module is required. 39 | module RunAtStartup = 40 | 41 | open System.Threading 42 | open System 43 | open Cronix 44 | open CSharpSample.Jobs 45 | 46 | /// startup method 47 | let start (scheduler : IScheduleManager) = 48 | scheduler.Schedule "my job name" "* * * * *" <| Callback(sampleJob) |> ignore 49 | () 50 | 51 | (** 52 | and more complex example... 53 | *) 54 | 55 | #if INTERACTIVE 56 | #I "." 57 | #I "bin\Debug" 58 | #I "bin\Release" 59 | #r "Cronix.dll" 60 | #r "CSharpSample.exe" 61 | #r "CSharpSample.Jobs.dll" 62 | #r "Chessie.dll" 63 | //#r "YourTask.dll" 64 | #endif 65 | 66 | namespace Cronix.Startup 67 | 68 | module RunAtStartup = 69 | 70 | open System.Threading 71 | open System 72 | open Cronix 73 | 74 | //reference yours custom assembly here 75 | open CSharpSample 76 | open CSharpSample.Jobs 77 | 78 | // inline job 79 | let sampleJob (token : CancellationToken) = 80 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 81 | Thread.Sleep 100 82 | 83 | let start (scheduler : IScheduleManager) = 84 | scheduler.Schedule "inline job" <| "* * * * *" <| Callback(sampleJob) |> ignore 85 | scheduler.Schedule "job from external assembly" <| "* * * * *" <| Callback(ExternalJobs.Callback) |> ignore 86 | scheduler.Schedule "job defined inside the cronix service" <| "* * * * *" <| Callback(EmbededJobs.Callback) |> ignore 87 | scheduler.Schedule "with frequency helper" <| frequency Hourly <| Callback( sampleJob ) |> ignore 88 | () 89 | 90 | (** 91 | Adding Startup.fsx file to the Cronix service 92 | 93 | - Just create new Startup.fsx file inside your Cronix project root directory, type your code there (use one of these samples) and change file property 'Copy to Output Directory' to 'Copy always'. 94 | *) 95 | -------------------------------------------------------------------------------- /docs/content/the big picture.fsx: -------------------------------------------------------------------------------- 1 | (** 2 | 3 | # The Big Picture 4 | 5 | # the architecture overview 6 | 7 | ## JobCallback 8 | JobCallback represents a single unit of work which will be executed after pulling a trigger. 9 | 10 | ## Trigger 11 | Triggering is a mechanism responsible for executing JobCallback at given occurrence date. The occurrence date is calculated during trigger creation and again after each JobCallback execution. Each trigger can run only one JobCallback at time, so it is not possible to start ne JobCallback execution while previous one is still running. 12 | 13 | ## Schedule Manager 14 | Schedule Manager is the main component which communicates with the triggers. It can schedule or unschedule a new job (create or delete a trigger), enable or disable a trigger, fire or terminate execution of given trigger. Schedule Manager supervises the state of all triggers. 15 | 16 | ## Start-up script 17 | It's a F# fsx file which holds the source code for job scheduling. 18 | 19 | ##Script Compiler 20 | Compiles the Start-up script into Cronix.Startup.dll on each application start. 21 | 22 | ##Bootstrapper 23 | The bootstrapper initializes the entire Cronix library. It's responsible for service installation, compiling the start-up script, job scheduling or even hosting the webui. 24 | 25 | ## WebUI 26 | WebUI is a simple self-hosted web interface. It allows to manage the scheduled triggers. 27 | 28 | *) -------------------------------------------------------------------------------- /docs/files/img/logo-template.pdn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/docs/files/img/logo-template.pdn -------------------------------------------------------------------------------- /docs/files/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/docs/files/img/logo.png -------------------------------------------------------------------------------- /docs/files/img/overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/docs/files/img/overview.png -------------------------------------------------------------------------------- /docs/files/img/overview.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/docs/files/img/overview.pptx -------------------------------------------------------------------------------- /docs/files/img/webui.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/docs/files/img/webui.PNG -------------------------------------------------------------------------------- /docs/samples/BitstampTicker/BitstampTicker.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 2a46137f-277f-4bff-a58d-15cbf0b2a2c9 9 | Library 10 | BitstampTicker 11 | BitstampTicker 12 | v4.5 13 | 4.3.1.0 14 | BitstampTicker 15 | 7d31a91a 16 | 17 | 18 | true 19 | full 20 | false 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | 3 25 | bin\Debug\BitstampTicker.XML 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | bin\Release\ 32 | TRACE 33 | 3 34 | bin\Release\BitstampTicker.XML 35 | 36 | 37 | 11 38 | 39 | 40 | 41 | 42 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 43 | 44 | 45 | 46 | 47 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | ..\..\..\packages\FSharp.Data.2.2.2\lib\net40\FSharp.Data.dll 63 | True 64 | 65 | 66 | ..\..\..\packages\SQLProvider.0.0.9-alpha\lib\net40\FSharp.Data.SqlProvider.dll 67 | True 68 | 69 | 70 | 71 | 72 | 73 | 74 | ..\..\..\packages\System.Data.SQLite.Core.1.0.96.0\lib\net45\System.Data.SQLite.dll 75 | True 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 85 | 86 | 87 | 88 | 95 | 96 | 97 | 98 | 99 | ..\..\..\packages\Zlib.Portable\lib\portable-net4+sl5+wp8+win8+wpa81+MonoTouch+MonoAndroid\Zlib.Portable.dll 100 | True 101 | True 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /docs/samples/BitstampTicker/Exchange.fs: -------------------------------------------------------------------------------- 1 | namespace BitstampTicker 2 | 3 | open FSharp.Data 4 | open FSharp.Data.Sql 5 | 6 | module Exchange = 7 | 8 | [] 9 | let connectionString = @"Data Source=.;Initial Catalog=Bitstamp;Integrated Security=True;Pooling=False" 10 | 11 | [] 12 | let fetchUrl = "https://www.bitstamp.net/api/ticker/" 13 | 14 | [] 15 | let sample = """ {"high": "237.99", "last": "236.95", "timestamp": "1431795831", "bid": "236.85", "vwap": "236.57", "volume": "3291.57347664", "low": "235.01", "ask": "236.95"} """ 16 | 17 | type Database = SqlDataProvider 18 | type BitStamp = JsonProvider 19 | 20 | let fetchData() = 21 | BitStamp.Parse <| Http.RequestString fetchUrl 22 | 23 | let insertData() = 24 | let fetchedData = BitStamp.Parse <| Http.RequestString fetchUrl 25 | let context = Database.GetDataContext() 26 | let tick = context.``[dbo].[Ticks]``.Create() 27 | tick.Ask <- fetchedData.Ask 28 | tick.Bid <- fetchedData.Bid 29 | tick.High <- fetchedData.High 30 | tick.Last <- fetchedData.Last 31 | tick.Low <- fetchedData.Low 32 | tick.Timestamp <- fetchedData.Timestamp 33 | tick.Volume <- fetchedData.Volume 34 | tick.Vwap <- fetchedData.Vwap 35 | 36 | context.SubmitUpdates() |> ignore 37 | 38 | -------------------------------------------------------------------------------- /docs/samples/BitstampTicker/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.net. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open BitstampTicker 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /docs/samples/BitstampTicker/SqlCreate.txt: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[Ticks] ( 2 | [Id] BIGINT IDENTITY (1, 1) NOT NULL, 3 | [High] DECIMAL (18, 2) NOT NULL, 4 | [Last] DECIMAL (18, 2) NOT NULL, 5 | [Bid] DECIMAL (18, 2) NOT NULL, 6 | [Vwap] DECIMAL (18, 2) NOT NULL, 7 | [Volume] DECIMAL (18, 2) NOT NULL, 8 | [Low] DECIMAL (18, 2) NOT NULL, 9 | [Ask] DECIMAL (18, 2) NOT NULL, 10 | [Timestamp] INT NOT NULL, 11 | PRIMARY KEY CLUSTERED ([Id] DESC) 12 | ); 13 | 14 | -------------------------------------------------------------------------------- /docs/samples/BitstampTicker/Types.fs: -------------------------------------------------------------------------------- 1 | module Types 2 | 3 | // Fiat currencies 4 | [] type USD 5 | 6 | // Crypto currencies 7 | [] type BTC 8 | 9 | // Currency pairs 10 | [] type BTCUSD = BTC/USD 11 | [] type USDBTC = USD/BTC -------------------------------------------------------------------------------- /docs/samples/BitstampTicker/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /docs/samples/BitstampTicker/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Data 2 | SQLProvider -------------------------------------------------------------------------------- /docs/samples/CSharpSample.Jobs/CSharpSample.Jobs.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5C018694-E498-4971-AF52-4E4343DC38FC} 8 | Library 9 | Properties 10 | CSharpSample.Jobs 11 | CSharpSample.Jobs 12 | v4.5 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | false 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | false 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 56 | -------------------------------------------------------------------------------- /docs/samples/CSharpSample.Jobs/ExternalJobs.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace CSharpSample.Jobs 9 | { 10 | public class ExternalJobs 11 | { 12 | public static void Callback(CancellationToken token) 13 | { 14 | Console.WriteLine("executing embeded job from CSharpSample.dll at {0}", DateTime.Now); 15 | Thread.Sleep(100); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs/samples/CSharpSample.Jobs/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CSharpSample.Jobs")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CSharpSample.Jobs")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5c018694-e498-4971-af52-4e4343dc38fc")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /docs/samples/CSharpSample/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/samples/CSharpSample/NLog.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/samples/CSharpSample/Program.cs: -------------------------------------------------------------------------------- 1 | using Chessie.ErrorHandling.CSharp; 2 | using Cronix; 3 | using Microsoft.FSharp.Core; 4 | using System; 5 | using System.Linq; 6 | using System.Threading; 7 | 8 | namespace CSharpSample 9 | { 10 | public static class EmbededJobs 11 | { 12 | public static void Callback(CancellationToken token) 13 | { 14 | Console.WriteLine("executing embeded job from CSharpSample.dll at {0}", DateTime.Now); 15 | Thread.Sleep(100); 16 | } 17 | } 18 | 19 | public class Program 20 | { 21 | public static void Main(string[] args) 22 | { 23 | var result = BootStrapper.InitService(args, scheduler => 24 | { 25 | // schedule your job here 26 | scheduler.ScheduleJob("scheduled job", "* * * * *", EmbededJobs.Callback); 27 | scheduler.ScheduleJob("scheduled job", Dsl.frequency(Dsl.CronExpr.Daily), EmbededJobs.Callback); 28 | }); 29 | result.Match( 30 | (state, msgs) => 31 | { 32 | Console.WriteLine(state); 33 | }, 34 | (msgs) => 35 | { 36 | msgs.ToList().ForEach(Console.WriteLine); 37 | }); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/samples/CSharpSample/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CSharpSample")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CSharpSample")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("06322d90-9a28-423c-9805-ac1393a8f18d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /docs/samples/CSharpSample/Startup.fsx: -------------------------------------------------------------------------------- 1 | (* 2 | Best practices: 3 | - Design startup script with visual studio 4 | - reference your dll with jobs for F# interactive (while editing this script) 5 | - remember to open(import) your job dll inside RunAtStartup module 6 | *) 7 | 8 | #if INTERACTIVE 9 | #I "." 10 | #I "bin\Debug" 11 | #I "bin\Release" 12 | #r "Cronix.dll" 13 | #r "CSharpSample.exe" 14 | #r "CSharpSample.Jobs.dll" 15 | #r "Chessie.dll" 16 | //#r "YourTask.dll" 17 | #endif 18 | 19 | namespace Cronix.Startup 20 | 21 | module RunAtStartup = 22 | 23 | open System.Threading 24 | open System 25 | open Cronix 26 | open Messages 27 | 28 | //open YourJob.dll must be opened here! 29 | open CSharpSample 30 | open CSharpSample.Jobs 31 | 32 | // sample job 33 | let sampleJob (token : CancellationToken) = 34 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 35 | Thread.Sleep 100 36 | 37 | 38 | // open your dll with tasks here 39 | let start (scheduler : IScheduleManager) = 40 | 41 | // schedules goes here! 42 | scheduler.ScheduleJob "job1" "* * * * *" <| JobCallback(sampleJob) |> ignore 43 | 44 | scheduler.ScheduleJob "job2" "* * * * *" <| JobCallback(ExternalJobs.Callback) |> ignore 45 | 46 | scheduler.ScheduleJob "job3" "* * * * *" <| JobCallback(EmbededJobs.Callback) |> ignore 47 | 48 | 49 | () -------------------------------------------------------------------------------- /docs/samples/CSharpSample/paket.references: -------------------------------------------------------------------------------- 1 | NLog 2 | NCronTab 3 | chessie 4 | Microsoft.AspNet.SignalR.Core 5 | Microsoft.AspNet.SignalR.SystemWeb 6 | Microsoft.Owin 7 | Microsoft.Owin.Hosting 8 | Microsoft.Owin.Host.SystemWeb 9 | Microsoft.Owin.Security 10 | Microsoft.Owin.Host.HttpListener 11 | Owin 12 | Microsoft.Owin.Diagnostics 13 | Microsoft.Owin.Cors -------------------------------------------------------------------------------- /docs/samples/CryptoTicker/CryptoTicker.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 8460c741-3498-4835-a2a6-4209e9b96206 9 | Library 10 | CryptoTicker 11 | CryptoTicker 12 | v4.5 13 | 4.3.1.0 14 | CryptoTicker 15 | 16 | 17 | true 18 | full 19 | false 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | 3 24 | 25 | 26 | 27 | 28 | pdbonly 29 | true 30 | true 31 | bin\Release\ 32 | TRACE 33 | 3 34 | bin\Release\CryptoTicker.XML 35 | 36 | 37 | 11 38 | 39 | 40 | 41 | 42 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 43 | 44 | 45 | 46 | 47 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | True 61 | 62 | 63 | 64 | 65 | 66 | 67 | 74 | 75 | 76 | 77 | 78 | ..\..\..\packages\FSharp.Data\lib\portable-net40+sl5+wp8+win8\FSharp.Data.dll 79 | True 80 | True 81 | 82 | 83 | 84 | 85 | 86 | 87 | ..\..\..\packages\FSharp.Data\lib\net40\FSharp.Data.dll 88 | True 89 | True 90 | 91 | 92 | True 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | ..\..\..\packages\SQLProvider\lib\net40\FSharp.Data.SqlProvider.dll 102 | True 103 | True 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | ..\..\..\packages\Zlib.Portable\lib\portable-net4+sl5+wp8+win8+wpa81+MonoTouch+MonoAndroid\Zlib.Portable.dll 113 | True 114 | True 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /docs/samples/CryptoTicker/Exchange.fs: -------------------------------------------------------------------------------- 1 | namespace BitstampTicker 2 | 3 | open FSharp.Data 4 | open FSharp.Data.Sql 5 | 6 | module Exchange = 7 | 8 | [] 9 | let connectionString = @"Data Source=.;Initial Catalog=Bitstamp;Integrated Security=True;Pooling=False" 10 | 11 | [] 12 | let fetchUrl = "https://www.bitstamp.net/api/ticker/" 13 | 14 | [] 15 | let sample = """ {"high": "237.99", "last": "236.95", "timestamp": "1431795831", "bid": "236.85", "vwap": "236.57", "volume": "3291.57347664", "low": "235.01", "ask": "236.95"} """ 16 | 17 | type Database = SqlDataProvider 18 | type BitStamp = JsonProvider 19 | 20 | let insertData() = 21 | let fetchedData = BitStamp.Parse <| Http.RequestString fetchUrl 22 | let context = Database.GetDataContext() 23 | let tick = context.``[dbo].[Ticks]``.Create() 24 | tick.Exchange <- "Bitstamp" 25 | tick.Ask <- fetchedData.Ask 26 | tick.Bid <- fetchedData.Bid 27 | tick.High <- fetchedData.High 28 | tick.Last <- fetchedData.Last 29 | tick.Low <- fetchedData.Low 30 | tick.Timestamp <- fetchedData.Timestamp 31 | tick.Volume <- fetchedData.Volume 32 | tick.Vwap <- fetchedData.Vwap 33 | 34 | context.SubmitUpdates() |> ignore -------------------------------------------------------------------------------- /docs/samples/CryptoTicker/SqlCreate.txt: -------------------------------------------------------------------------------- 1 | USE [Bitstamp] 2 | GO 3 | 4 | /****** Object: Table [dbo].[Ticks] Script Date: 17/05/2015 08:46:37 ******/ 5 | SET ANSI_NULLS ON 6 | GO 7 | 8 | SET QUOTED_IDENTIFIER ON 9 | GO 10 | 11 | CREATE TABLE [dbo].[Ticks]( 12 | [Id] [bigint] IDENTITY(1,1) NOT NULL, 13 | [Exchange] [nvarchar](10) NOT NULL, 14 | [Timestamp] [int] NOT NULL, 15 | [High] [decimal](18, 2) NOT NULL, 16 | [Low] [decimal](18, 2) NOT NULL, 17 | [Last] [decimal](18, 2) NOT NULL, 18 | [Volume] [decimal](18, 2) NOT NULL, 19 | [Ask] [decimal](18, 2) NOT NULL, 20 | [Bid] [decimal](18, 2) NOT NULL, 21 | [Vwap] [decimal](18, 2) NOT NULL, 22 | CONSTRAINT [PK_Ticks_1] PRIMARY KEY NONCLUSTERED 23 | ( 24 | [Id] ASC 25 | )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY], 26 | CONSTRAINT [PK_Exchange_Timestamp] UNIQUE CLUSTERED 27 | ( 28 | [Exchange] ASC, 29 | [Timestamp] DESC 30 | )WITH (PAD_INDEX = ON, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON, FILLFACTOR = 80) ON [PRIMARY] 31 | ) ON [PRIMARY] 32 | 33 | GO 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/samples/CryptoTicker/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Data 2 | SQLProvider -------------------------------------------------------------------------------- /docs/samples/FSharpSample/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /docs/samples/FSharpSample/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpSample.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | (* General Information about an assembly is controlled through the following 8 | set of attributes. Change these attribute values to modify the information 9 | associated with an assembly. *) 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | (* Setting ComVisible to false makes the types in this assembly not visible 20 | to COM components. If you need to access a type in this assembly from 21 | COM, set the ComVisible attribute to true on that type. *) 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | (* Version information for an assembly consists of the following four values: 28 | 29 | Major Version 30 | Minor Version 31 | Build Number 32 | Revision 33 | 34 | You can specify all the values or you can default the Build and Revision Numbers 35 | by using the '*' as shown below: 36 | [] *) 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /docs/samples/FSharpSample/NLog.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/samples/FSharpSample/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | open Cronix 3 | open Chessie.ErrorHandling 4 | open System.Threading 5 | open Cronix.Web 6 | 7 | let sampleJob (token : CancellationToken) = 8 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 9 | Thread.Sleep(10000) 10 | 11 | [] 12 | let main argv = 13 | 14 | let startupHandler = 15 | new StartupHandler( 16 | fun(scheduler) -> 17 | 18 | WebPlugin.InitPlugin(scheduler) |> ignore 19 | 20 | scheduler.ScheduleJob "scheduled job" <| "* * * * *" <| JobCallback( sampleJob ) |> ignore 21 | scheduler.ScheduleJob "second name" <| frequency Hourly <| JobCallback( sampleJob ) |> ignore 22 | ) 23 | 24 | let result = BootStrapper.InitService(argv, startupHandler) 25 | match result with 26 | | Ok (state, msgs) -> printfn "%s" state 27 | | Fail msgs -> msgs |> List.iter(fun(s) -> printfn "%s" s) 28 | | _ -> () 29 | 0 // return an integer exit code 30 | -------------------------------------------------------------------------------- /docs/samples/FSharpSample/Startup.fsx: -------------------------------------------------------------------------------- 1 | #if INTERACTIVE 2 | #I "." 3 | #I "bin\Debug" 4 | #I "bin\Release" 5 | #r "Cronix.dll" 6 | #r "Chessie.dll" 7 | #r "CryptoTicker.dll" 8 | #endif 9 | 10 | namespace Cronix.Startup 11 | 12 | module RunAtStartup = 13 | 14 | open System.Threading 15 | open System 16 | open Cronix 17 | open BitstampTicker 18 | 19 | /// sample job 20 | let sampleJob (token : CancellationToken) = 21 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 22 | Thread.Sleep 100 23 | 24 | let cryptoTickerJob (token : CancellationToken) = 25 | Exchange.insertData() 26 | printf "executed cryptoTickerJob job at (UTC) %s\n" <| DateTime.UtcNow.ToString() 27 | Thread.Sleep 100 28 | 29 | /// open your dll with tasks here 30 | let start (scheduler : IScheduleManager) = 31 | 32 | // schedules goes here! 33 | scheduler.ScheduleJob "job1" "* * * * *" <| JobCallback(sampleJob) |> ignore 34 | 35 | scheduler.ScheduleJob "cryptoTickerJob" "* * * * *" <| JobCallback(cryptoTickerJob) |> ignore 36 | () 37 | -------------------------------------------------------------------------------- /docs/samples/FSharpSample/paket.references: -------------------------------------------------------------------------------- 1 | NLog 2 | NCronTab 3 | chessie 4 | Microsoft.AspNet.SignalR.Core 5 | Microsoft.AspNet.SignalR.SystemWeb 6 | Microsoft.Owin 7 | Microsoft.Owin.Hosting 8 | Microsoft.Owin.Host.SystemWeb 9 | Microsoft.Owin.Security 10 | Microsoft.Owin.Host.HttpListener 11 | Owin 12 | Microsoft.Owin.Diagnostics 13 | Microsoft.Owin.Cors 14 | 15 | FSharp.Data 16 | SQLProvider -------------------------------------------------------------------------------- /docs/tools/generate.fsx: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------------- 2 | // Builds the documentation from `.fsx` and `.md` files in the 'docs/content' directory 3 | // (the generated documentation is stored in the 'docs/output' directory) 4 | // -------------------------------------------------------------------------------------- 5 | 6 | // Web site location for the generated documentation 7 | let website = "/cronix" 8 | 9 | let githubLink = "http://github.com/aph5nt/cronix" 10 | 11 | // Specify more information about your project 12 | let info = 13 | [ "project-name", "cronix" 14 | "project-author", "aph5nt" 15 | "project-summary", "Cron Library" 16 | "project-github", githubLink 17 | "project-nuget", "http://nuget.org/packages/cronix" ] 18 | 19 | // -------------------------------------------------------------------------------------- 20 | // For typical project, no changes are needed below 21 | // -------------------------------------------------------------------------------------- 22 | 23 | #I "../../packages/FAKE/tools/" 24 | #load "../../packages/FSharp.Formatting/FSharp.Formatting.fsx" 25 | #r "NuGet.Core.dll" 26 | #r "FakeLib.dll" 27 | open Fake 28 | open System.IO 29 | open Fake.FileHelper 30 | open FSharp.Literate 31 | open FSharp.MetadataFormat 32 | 33 | // When called from 'build.fsx', use the public project URL as 34 | // otherwise, use the current 'output' directory. 35 | #if RELEASE 36 | let root = website 37 | #else 38 | let root = "file://" + (__SOURCE_DIRECTORY__ @@ "../output") 39 | #endif 40 | 41 | // Paths with template/source/output locations 42 | let bin = __SOURCE_DIRECTORY__ @@ "../../bin" 43 | let content = __SOURCE_DIRECTORY__ @@ "../content" 44 | let output = __SOURCE_DIRECTORY__ @@ "../output" 45 | let files = __SOURCE_DIRECTORY__ @@ "../files" 46 | let templates = __SOURCE_DIRECTORY__ @@ "templates" 47 | let formatting = __SOURCE_DIRECTORY__ @@ "../../packages/FSharp.Formatting/" 48 | let docTemplate = formatting @@ "templates/docpage.cshtml" 49 | 50 | // Where to look for *.csproj templates (in this order) 51 | let layoutRootsAll = new System.Collections.Generic.Dictionary() 52 | layoutRootsAll.Add("en",[ templates; formatting @@ "templates" 53 | formatting @@ "templates/reference" ]) 54 | subDirectories (directoryInfo templates) 55 | |> Seq.iter (fun d -> 56 | let name = d.Name 57 | if name.Length = 2 || name.Length = 3 then 58 | layoutRootsAll.Add( 59 | name, [templates @@ name 60 | formatting @@ "templates" 61 | formatting @@ "templates/reference" ])) 62 | 63 | // Copy static files and CSS + JS from F# Formatting 64 | let copyFiles () = 65 | CopyRecursive files output true |> Log "Copying file: " 66 | ensureDirectory (output @@ "content") 67 | CopyRecursive (formatting @@ "styles") (output @@ "content") true 68 | |> Log "Copying styles and scripts: " 69 | 70 | let references = 71 | if isMono then 72 | // Workaround compiler errors in Razor-ViewEngine 73 | let d = RazorEngine.Compilation.ReferenceResolver.UseCurrentAssembliesReferenceResolver() 74 | let loadedList = d.GetReferences () |> Seq.map (fun r -> r.GetFile()) |> Seq.cache 75 | // We replace the list and add required items manually as mcs doesn't like duplicates... 76 | let getItem name = loadedList |> Seq.find (fun l -> l.Contains name) 77 | [ (getItem "FSharp.Core").Replace("4.3.0.0", "4.3.1.0") 78 | Path.GetFullPath "./../../packages/FSharp.Compiler.Service/lib/net40/FSharp.Compiler.Service.dll" 79 | Path.GetFullPath "./../../packages/FSharp.Formatting/lib/net40/System.Web.Razor.dll" 80 | Path.GetFullPath "./../../packages/FSharp.Formatting/lib/net40/RazorEngine.dll" 81 | Path.GetFullPath "./../../packages/FSharp.Formatting/lib/net40/FSharp.Literate.dll" 82 | Path.GetFullPath "./../../packages/FSharp.Formatting/lib/net40/FSharp.CodeFormat.dll" 83 | Path.GetFullPath "./../../packages/FSharp.Formatting/lib/net40/FSharp.MetadataFormat.dll" ] 84 | |> Some 85 | else None 86 | 87 | let binaries = 88 | directoryInfo bin 89 | |> subDirectories 90 | |> Array.map (fun d -> d.FullName @@ (sprintf "%s.dll" d.Name)) 91 | |> List.ofArray 92 | 93 | let libDirs = 94 | directoryInfo bin 95 | |> subDirectories 96 | |> Array.map (fun d -> d.FullName) 97 | |> List.ofArray 98 | 99 | // Build API reference from XML comments 100 | let buildReference () = 101 | CleanDir (output @@ "reference") 102 | MetadataFormat.Generate 103 | ( binaries, output @@ "reference", layoutRootsAll.["en"], 104 | parameters = ("root", root)::info, 105 | sourceRepo = githubLink @@ "tree/master", 106 | sourceFolder = __SOURCE_DIRECTORY__ @@ ".." @@ "..", 107 | ?assemblyReferences = references, 108 | publicOnly = true,libDirs = libDirs ) 109 | 110 | // Build documentation from `fsx` and `md` files in `docs/content` 111 | let buildDocumentation () = 112 | let subdirs = Directory.EnumerateDirectories(content, "*", SearchOption.AllDirectories) 113 | for dir in Seq.append [content] subdirs do 114 | let sub = if dir.Length > content.Length then dir.Substring(content.Length + 1) else "." 115 | let langSpecificPath(lang, path:string) = 116 | path.Split([|'/'; '\\'|], System.StringSplitOptions.RemoveEmptyEntries) 117 | |> Array.exists(fun i -> i = lang) 118 | let layoutRoots = 119 | let key = layoutRootsAll.Keys |> Seq.tryFind (fun i -> langSpecificPath(i, dir)) 120 | match key with 121 | | Some lang -> layoutRootsAll.[lang] 122 | | None -> layoutRootsAll.["en"] // "en" is the default language 123 | Literate.ProcessDirectory 124 | ( dir, docTemplate, output @@ sub, replacements = ("root", root)::info, 125 | layoutRoots = layoutRoots, 126 | ?assemblyReferences = references, 127 | generateAnchors = true ) 128 | 129 | // Generate 130 | copyFiles() 131 | #if HELP 132 | buildDocumentation() 133 | #endif 134 | #if REFERENCE 135 | buildReference() 136 | #endif 137 | -------------------------------------------------------------------------------- /docs/tools/templates/template.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | @Title 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 |
24 |
25 | 28 |

@Properties["project-name"]

29 |
30 |
31 |
32 |
33 | @RenderBody() 34 |
35 |
36 | Cronix 37 | 68 |
69 |
70 |
71 | Fork me on GitHub 72 | 73 | 74 | -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://nuget.org/api/v2 2 | 3 | nuget FSharp.Formatting 4 | nuget xUnit ~> 1.9.2 5 | nuget xUnit.runners ~> 1.9.2 6 | nuget FsUnit.xUnit 7 | nuget xunit.runner.visualstudio 8 | nuget FAKE 9 | nuget SourceLink.Fake 10 | nuget NCrontab 11 | nuget NLog 12 | nuget chessie 13 | 14 | nuget FSharp.Compiler.CodeDom 15 | 16 | nuget jQuery 17 | nuget knockoutjs 18 | nuget Microsoft.AspNet.Razor 19 | nuget Microsoft.AspNet.SignalR 20 | nuget Microsoft.AspNet.SignalR.Core 21 | nuget Microsoft.AspNet.SignalR.JS 22 | nuget Microsoft.AspNet.SignalR.SystemWeb 23 | nuget Microsoft.Owin 24 | nuget Microsoft.Owin.Hosting 25 | nuget Microsoft.Owin.Host.SystemWeb 26 | nuget Microsoft.Owin.Host.HttpListener 27 | nuget Microsoft.Owin.Security 28 | nuget Nancy 29 | nuget Nancy.Owin 30 | nuget Nancy.Viewengines.Razor 31 | nuget Newtonsoft.Json 32 | nuget Owin 33 | nuget Microsoft.Owin.Diagnostics 34 | nuget Microsoft.Owin.Cors 35 | nuget Foq 36 | 37 | nuget FSharp.Data 38 | nuget SQLProvider 39 | nuget System.Data.SQLite 40 | 41 | github fsharp/FAKE modules/Octokit/Octokit.fsx -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | NUGET 2 | remote: https://nuget.org/api/v2 3 | specs: 4 | Chessie (0.1.1) 5 | FSharp.Core 6 | EntityFramework (6.1.3) - framework: net40, >= net45, >= net451 7 | FAKE (3.31.4) 8 | Foq (1.7) 9 | FSharp.Compiler.CodeDom (0.9.2) 10 | FSharp.Compiler.Service (0.0.89) 11 | FSharp.Core (3.1.2.1) 12 | FSharp.Data (2.2.2) 13 | Zlib.Portable (>= 1.10.0) - framework: portable-net40+sl50+wp80+win80 14 | FSharp.Formatting (2.9.6) 15 | FSharp.Compiler.Service (>= 0.0.87) 16 | FSharpVSPowerTools.Core (1.8.0) 17 | FSharpVSPowerTools.Core (1.8.0) 18 | FSharp.Compiler.Service (>= 0.0.87) 19 | FsUnit.xUnit (1.3.0.1) 20 | xunit (>= 1.9.1 < 3.0) - framework: net20, >= net40, >= net45 21 | jQuery (2.1.4) 22 | knockoutjs (3.3.0) 23 | Microsoft.AspNet.Cors (5.2.3) 24 | Microsoft.AspNet.Razor (3.2.3) 25 | Microsoft.AspNet.SignalR (2.2.0) 26 | Microsoft.AspNet.SignalR.JS (>= 2.2.0) 27 | Microsoft.AspNet.SignalR.SystemWeb (>= 2.2.0) 28 | Microsoft.AspNet.SignalR.Core (2.2.0) 29 | Microsoft.Owin (>= 2.1.0) 30 | Microsoft.Owin.Security (>= 2.1.0) 31 | Newtonsoft.Json (>= 6.0.4) 32 | Owin (>= 1.0) 33 | Microsoft.AspNet.SignalR.JS (2.2.0) 34 | jQuery (>= 1.6.4) 35 | Microsoft.AspNet.SignalR.SystemWeb (2.2.0) 36 | Microsoft.AspNet.SignalR.Core (>= 2.2.0) 37 | Microsoft.Owin.Host.SystemWeb (>= 2.1.0) 38 | Microsoft.Bcl (1.1.10) 39 | Microsoft.Bcl.Build (>= 1.0.14) 40 | Microsoft.Bcl.Build (1.0.21) 41 | Microsoft.Net.Http (2.2.29) 42 | Microsoft.Bcl (>= 1.1.10) 43 | Microsoft.Bcl.Build (>= 1.0.14) 44 | Microsoft.Owin (3.0.1) 45 | Owin (>= 1.0) 46 | Microsoft.Owin.Cors (3.0.1) 47 | Microsoft.AspNet.Cors (>= 5.0.0) 48 | Microsoft.Owin (>= 3.0.1) 49 | Owin (>= 1.0) 50 | Microsoft.Owin.Diagnostics (3.0.1) 51 | Microsoft.Owin (>= 3.0.1) 52 | Owin (>= 1.0) 53 | Microsoft.Owin.Host.HttpListener (3.0.1) 54 | Microsoft.Owin.Host.SystemWeb (3.0.1) 55 | Microsoft.Owin (>= 3.0.1) 56 | Owin (>= 1.0) 57 | Microsoft.Owin.Hosting (3.0.1) 58 | Microsoft.Owin (>= 3.0.1) 59 | Owin (>= 1.0) 60 | Microsoft.Owin.Security (3.0.1) 61 | Microsoft.Owin (>= 3.0.1) 62 | Owin (>= 1.0) 63 | Nancy (1.2.0) 64 | Nancy.Owin (1.2.0) 65 | Nancy (>= 1.2.0) 66 | Owin (>= 1.0) 67 | Nancy.Viewengines.Razor (1.2.0) 68 | Microsoft.AspNet.Razor 69 | Microsoft.AspNet.Razor (>= 2.0.30506) - framework: >= net40 70 | Nancy (>= 1.2.0) 71 | ncrontab (2.0.0) 72 | Newtonsoft.Json (6.0.8) 73 | NLog (3.2.1) 74 | Octokit (0.11.0) 75 | Microsoft.Net.Http 76 | Owin (1.0) 77 | SourceLink.Fake (0.5.0) 78 | SQLProvider (0.0.9-alpha) 79 | System.Data.SQLite (1.0.96.0) 80 | System.Data.SQLite.Core (>= 1.0.96.0) - framework: net20, net40, >= net45, >= net451 81 | System.Data.SQLite.EF6 (>= 1.0.96.0) - framework: net40, >= net45, >= net451 82 | System.Data.SQLite.Linq (>= 1.0.96.0) - framework: net20, net40, >= net45, >= net451 83 | System.Data.SQLite.Core (1.0.96.0) - framework: net20, net40, >= net45, >= net451 84 | System.Data.SQLite.EF6 (1.0.96.0) - framework: net40, >= net45, >= net451 85 | EntityFramework (>= 6.1.2.0) - framework: net40, >= net45, >= net451 86 | System.Data.SQLite.Linq (1.0.96.0) - framework: net20, net40, >= net45, >= net451 87 | xunit (1.9.2) 88 | xunit.abstractions (2.0.0) 89 | xunit.assert (2.0.0) 90 | xunit.core (2.0.0) 91 | xunit.extensibility.core (2.0.0) 92 | xunit.extensibility.core (2.0.0) 93 | xunit.abstractions (2.0.0) 94 | xunit.runner.visualstudio (2.0.0) 95 | xunit.runners (1.9.2) 96 | Zlib.Portable (1.10.0) - framework: portable-net40+sl50+wp80+win80 97 | GITHUB 98 | remote: fsharp/FAKE 99 | specs: 100 | modules/Octokit/Octokit.fsx (3f9b431915b7a568217d257f50693a3fdc1c7ebc) 101 | Octokit -------------------------------------------------------------------------------- /src/Cronix.UI/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/bower_modules" 3 | } 4 | -------------------------------------------------------------------------------- /src/Cronix.UI/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | bower_modules/ 4 | 5 | # Don't track build output 6 | dist/ 7 | -------------------------------------------------------------------------------- /src/Cronix.UI/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/Cronix.UI/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cronix-ui", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "crossroads": "~0.12.0", 7 | "hasher": "~1.2.0", 8 | "requirejs": "~2.1.11", 9 | "requirejs-text": "~2.0.10", 10 | "knockout": "~3.3.0-alpha", 11 | "knockout-projections": "~1.1.0", 12 | "moment": "~2.10.2" 13 | }, 14 | "resolutions": { 15 | "knockout": "~3.3.0-alpha" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Cronix.UI/gulpfile.js: -------------------------------------------------------------------------------- 1 | // Node modules 2 | var fs = require('fs'), vm = require('vm'), merge = require('deeply'), chalk = require('chalk'), es = require('event-stream'); 3 | var output = 'dist'; 4 | 5 | // Gulp and plugins 6 | var gulp = require('gulp'), rjs = require('gulp-requirejs-bundler'), concat = require('gulp-concat'), clean = require('gulp-clean'), 7 | replace = require('gulp-replace'), uglify = require('gulp-uglify'), htmlreplace = require('gulp-html-replace'); 8 | 9 | // Config 10 | var requireJsRuntimeConfig = vm.runInNewContext(fs.readFileSync('src/app/require.config.js') + '; require;'); 11 | requireJsOptimizerConfig = merge(requireJsRuntimeConfig, { 12 | out: 'scripts.js', 13 | baseUrl: './src', 14 | name: 'app/startup', 15 | paths: { 16 | requireLib: 'bower_modules/requirejs/require' 17 | }, 18 | include: [ 19 | 'requireLib', 20 | 'components/nav-bar/nav-bar', 21 | 'components/job-preview/job-preview' 22 | ], 23 | insertRequire: ['app/startup'], 24 | bundles: { 25 | // If you want parts of the site to load on demand, remove them from the 'include' list 26 | // above, and group them into bundles here. 27 | // 'bundle-name': [ 'some/module', 'another/module' ], 28 | // 'another-bundle-name': [ 'yet-another-module' ] 29 | } 30 | }); 31 | 32 | // Discovers all AMD dependencies, concatenates together all required .js files, minifies them 33 | gulp.task('js', function () { 34 | return rjs(requireJsOptimizerConfig) 35 | .pipe(uglify({ preserveComments: 'some' })) 36 | .pipe(gulp.dest(output)); 37 | }); 38 | 39 | // Concatenates CSS files, rewrites relative paths to Bootstrap fonts, copies Bootstrap fonts 40 | gulp.task('css', function () { 41 | var bowerCss = gulp.src('src/css/bootstrap.css').pipe(replace(/url\((')?\.\/fonts\//g, 'url($1fonts/')), 42 | appCss = gulp.src('src/css/*.css'), 43 | combinedCss = es.concat(bowerCss, appCss).pipe(concat('css.css')), 44 | fontFiles = gulp.src('./src/css/fonts/*', { base: './src/css/' }); 45 | 46 | return es.concat(combinedCss, fontFiles) 47 | .pipe(gulp.dest(output)); 48 | }); 49 | 50 | // Copies index.html, replacing 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 49 | 50 | 68 | 69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 77 |
78 | 79 |
80 | 81 |
82 | 83 |
84 | 85 | 86 | 87 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /src/Cronix.UI/test/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_modules" 3 | } 4 | -------------------------------------------------------------------------------- /src/Cronix.UI/test/SpecRunner.browser.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | // Reference your test modules here 3 | var testModules = [ 4 | 'components/home-page' 5 | ]; 6 | 7 | // After the 'jasmine-boot' module creates the Jasmine environment, load all test modules then run them 8 | require(['jasmine-boot'], function () { 9 | var modulesCorrectedPaths = testModules.map(function(m) { return '../test/' + m; }); 10 | require(modulesCorrectedPaths, window.onload); 11 | }); 12 | })(); 13 | -------------------------------------------------------------------------------- /src/Cronix.UI/test/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "0.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "jasmine": "~2.0.0", 7 | "requirejs": "~2.1.11" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Cronix.UI/test/components/home-page.js: -------------------------------------------------------------------------------- 1 | define(['components/home-page/home'], function(homePage) { 2 | var HomePageViewModel = homePage.viewModel; 3 | 4 | describe('Home page view model', function() { 5 | 6 | it('should supply a friendly message which changes when acted upon', function() { 7 | var instance = new HomePageViewModel(); 8 | expect(instance.message()).toContain('Welcome to '); 9 | 10 | // See the message change 11 | instance.doSomething(); 12 | expect(instance.message()).toContain('You invoked doSomething()'); 13 | }); 14 | 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /src/Cronix.UI/test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spec Runner 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Cronix.UI/test/require.config.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | // Resolve all AMD modules relative to the 'src' directory, to produce the 3 | // same behavior that occurs at runtime 4 | require.baseUrl = '../src/'; 5 | 6 | // It's not obvious, but this is a way of making Jasmine load and run in an AMD environment 7 | // Credit: http://stackoverflow.com/a/20851265 8 | var jasminePath = '../test/bower_modules/jasmine/lib/jasmine-core/'; 9 | require.paths['jasmine'] = jasminePath + 'jasmine'; 10 | require.paths['jasmine-html'] = jasminePath + 'jasmine-html'; 11 | require.paths['jasmine-boot'] = jasminePath + 'boot'; 12 | require.shim['jasmine'] = { exports: 'window.jasmineRequire' }; 13 | require.shim['jasmine-html'] = { deps: ['jasmine'], exports: 'window.jasmineRequire' }; 14 | require.shim['jasmine-boot'] = { deps: ['jasmine', 'jasmine-html'], exports: 'window.jasmineRequire' }; 15 | })(); 16 | -------------------------------------------------------------------------------- /src/Cronix.UI/vwd.webinfo: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/Cronix.Web/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace System 2 | open System.Reflection 3 | 4 | [] 5 | [] 6 | [] 7 | [] 8 | [] 9 | do () 10 | 11 | module internal AssemblyVersionInformation = 12 | let [] Version = "0.3.2" 13 | -------------------------------------------------------------------------------- /src/Cronix.Web/Hubs.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Web 2 | 3 | /// Module responsible for signalr hubs. 4 | module Hubs = 5 | open Microsoft.AspNet.SignalR.Hubs 6 | open Microsoft.AspNet.SignalR 7 | open Cronix 8 | 9 | [] 10 | type ScheduleManagerHub() as self = 11 | inherit Hub() 12 | 13 | // TODO: Service locator antipattern, but it works just for now... 14 | let scheduleManager = GlobalHost.DependencyResolver.Resolve(typeof) :?> IScheduleManager 15 | let state() = scheduleManager.TriggerDetails |> Seq.toArray 16 | 17 | do 18 | scheduleManager.OnTriggerStateChanged.AddHandler(fun sender args -> self.onStateChangedHandler args) 19 | 20 | member private self.onStateChangedHandler args = 21 | base.Clients.All.OnStateChanged(args) 22 | 23 | member self.GetData() = 24 | base.Clients.All.GetData(state()) |> ignore 25 | 26 | member self.EnableTrigger(name : TriggerName) = 27 | scheduleManager.EnableTrigger(name) |> ignore 28 | 29 | member self.FireTrigger(name : TriggerName) = 30 | scheduleManager.FireTrigger(name) |> ignore 31 | 32 | member self.DisableTrigger(name : TriggerName) = 33 | scheduleManager.DisableTrigger(name) |> ignore 34 | 35 | member self.TerminateTrigger(name : TriggerName) = 36 | scheduleManager.TerminateTriggerExecution(name) |> ignore 37 | 38 | -------------------------------------------------------------------------------- /src/Cronix.Web/WebPlugin.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Web 2 | 3 | open Logging 4 | open System 5 | open Owin 6 | open Microsoft.AspNet.SignalR 7 | open Microsoft.Owin.Hosting 8 | open Microsoft.Owin.Cors 9 | 10 | /// Module responsible for webui plugin functionality. 11 | module WebPlugin = 12 | open Cronix 13 | 14 | let logger = logger() 15 | 16 | /// Plugin implementation. 17 | let InitPlugin : InitPlugin = 18 | fun(scheduleManager) -> 19 | try 20 | let options = new StartOptions() 21 | options.Port <- new Nullable(AppSettings.port 8111) 22 | let config = new HubConfiguration(EnableDetailedErrors = true) 23 | config.EnableJSONP <- true 24 | 25 | WebApp.Start(options, 26 | fun(app : Owin.IAppBuilder) -> ( 27 | GlobalHost.DependencyResolver.Register(typeof, fun() -> scheduleManager :> obj) 28 | app.UseCors(CorsOptions.AllowAll) |> ignore 29 | Owin.OwinExtensions.MapSignalR(app, "/signalr", config) |> ignore 30 | app.UseNancy() |> ignore 31 | )) |> ignore 32 | with 33 | | exn -> logger.Error(exn) 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/Cronix.Web/Website.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Web 2 | 3 | /// Module responsible for nancy settings. 4 | module Website = 5 | 6 | open System 7 | open Nancy.Conventions 8 | open Nancy.TinyIoc 9 | open Nancy.Bootstrapper 10 | open Nancy 11 | 12 | /// Nancy web boostrapper 13 | type WebBootstrapper() = 14 | inherit DefaultNancyBootstrapper() 15 | 16 | override x.ApplicationStartup( container : TinyIoCContainer, pipelines : IPipelines) = 17 | let impl viewName model content = 18 | String.Concat("webui/", viewName) 19 | let convestion() = 20 | Func(impl) 21 | base.Conventions.ViewLocationConventions.Add( convestion() ) 22 | 23 | override x.ConfigureConventions(conventions : NancyConventions) = 24 | conventions.StaticContentsConventions.Add(StaticContentConventionBuilder.AddDirectory("/", "webui")) 25 | base.ConfigureConventions(conventions) 26 | 27 | /// WebUi nancy module 28 | type WebUiModule() as self = 29 | inherit NancyModule() 30 | do 31 | self.Get.["/"] <- fun _ -> self.Index() 32 | 33 | member self.Index() = 34 | base.View.["index.html"] :> obj -------------------------------------------------------------------------------- /src/Cronix.Web/paket.references: -------------------------------------------------------------------------------- 1 | NLog 2 | NCronTab 3 | chessie 4 | Microsoft.AspNet.SignalR.Core 5 | Microsoft.AspNet.SignalR.SystemWeb 6 | Microsoft.Owin 7 | Microsoft.Owin.Hosting 8 | Microsoft.Owin.Host.SystemWeb 9 | Microsoft.Owin.Security 10 | Microsoft.Owin.Host.HttpListener 11 | Owin 12 | Microsoft.Owin.Diagnostics 13 | Microsoft.Owin.Cors 14 | Nancy 15 | Nancy.Owin 16 | -------------------------------------------------------------------------------- /src/Cronix.Web/paket.template: -------------------------------------------------------------------------------- 1 | type project 2 | owners 3 | aph5nt 4 | authors 5 | aph5nt 6 | projectUrl 7 | http://github.com/aph5nt/Cronix 8 | iconUrl 9 | https://raw.githubusercontent.com/aph5nt/Cronix/master/docs/files/img/logo.png 10 | licenseUrl 11 | http://github.com/aph5nt/Cronix/blob/master/LICENSE.txt 12 | requireLicenseAcceptance 13 | false 14 | copyright 15 | Copyright 2015 16 | tags 17 | cronix ui cron ncron ncrontab scheduler service 18 | summary 19 | Cronix Web Interface 20 | description 21 | Cronix Web Interface 22 | files 23 | ../Cronix.UI/dist/index.html ==> content/webui 24 | ../Cronix.UI/dist/scripts.js ==> content/webui 25 | ../Cronix.UI/dist/css.css ==> content/webui 26 | ../Cronix.UI/dist/font/fontawesome-webfont.eot ==> content/webui/font 27 | ../Cronix.UI/dist/font/fontawesome-webfont.svg ==> content/webui/font 28 | ../Cronix.UI/dist/font/fontawesome-webfont.svgz ==> content/webui/font 29 | ../Cronix.UI/dist/font/fontawesome-webfont.ttf ==> content/webui/font 30 | ../Cronix.UI/dist/font/fontawesome-webfont.woff ==> content/webui/font 31 | ../Cronix.UI/dist/font/fontawesome-webfontd41d.eot ==> content/webui/font 32 | ../Cronix.UI/dist/font/FontAwesome.otf ==> content/webui/font 33 | ../../Install.ps1 ==> tools 34 | ../Cronix.UI/readme.txt ==> content -------------------------------------------------------------------------------- /src/Cronix/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Cronix/AppSettingProvider.fs: -------------------------------------------------------------------------------- 1 | module FSharp.Configuration.AppSettingsTypeProvider 2 | 3 | #nowarn "57" 4 | 5 | open FSharp.Configuration.Helper 6 | open ProviderImplementation.ProvidedTypes 7 | open System 8 | open System.Configuration 9 | open System.Collections.Generic 10 | open System.Globalization 11 | open System.Web.Hosting 12 | 13 | let private getConfig() = 14 | if HostingEnvironment.IsHosted then 15 | Web.Configuration.WebConfigurationManager.OpenWebConfiguration "~" 16 | else ConfigurationManager.OpenExeConfiguration ConfigurationUserLevel.None 17 | 18 | let getConfigValue key = 19 | let settings = getConfig().AppSettings.Settings 20 | settings.[key].Value 21 | 22 | let setConfigValue (key, value) = 23 | let config = getConfig() 24 | config.AppSettings.Settings.[key].Value <- value 25 | config.Save() 26 | 27 | let getConnectionString (key: string) = 28 | getConfig().ConnectionStrings.ConnectionStrings.[key].ConnectionString 29 | 30 | let setConnectionString (key: string, value) = 31 | let config = getConfig() 32 | config.ConnectionStrings.ConnectionStrings.[key].ConnectionString <- value 33 | config.Save() 34 | 35 | let internal typedAppSettings (context: Context) = 36 | let appSettings = erasedType thisAssembly rootNamespace "AppSettings" 37 | 38 | appSettings.DefineStaticParameters( 39 | parameters = [ProvidedStaticParameter("configFileName", typeof)], 40 | instantiationFunction = (fun typeName parameterValues -> 41 | let typedConnectionStrings (config:Configuration, filePath, configFileName) = 42 | let typeDef = ProvidedTypeDefinition("ConnectionStrings", Some typeof, HideObjectMethods = true) 43 | typeDef.AddXmlDoc (sprintf "Represents the available connection strings from %s" configFileName) 44 | let names = HashSet() 45 | 46 | let connectionStrings = config.ConnectionStrings.ConnectionStrings 47 | for connectionString in connectionStrings do 48 | let key = connectionString.Name 49 | let name = niceName names key 50 | let prop = 51 | ProvidedProperty(name, typeof, 52 | GetterCode = (fun _ -> <@@ getConnectionString key @@>), 53 | SetterCode = fun args -> <@@ setConnectionString(key, %%args.[0]) @@>) 54 | 55 | prop.IsStatic <- true 56 | prop.AddXmlDoc (sprintf "Returns the connection string from %s with name %s" configFileName name) 57 | prop.AddDefinitionLocation(1,1,filePath) 58 | typeDef.AddMember prop 59 | 60 | typeDef 61 | 62 | 63 | match parameterValues with 64 | | [| :? string as configFileName |] -> 65 | let typeDef = erasedType thisAssembly rootNamespace typeName 66 | let names = HashSet() 67 | try 68 | let filePath = findConfigFile context.ResolutionFolder configFileName 69 | let fileMap = ExeConfigurationFileMap(ExeConfigFilename=filePath) 70 | let config = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None) 71 | let appSettings = config.AppSettings.Settings 72 | 73 | for key in appSettings.AllKeys do 74 | let name = niceName names key 75 | let prop = 76 | match (appSettings.Item key).Value with 77 | | ValueParser.Uri _ -> 78 | ProvidedProperty(name, typeof, 79 | GetterCode = (fun _ -> <@@ Uri (getConfigValue key) @@>), 80 | SetterCode = fun args -> <@@ setConfigValue(key, string (%%args.[0]: Uri)) @@>) 81 | | ValueParser.Int _ -> 82 | ProvidedProperty(name, typeof, 83 | GetterCode = (fun _ -> <@@ Int32.Parse (getConfigValue key) @@>), 84 | SetterCode = fun args -> <@@ setConfigValue(key, string (%%args.[0]: Int32)) @@>) 85 | | ValueParser.Bool _ -> 86 | ProvidedProperty(name, typeof, 87 | GetterCode = (fun _ -> <@@ Boolean.Parse (getConfigValue key) @@>), 88 | SetterCode = fun args -> <@@ setConfigValue(key, string (%%args.[0]: Boolean)) @@>) 89 | | ValueParser.Float _ -> 90 | ProvidedProperty(name, typeof, 91 | GetterCode = (fun _ -> <@@ Double.Parse (getConfigValue key, NumberStyles.Any, CultureInfo.InvariantCulture) @@>), 92 | SetterCode = fun args -> <@@ setConfigValue(key, string (%%args.[0]: float)) @@>) 93 | | ValueParser.TimeSpan _ -> 94 | ProvidedProperty(name, typeof, 95 | GetterCode = (fun _ -> <@@ TimeSpan.Parse(getConfigValue key, CultureInfo.InvariantCulture) @@>), 96 | SetterCode = fun args -> <@@ setConfigValue(key, string (%%args.[0]: TimeSpan)) @@>) 97 | | ValueParser.DateTime _ -> 98 | ProvidedProperty(name, typeof, 99 | GetterCode = (fun _ -> <@@ DateTime.Parse(getConfigValue key, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal) @@>), 100 | SetterCode = fun args -> <@@ setConfigValue(key, (%%args.[0]: DateTime).ToString("o")) @@>) 101 | | _ -> 102 | ProvidedProperty(name, typeof, 103 | GetterCode = (fun _ -> <@@ getConfigValue key @@>), 104 | SetterCode = fun args -> <@@ setConfigValue(key, %%args.[0]) @@>) 105 | 106 | prop.IsStatic <- true 107 | prop.AddXmlDoc (sprintf "Returns the value from %s with key %s" configFileName key) 108 | prop.AddDefinitionLocation(1, 1, filePath) 109 | 110 | typeDef.AddMember prop 111 | 112 | let prop = 113 | ProvidedProperty(niceName names "ConfigFileName", typeof, GetterCode = fun _ -> <@@ filePath @@>) 114 | 115 | prop.IsStatic <- true 116 | prop.AddXmlDoc "Returns the Filename" 117 | typeDef.AddMember prop 118 | 119 | let connectionStringTypeDefinition = typedConnectionStrings (config, filePath, configFileName) 120 | typeDef.AddMember connectionStringTypeDefinition 121 | 122 | context.WatchFile filePath 123 | typeDef 124 | with _ -> typeDef 125 | | x -> failwithf "unexpected parameter values %A" x)) 126 | 127 | appSettings -------------------------------------------------------------------------------- /src/Cronix/AppSettings.fs: -------------------------------------------------------------------------------- 1 | /// Module responsible for getting configuration settings. 2 | [] 3 | module AppSettings 4 | open System 5 | open System.Configuration 6 | 7 | let port (defaultPort: int) = 8 | if ConfigurationManager.AppSettings.["host.port"] <> null then ConfigurationManager.AppSettings.["host.port"] |> Int32.Parse else defaultPort 9 | -------------------------------------------------------------------------------- /src/Cronix/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace System 2 | open System.Reflection 3 | 4 | [] 5 | [] 6 | [] 7 | [] 8 | [] 9 | do () 10 | 11 | module internal AssemblyVersionInformation = 12 | let [] Version = "0.3.2" 13 | -------------------------------------------------------------------------------- /src/Cronix/BootStrapper.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix 2 | 3 | open System.ServiceProcess 4 | open System.IO 5 | open Logging 6 | open System 7 | 8 | /// An adapter responsible for starting, stopping and shutting down the cronix service. 9 | type ServiceProcessAdapter(service : IScheduleManager, setup) = 10 | inherit ServiceBase() 11 | let logger = logger() 12 | do 13 | (* Set default directory for windows service *) 14 | Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory) 15 | 16 | ///Starts the ScheduleManager and performs the manager setup 17 | override x.OnStart(args : string[]) = 18 | logger.Debug("starting service") 19 | service.StartManager() 20 | //WebHost.run(service) |> ignore 21 | setup() 22 | 23 | /// Manually starts the schedulemanager 24 | member x.Start() = 25 | x.OnStart(null) 26 | 27 | /// Stops the ScheduleManager 28 | override x.OnStop() = 29 | logger.Debug("stopping service") 30 | service.StopManager() 31 | 32 | /// Stops the ScheduleManager 33 | override x.OnShutdown() = 34 | logger.Debug("shutting down service") 35 | service.StopManager() 36 | 37 | /// Module responsible for cronix service initialization. 38 | module BootStrapper = 39 | 40 | open Chessie.ErrorHandling 41 | open Logging 42 | open Compiler 43 | open System.IO 44 | open System 45 | open System.ServiceProcess 46 | open System.Reflection 47 | open System.Diagnostics 48 | 49 | /// Boostrapper module specific logger. 50 | let logger = logger() 51 | 52 | /// Invokes the compiled startup script. 53 | let invokeStartupScript(state : StartupScriptState) = 54 | try 55 | match state.compiled with 56 | | None -> fail(sprintf "Compile code has not been craeted.") 57 | | Some assembly -> let startMethod = assembly.GetType("Cronix.Startup.RunAtStartup").GetMethod("start") 58 | let startInvoke (scheduler : IScheduleManager) = startMethod.Invoke(null, [|box (scheduler:IScheduleManager);|]) :?> unit 59 | startInvoke(state.scheduleManager) 60 | ok state 61 | with 62 | | exn -> fail(sprintf "Failed to run startup script. %s" exn.Message) 63 | 64 | /// Invokes the startup handler. 65 | let invokeStartupHandler (state: StartupHandlerState) = 66 | match state.startupHandler with 67 | | None -> ok state 68 | | Some handler -> 69 | try 70 | handler.Invoke(state.scheduleManager) 71 | ok state 72 | with 73 | | exn -> fail(sprintf "Failed to run a startup handler. %s" exn.Message) 74 | 75 | /// Setups up the cronix service 76 | let setupService(scheduleManager : IScheduleManager) (startupHandler : Option) = 77 | 78 | compileSetup() 79 | 80 | let startupScriptState scheduleManager referencedAssemblies source = 81 | { StartupScriptState.scheduleManager = scheduleManager; 82 | referencedAssemblies = referencedAssemblies; 83 | source = source; 84 | compiled = None; 85 | } 86 | let startupHandlerState scheduleManager startupHandler = 87 | { StartupHandlerState.scheduleManager = scheduleManager; startupHandler = startupHandler;} 88 | 89 | startupHandlerState 90 | ok scheduleManager 91 | <*> ok startupHandler 92 | >>= invokeStartupHandler 93 | |> logResult 94 | |> ignore 95 | 96 | if File.Exists(startupFile) = true then 97 | startupScriptState 98 | ok scheduleManager 99 | <*> loadAssemblies() 100 | <*> getStartupScript() 101 | 102 | >>= compileScript 103 | >>= invokeStartupScript 104 | |> logResult 105 | |> ignore 106 | 107 | /// Runs the cronix service. If its run in console mode then the service will start immediately. If not then the ServiceProcessAdapter will start. 108 | let runService(startupHandler : Option) debug = 109 | try 110 | AppDomain.CurrentDomain.UnhandledException.AddHandler(fun(_) (args:UnhandledExceptionEventArgs) -> logger.Fatal(args.ExceptionObject)) 111 | let scheduleManager = new ScheduleManager() :> IScheduleManager 112 | let setup() = 113 | setupService scheduleManager startupHandler |> ignore 114 | use processAdapter = new ServiceProcessAdapter(scheduleManager, setup) 115 | if debug = true then 116 | processAdapter.Start() |> ignore 117 | Console.Read() |> ignore 118 | processAdapter.Stop() 119 | else 120 | ServiceBase.Run(processAdapter) 121 | 122 | with 123 | | exn -> logger.Fatal(exn) 124 | 125 | /// Prints the user guide. 126 | let printGuide() = 127 | let entryAsm = Assembly.GetEntryAssembly() 128 | let executingAsm = Assembly.GetExecutingAssembly() 129 | let assemblyName = if entryAsm <> null then entryAsm.GetName().Name else executingAsm.GetName().Name 130 | printfn "Usage:" 131 | printfn "%s debug" assemblyName 132 | printfn " - Starts '%s' in the interactive mode." assemblyName 133 | printfn "%s install" assemblyName 134 | printfn " - Installs '%s' as a Windows service." assemblyName 135 | printfn "%s uninstall" assemblyName 136 | printfn " - Uninstalls '%s' as a Windows service." assemblyName 137 | 138 | /// Returns true if service is run from console. 139 | let isDebug() = Environment.UserInteractive 140 | 141 | /// Changes the Some(null) into None value. 142 | let parseOption (input : Option<'a>) = 143 | match input with 144 | | None -> None 145 | | Some(null) -> None 146 | | Some(a') -> Some(a') 147 | 148 | /// Initializes the cronix service 149 | let InitService : InitService = 150 | fun (args, startupHandler) -> 151 | let args' = parseOption(Some(args)) 152 | let startupHandler' = parseOption(Some(startupHandler)) 153 | 154 | if args'.IsSome && args'.Value.Length = 0 && isDebug() = false then 155 | runService startupHandler' <| isDebug() 156 | ok("runService") 157 | 158 | else if args'.IsSome && args'.Value.Length = 1 then 159 | match args'.Value.[0] with 160 | | "install" -> ProjectInstaller.Install(Assembly.GetEntryAssembly()) 161 | | "uninstall" -> ProjectInstaller.Uninstall(Assembly.GetEntryAssembly()) 162 | | "debug" -> runService startupHandler' <| isDebug() 163 | ok("runService") 164 | | _ -> printGuide() 165 | ok("printGuide") 166 | else 167 | printGuide() 168 | ok("printGuide") -------------------------------------------------------------------------------- /src/Cronix/BootStrapper.fsi: -------------------------------------------------------------------------------- 1 | namespace Cronix 2 | 3 | open Chessie.ErrorHandling 4 | 5 | module BootStrapper = 6 | val InitService : Option * Option -> Result 7 | -------------------------------------------------------------------------------- /src/Cronix/Compiler.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix 2 | 3 | open System 4 | open Logging 5 | 6 | /// Module responsible for compiling the startup.fsx script. 7 | [] 8 | module Compiler = 9 | 10 | open System.Reflection 11 | open System.IO 12 | open Chessie.ErrorHandling 13 | open FSharp.Compiler.CodeDom 14 | open System.CodeDom.Compiler 15 | 16 | /// Statup script name constant. 17 | let startupFile = "Startup.fsx" 18 | 19 | /// Output assembly name constant. 20 | let outputAssembly = "Cronix.Startup.dll" 21 | 22 | /// Compiler module specific logger. 23 | let private logger = logger() 24 | 25 | /// Scans for assemblies in the cronix service directory. Returns assembly name list. 26 | let loadAssemblies() = 27 | let assemblies = 28 | Directory.GetFiles(".", "*.dll", SearchOption.TopDirectoryOnly) 29 | |> Seq.map(fun(f) -> Path.GetFileName(f)) 30 | |> Seq.filter ((<>)outputAssembly) 31 | |> Seq.sort 32 | |> Seq.toArray 33 | let defaultAssemblies = 34 | let entry = Assembly.GetEntryAssembly() 35 | let executing = Assembly.GetExecutingAssembly() 36 | let calling = Assembly.GetCallingAssembly() 37 | [|"System.dll"; 38 | (if entry <> null then Path.GetFileName(entry.Location) else ""); 39 | (if executing <> null then Path.GetFileName(executing.Location) else ""); 40 | (if calling <> null then Path.GetFileName(calling.Location) else ""); 41 | |] 42 | |> Seq.distinct |> Seq.toArray 43 | let asm = Array.append assemblies defaultAssemblies 44 | |> Array.toSeq 45 | |> Seq.filter ((<>)"") 46 | |> Seq.distinct 47 | |> Seq.toArray 48 | 49 | ok asm 50 | 51 | /// cleans up the working directory 52 | let compileSetup() = 53 | try 54 | if File.Exists(outputAssembly) then 55 | File.SetAttributes(outputAssembly, FileAttributes.Normal) 56 | File.Delete(outputAssembly) 57 | with 58 | | exn -> failwith(exn.Message) 59 | 60 | 61 | /// Loads the startup script. 62 | let getStartupScript() = 63 | try 64 | let sourceCode = File.ReadAllText("Startup.fsx") 65 | ok sourceCode 66 | with 67 | | exn -> fail(sprintf "Failed to load source code from the starup script. %s" exn.Message) 68 | 69 | /// Compiles the startup script. 70 | let compileScript(state : StartupScriptState) = 71 | try 72 | let provider = new FSharpCodeProvider() 73 | let params'= CompilerParameters() 74 | 75 | params'.GenerateExecutable <- false 76 | params'.OutputAssembly <- IO.Path.Combine(System.Environment.CurrentDirectory, outputAssembly) 77 | params'.IncludeDebugInformation <- true 78 | params'.ReferencedAssemblies.AddRange(state.referencedAssemblies) 79 | params'.GenerateInMemory <- false 80 | params'.WarningLevel <- 3 81 | params'.TreatWarningsAsErrors <- false 82 | 83 | let compiledResults = provider.CompileAssemblyFromSource(params', state.source) 84 | 85 | if compiledResults.Errors.Count > 0 then 86 | let errors = Array.init compiledResults.Errors.Count (fun(_)-> new CompilerError()) 87 | compiledResults.Errors.CopyTo(errors, 0) 88 | let errorMessage = seq { for error in errors do yield error.ErrorText} |> Seq.toArray 89 | let errorMessages = Array.append [|"Failed to compile startup script."|] errorMessage 90 | fail(String.Join("\n", errorMessages)) 91 | else 92 | let asm = Some(compiledResults.CompiledAssembly) 93 | ok { state with compiled = asm} 94 | with 95 | | exn -> fail(sprintf "Failed to compile startup script. %s" exn.Message) -------------------------------------------------------------------------------- /src/Cronix/Cronix.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 1ab7e0b7-c56e-4a50-b5fa-7ad04f56c17c 9 | Library 10 | Cronix 11 | Cronix 12 | v4.5 13 | 4.3.1.0 14 | Cronix 15 | 16 | 17 | 18 | true 19 | full 20 | false 21 | false 22 | .\bin\Debug 23 | DEBUG;TRACE 24 | 3 25 | 26 | 27 | 28 | 29 | pdbonly 30 | true 31 | true 32 | .\bin\Release 33 | TRACE 34 | 3 35 | .\bin\Release\Cronix.xml 36 | 37 | 38 | 11 39 | 40 | 41 | 42 | 43 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 44 | 45 | 46 | 47 | 48 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 49 | 50 | 51 | 52 | 53 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Always 76 | 77 | 78 | Always 79 | 80 | 81 | 82 | 83 | ..\..\packages\FSharp.Compiler.CodeDom\lib\net40\FSharp.Compiler.CodeDom.dll 84 | 85 | 86 | 87 | False 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | ..\..\packages\Chessie\lib\net40\Chessie.dll 101 | True 102 | True 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | ..\..\packages\ncrontab\lib\net20\NCrontab.dll 112 | True 113 | True 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | ..\..\packages\NLog\lib\net35\NLog.dll 123 | True 124 | True 125 | 126 | 127 | 128 | 129 | 130 | 131 | ..\..\packages\NLog\lib\net40\NLog.dll 132 | True 133 | True 134 | 135 | 136 | 137 | 138 | 139 | 140 | ..\..\packages\NLog\lib\sl4\NLog.dll 141 | True 142 | True 143 | 144 | 145 | 146 | 147 | 148 | 149 | ..\..\packages\NLog\lib\sl5\NLog.dll 150 | True 151 | True 152 | 153 | 154 | 155 | 156 | 157 | 158 | ..\..\packages\NLog\lib\net45\NLog.dll 159 | True 160 | True 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/Cronix/FSharp.Core.optdata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/src/Cronix/FSharp.Core.optdata -------------------------------------------------------------------------------- /src/Cronix/FSharp.Core.sigdata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aph5nt/cronix/0ed71e23c9d52eff2685888efc1884a7c2603aeb/src/Cronix/FSharp.Core.sigdata -------------------------------------------------------------------------------- /src/Cronix/Installer.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix 2 | 3 | /// Module responsible for installing, uninstalling and starting up the cronix service 4 | module ProjectInstaller = 5 | 6 | open System.Collections 7 | open System.Configuration.Install 8 | open System.Reflection 9 | open System.ServiceProcess 10 | open Chessie.ErrorHandling 11 | open Logging 12 | open System.IO 13 | open System 14 | 15 | /// Project installer module specific logger 16 | let logger = logger() 17 | 18 | /// Performs required initialization steps in order to install or uninstall the cronix service 19 | let initialize (installer : Installer) (assembly : Assembly) = 20 | let serviceName = assembly.GetName().Name 21 | let serviceProcessInstaller = new ServiceProcessInstaller() 22 | serviceProcessInstaller.Account <- ServiceAccount.LocalService 23 | installer.Installers.Add(serviceProcessInstaller) |> ignore 24 | 25 | let serviceInstaller = new ServiceInstaller() 26 | serviceInstaller.ServiceName <- serviceName 27 | serviceInstaller.DisplayName <- serviceName 28 | serviceInstaller.Description <- "Executes scheduled jobs." 29 | serviceInstaller.StartType <- ServiceStartMode.Automatic 30 | installer.Installers.Add(serviceInstaller) |> ignore 31 | 32 | installer.Context <- new InstallContext() 33 | installer.Context.Parameters.["assemblypath"] <- assembly.Location; 34 | 35 | /// Installs the cronix service. An installation rollback will be done on failure. 36 | let Install : Install = 37 | fun(assembly) -> 38 | let installer = new Installer() 39 | initialize installer assembly 40 | 41 | let mutable state = new Hashtable() 42 | try 43 | installer.Install(state) 44 | installer.Commit(state) 45 | installer.Dispose() 46 | ok("installed") 47 | with 48 | | exn -> 49 | try 50 | installer.Rollback(state) 51 | installer.Dispose() 52 | fail("rollback: " + exn.Message) 53 | with 54 | | ex -> fail("failed to rollback: " + ex.Message) 55 | 56 | /// Uninstalls the cronix service. 57 | let Uninstall : Uninstall = 58 | fun(assembly) -> 59 | try 60 | let installer = new Installer() 61 | initialize installer assembly 62 | installer.Uninstall(null); 63 | installer.Dispose() 64 | ok("uninstalled") 65 | with 66 | | ex -> fail("failed to uninstall: " + ex.Message) 67 | 68 | -------------------------------------------------------------------------------- /src/Cronix/Logging.fs: -------------------------------------------------------------------------------- 1 | /// Module responsible for handling logging 2 | module Logging 3 | 4 | open NLog 5 | open System 6 | open Chessie.ErrorHandling 7 | 8 | /// Returns new logger instance. 9 | let logger() = 10 | let caller = System.Diagnostics.StackTrace(1, false).GetFrames().[1].GetMethod().DeclaringType 11 | LogManager.GetLogger(caller.Name) 12 | 13 | /// Logs the messages on success or failure Tee 14 | let logResult result : Result<_,string> = 15 | 16 | let caller = System.Diagnostics.StackTrace(1, false).GetFrames().[0].GetMethod().DeclaringType 17 | let logger = LogManager.GetLogger(caller.Name) 18 | 19 | result 20 | |> successTee (fun (obj, msgs) -> msgs |> List.iter (fun (m:string) -> logger.Debug(m))) 21 | |> ignore 22 | result 23 | |> failureTee (fun (msgs) -> msgs |> List.rev |> List.iter (fun (m:string) -> logger.Error(m))) 24 | |> ignore 25 | result -------------------------------------------------------------------------------- /src/Cronix/Startup.fsx: -------------------------------------------------------------------------------- 1 | (* 2 | Best practices: 3 | - Design startup script with visual studio 4 | - reference your dll with jobs for F# interactive (while editing this script) 5 | - remember to open(import) your job dll inside RunAtStartup module 6 | *) 7 | 8 | #if INTERACTIVE 9 | #I "." 10 | #I "bin\Debug" 11 | #I "bin\Release" 12 | #r "Cronix.dll" 13 | #r "Chessie.dll" 14 | //#r "YourTask.dll" 15 | #endif 16 | 17 | namespace Cronix.Startup 18 | 19 | module RunAtStartup = 20 | 21 | open System.Threading 22 | open System 23 | open Cronix 24 | 25 | //open YourJob.dll must be opened here! 26 | 27 | // sample job 28 | let sampleJob (token : CancellationToken) = 29 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 30 | Thread.Sleep 100 31 | 32 | 33 | // open your dll with tasks here 34 | let start (scheduler : IScheduleManager) = 35 | 36 | // schedules goes here! 37 | scheduler.Schedule "job1" "* * * * *" <| JobCallback(sampleJob) |> ignore 38 | () -------------------------------------------------------------------------------- /src/Cronix/Triggers.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix 2 | 3 | /// Module responsible for triggering the jobs. 4 | module Triggers = 5 | 6 | open System.Threading 7 | open NCrontab 8 | open System 9 | open Logging 10 | 11 | /// Trigger module specific logger. 12 | let logger = logger() 13 | 14 | /// Calculates the timer arguments. 15 | let calculateTimerArgs : CalculateTimerArgs = 16 | fun(expr, date, occurrenceAt) -> 17 | let nextOccurence = CrontabSchedule.Parse(expr).GetNextOccurrence occurrenceAt 18 | let due = int (occurrenceAt - date).TotalMilliseconds 19 | let interval = int (nextOccurence - occurrenceAt).TotalMilliseconds 20 | logger.Debug("Calculating TimerArgs: due = <%d>; interval = <%d>.", due, interval) 21 | (due, interval) 22 | 23 | /// Calculates the occurance at time. 24 | let calculatetOccurrenceAt : CalculatetOccurrenceAt = 25 | fun(expr, date) -> 26 | CrontabSchedule.Parse(expr).GetNextOccurrence date 27 | 28 | /// Invokes the callback function. 29 | let timerCallback : Cronix.TimerCallback = 30 | fun(name, callback, token) -> 31 | try 32 | token.ThrowIfCancellationRequested() |> ignore 33 | logger.Debug("Executing Trigger<'{0}'>", name) 34 | callback.Invoke token 35 | logger.Debug("Trigger<'{0}'> has been executed.", name) 36 | with 37 | | :? OperationCanceledException -> logger.Debug("Trigger<'{0}'> has been cancelled", name) 38 | | _ as ex -> logger.Error("Trigger<'{0}'> failed", name, ex) 39 | 40 | /// Mannages the job execution. 41 | type Trigger(name: string, expr : string, jobCallback : JobCallback) as x = 42 | let mutable triggerState = Stopped 43 | let mutable occurrenceAt = DateTime.UtcNow 44 | let mutable isDisposed = false 45 | 46 | let stateChanged = Event<(TriggerName * TriggerState)>() 47 | let stateChangedHandler = new Handler<(TriggerName * TriggerState)>(fun sender args -> 48 | let _, state = args 49 | triggerState <- state) 50 | do 51 | stateChanged.Publish.AddHandler(stateChangedHandler) 52 | 53 | let tokenSource = new CancellationTokenSource() 54 | 55 | let timer = new System.Threading.Timer(fun _ -> 56 | stateChanged.Trigger(name, TriggerState.Executing) 57 | occurrenceAt <- CrontabSchedule.Parse(expr).GetNextOccurrence DateTime.UtcNow 58 | timerCallback(name, jobCallback, tokenSource.Token) 59 | stateChanged.Trigger(name, TriggerState.Idle) 60 | ) 61 | 62 | // internal method to cleanup resources 63 | let cleanup(disposing : bool) = 64 | if not isDisposed then 65 | isDisposed <- true 66 | if disposing then 67 | tokenSource.Cancel() |> ignore 68 | timer.Dispose() |> ignore 69 | stateChanged.Trigger(name, TriggerState.Terminated) 70 | logger.Debug("Trigger<'{0}'> has been disposed.", name) 71 | 72 | interface IDisposable with 73 | member x.Dispose() = 74 | cleanup(true) 75 | GC.SuppressFinalize(x) 76 | 77 | interface ITrigger with 78 | member x.State 79 | with get () = triggerState 80 | 81 | member x.OccurrenceAt 82 | with get () = occurrenceAt 83 | 84 | member x.Name 85 | with get() = name 86 | 87 | member x.CronExpr 88 | with get() = expr 89 | 90 | member x.OnStateChanged 91 | with get() = stateChanged.Publish 92 | 93 | member x.Start() = 94 | if isDisposed = false then 95 | occurrenceAt <- calculatetOccurrenceAt(expr, DateTime.UtcNow) 96 | let due, interval = calculateTimerArgs(expr, DateTime.UtcNow, occurrenceAt) 97 | timer.Change(due, interval) |> ignore 98 | stateChanged.Trigger(name, TriggerState.Idle) 99 | logger.Debug("Trigger<'{0}'> started. dueTime = {1}; interval '{2}'.", name, due, interval) 100 | 101 | member x.Stop() = 102 | if isDisposed = false then 103 | timer.Change(Timeout.Infinite, Timeout.Infinite) |> ignore 104 | stateChanged.Trigger(name, TriggerState.Stopped) 105 | logger.Debug("Trigger<'{0}'> stopped.", name) 106 | 107 | member x.Terminate() = 108 | if isDisposed = false then 109 | tokenSource.Cancel() |> ignore 110 | stateChanged.Trigger(name, TriggerState.Terminated) 111 | logger.Debug("Trigger<'{0}'> terminated.", name) 112 | 113 | member x.Fire() = 114 | if isDisposed = false then 115 | if triggerState = TriggerState.Idle then 116 | timer.Change(0, Timeout.Infinite) |> ignore 117 | logger.Debug("Trigger<'{0}'> has been fired.", name) 118 | let due, interval = calculateTimerArgs(expr, DateTime.UtcNow, occurrenceAt) 119 | timer.Change(due, interval) |> ignore 120 | 121 | member x.PublishRemovedEvent() = 122 | stateChanged.Trigger(name, TriggerState.Removed) 123 | logger.Debug("Trigger<'{0}'> published removed event.", name) -------------------------------------------------------------------------------- /src/Cronix/paket.references: -------------------------------------------------------------------------------- 1 | NLog 2 | NCronTab 3 | chessie 4 | -------------------------------------------------------------------------------- /src/Cronix/paket.template: -------------------------------------------------------------------------------- 1 | type project 2 | owners 3 | aph5nt 4 | authors 5 | aph5nt 6 | projectUrl 7 | http://github.com/aph5nt/Cronix 8 | iconUrl 9 | https://raw.githubusercontent.com/aph5nt/Cronix/master/docs/files/img/logo.png 10 | licenseUrl 11 | http://github.com/aph5nt/Cronix/blob/master/LICENSE.txt 12 | requireLicenseAcceptance 13 | false 14 | copyright 15 | Copyright 2015 16 | tags 17 | cron ncron ncrontab scheduler service 18 | summary 19 | F# / C# Cron Service 20 | description 21 | Simple Cron Library / Service 22 | 23 | 24 | -------------------------------------------------------------------------------- /tests/Cronix.Tests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /tests/Cronix.Tests/AppSettingsTests.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Tests 2 | 3 | open Xunit 4 | open FsUnit.Xunit 5 | 6 | module AppSettingsTests = 7 | 8 | [] 9 | let ``Read host port setting default``() = 10 | AppSettings.port 9090 |> should not' (equal 9090) 11 | 12 | [] 13 | let ``Read host port setting``() = 14 | AppSettings.port 9090 |> should equal 8080 -------------------------------------------------------------------------------- /tests/Cronix.Tests/SchedulingTests.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Tests 2 | 3 | open System 4 | open System.Threading 5 | open System.Collections.Generic 6 | open Xunit 7 | open FsUnit.Xunit 8 | open Cronix 9 | open Scheduling 10 | open Chessie.ErrorHandling 11 | 12 | 13 | [] 14 | type SchedulingTests() = 15 | let jobName1 = "jobname1" 16 | let exprArgValid = "* * * * *" 17 | let exprArgInValid = "0/15 * * * * *" 18 | let mutable state = new ScheduleState() 19 | 20 | let compareResults (actual : Result<'TSuccess, 'TMessage>) (expected : Result<'TSuccess, 'TMessage>) = 21 | actual |> should equal expected 22 | 23 | let callback (token : CancellationToken) = 24 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 25 | Thread.Sleep 300 26 | 27 | [] 28 | let ``validate exprArgValid expression``() = 29 | let params' = (jobName1, exprArgValid, JobCallback(callback)) 30 | let actual = validateExpr(state, params') 31 | let expected = SuccesMessage(state, params') ExpressionValidated [exprArgValid] 32 | compareResults actual expected |> ignore 33 | 34 | [] 35 | let ``validate exprArgInValid expression``() = 36 | let params' = (jobName1, exprArgInValid, JobCallback(callback)) 37 | let actual = validateExpr(state, params') 38 | let expected = FailureMessage InvalidCronExpr [exprArgInValid] 39 | compareResults actual expected |> ignore 40 | 41 | 42 | [] 43 | let ``can add trigger``() = 44 | let params' = (jobName1, exprArgValid, JobCallback(callback)) 45 | let actual = canAddTrigger(state, params') 46 | let expected = SuccesMessage(state, params') TriggerCanBeAdded [jobName1] 47 | compareResults actual expected |> ignore 48 | 49 | state.Add(jobName1, createTrigger(jobName1, exprArgValid, JobCallback(callback))) 50 | 51 | let actual2 = canAddTrigger(state, params') 52 | let expected2 = FailureMessage TriggerExists [jobName1] 53 | compareResults actual2 expected2 |> ignore 54 | 55 | [] 56 | let ``add trigger``() = 57 | let params' = (jobName1, exprArgValid, JobCallback(callback)) 58 | let actual = addTrigger(state, params') 59 | let expected = SuccesMessage(state) TriggerAdded [jobName1] 60 | compareResults actual expected |> ignore 61 | 62 | [] 63 | let ``fire trigger``() = 64 | let params' = (jobName1, exprArgValid, JobCallback(callback)) 65 | addTrigger(state, params') |> ignore 66 | let actual = fireTrigger(state, jobName1) 67 | let expected = SuccesMessage(state) TriggerFired [jobName1] 68 | compareResults actual expected |> ignore 69 | 70 | 71 | [] 72 | let ``terminate trigger``() = 73 | let params' = (jobName1, exprArgValid, JobCallback(callback)) 74 | addTrigger(state, params') |> ignore 75 | fireTrigger(state, jobName1) |> ignore 76 | let actual = terminateTrigger(state, jobName1) 77 | let expected = SuccesMessage(state) TriggerTerminated [jobName1] 78 | compareResults actual expected |> ignore 79 | 80 | [] 81 | let ``trigger should do the cleanup when disposing``() = 82 | let params' = (jobName1, exprArgValid, JobCallback(callback)) 83 | addTrigger(state, params') |> ignore 84 | state.[jobName1].State |> should equal Idle 85 | state.[jobName1].Dispose() 86 | state.[jobName1].State |> should equal Terminated 87 | 88 | [] 89 | type ScheduleManagerTests() = 90 | let manager = new ScheduleManager() :> IScheduleManager 91 | let mutable triggerState = Idle 92 | let stateChangedHandler = new Handler<(TriggerDetail)>(fun sender args -> triggerState <- args.TriggerState) 93 | let sampleJob (token : CancellationToken) = 94 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 95 | Thread.Sleep 100 96 | do 97 | manager.StartManager() 98 | 99 | [] 100 | let ``schedule a job``() = 101 | let result = manager.ScheduleJob "job1" 102 | <| "* * * * *" 103 | <| JobCallback(sampleJob) 104 | 105 | match result with 106 | | Ok (_, msgs) -> 107 | msgs |> should contain "Expr <[* * * * *]> is valid." 108 | msgs |> should contain "Trigger <[job1]> can be added." 109 | msgs |> should contain "Trigger <[job1]> has been added." 110 | | _ -> failwith "Expected Success Tee" 111 | 112 | 113 | [] 114 | let ``schedule not the same job twice``() = 115 | manager.ScheduleJob "job1" 116 | <| "* * * * *" 117 | <| JobCallback(sampleJob) 118 | |> ignore 119 | 120 | let result = manager.ScheduleJob "job1" "* * * * *" <| JobCallback(sampleJob) 121 | match result with 122 | | Fail msgs -> msgs |> should contain "Job <[job1]> already exists." 123 | | _ -> failwith "Expected Failure Tee" 124 | 125 | [] 126 | let ``schedule not job with invalid expression``() = 127 | let result = manager.ScheduleJob "job1" 128 | <| "a * * * *" 129 | <| JobCallback(sampleJob) 130 | 131 | match result with 132 | | Fail msgs -> msgs |> should contain "Expr <[a * * * *]> is not valid." 133 | | _ -> failwith "Expected Failure Tee" 134 | 135 | [] 136 | let ``unschedule a job``() = 137 | manager.ScheduleJob "job1" 138 | <| "* * * * *" 139 | <| JobCallback(sampleJob) 140 | |> ignore 141 | 142 | let result = manager.UnScheduleJob "job1" 143 | match result with 144 | | Ok (_, msgs) -> 145 | msgs |> should contain "Trigger <[job1]> found." 146 | msgs |> should contain "Trigger <[job1]> has been removed." 147 | | _ -> failwith "Expected Success Tee" 148 | 149 | [] 150 | let ``unschedule not existing job``() = 151 | let result = manager.UnScheduleJob "job1" 152 | match result with 153 | | Fail msgs -> msgs |> should contain "Job <[job1]> does not exists." 154 | | _ -> failwith "Expected Failure Tee" 155 | 156 | [] 157 | let ``enable and disable trigger``() = 158 | manager.ScheduleJob "job1" 159 | <| "* * * * *" 160 | <| JobCallback(sampleJob) 161 | |> ignore 162 | 163 | let stopped = manager.DisableTrigger("job1") 164 | match stopped with 165 | | Ok (_, msgs) -> 166 | msgs |> should contain "Trigger <[job1]> has been disabled." 167 | | _ -> failwith "Expected Success Tee" 168 | 169 | let result = manager.EnableTrigger("job1") 170 | match result with 171 | | Ok (_, msgs) -> 172 | msgs |> should contain "Trigger <[job1]> has been enabled." 173 | | _ -> failwith "Expected Success Tee" 174 | 175 | [] 176 | let ``get trigger details``() = 177 | manager.ScheduleJob "job1" 178 | <| "* * * * *" 179 | <| JobCallback(sampleJob) 180 | |> ignore 181 | 182 | manager.TriggerDetails |> Seq.toArray |> Array.find(fun(i) -> i.Name = "job1") |> should not' Null 183 | 184 | [] 185 | let ``fire trigger``() = 186 | manager.ScheduleJob "job1" 187 | <| "* * * * *" 188 | <| JobCallback(sampleJob) 189 | |> ignore 190 | let result = manager.FireTrigger("job1") 191 | match result with 192 | | Ok (_, msgs) -> 193 | msgs |> should contain "Trigger <[job1]> has been fired." 194 | | _ -> failwith "Expected Success Tee" 195 | 196 | [] 197 | let ``terminate trigger execution``() = 198 | manager.ScheduleJob "job1" 199 | <| "* * * * *" 200 | <| JobCallback(sampleJob) 201 | |> ignore 202 | manager.FireTrigger("job1") |> ignore 203 | 204 | let result = manager.TerminateTriggerExecution("job1") 205 | match result with 206 | | Ok (_, msgs) -> 207 | msgs |> should contain "Trigger <[job1]> has been terminated." 208 | | _ -> failwith "Expected Success Tee" 209 | 210 | [] 211 | let ``handle OnTriggerStateChanged event``() = 212 | triggerState <- Terminated 213 | manager.OnTriggerStateChanged.AddHandler(stateChangedHandler) 214 | manager.ScheduleJob "job1" 215 | <| "* * * * *" 216 | <| JobCallback(sampleJob) 217 | |> ignore 218 | manager.FireTrigger("job1") |> ignore 219 | Thread.Sleep(1000) 220 | triggerState |> should equal Idle 221 | 222 | 223 | interface IDisposable with 224 | member x.Dispose() = manager.StopManager() -------------------------------------------------------------------------------- /tests/Cronix.Tests/ServiceEnviroment.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Tests 2 | 3 | open System.Reflection 4 | open System.ServiceProcess 5 | open System.Threading 6 | 7 | type DummyType() = 8 | class 9 | end 10 | 11 | module ServiceEnviroment = 12 | open System 13 | open System.IO 14 | open System.Diagnostics 15 | open System.Security 16 | 17 | let sourcePath = "../../../../docs/samples/CSharpSample/bin/debug" 18 | let enviromentPath = "../../../../envs" 19 | let executable = "CSharpSample.exe" 20 | 21 | let rec deleteDirectory targetPath = 22 | let target = new DirectoryInfo(targetPath) 23 | if target.Exists then 24 | seq { for directory in target.EnumerateDirectories() -> directory } 25 | |> Seq.iter(fun(dir) -> deleteDirectory dir.FullName) 26 | 27 | let copyExecutable source target = 28 | let rec copyAll (source : DirectoryInfo) (target : DirectoryInfo) = 29 | if target.Exists = false then 30 | target.Create() |> ignore 31 | for file in source.GetFiles() do 32 | let path = Path.Combine(target.FullName, file.Name) 33 | file.CopyTo(path, true) |> ignore 34 | for dirSourceSub in source.GetDirectories() do 35 | let nexttargetSubDir = target.CreateSubdirectory(dirSourceSub.Name) 36 | copyAll dirSourceSub nexttargetSubDir 37 | 38 | let copy source target = 39 | let dirSource = new DirectoryInfo(source) 40 | let dirTarget = new DirectoryInfo(target) 41 | copyAll dirSource dirTarget 42 | 43 | copy source target 44 | 45 | let deleteLogDir targetPath = 46 | let logDir = IO.Path.Combine(targetPath, "logs") 47 | if Directory.Exists logDir then 48 | Directory.EnumerateFiles(logDir) 49 | |> Seq.iter (fun(f) -> File.Delete f) |> ignore 50 | 51 | let getTargetPath enviromentName = 52 | let uri = new Uri(Assembly.GetAssembly(typedefof).CodeBase) 53 | let location = Path.GetDirectoryName(uri.LocalPath) 54 | let targetPath = Path.Combine(location, enviromentPath, enviromentName) 55 | let target = new DirectoryInfo(targetPath) 56 | target.FullName 57 | 58 | 59 | 60 | let isServiceInstalled serviceName = 61 | let service = ServiceController.GetServices() |> Array.tryFind(fun(s: System.ServiceProcess.ServiceController) -> s.ServiceName = serviceName) 62 | match service with 63 | | Some s -> true 64 | | _ -> false 65 | 66 | let executeCmd args = 67 | let process' = new Process() 68 | let startInfo = new ProcessStartInfo() 69 | startInfo.WindowStyle <- System.Diagnostics.ProcessWindowStyle.Hidden; 70 | startInfo.FileName <- "cmd.exe" 71 | startInfo.Arguments <- args 72 | startInfo.RedirectStandardOutput <- true 73 | startInfo.RedirectStandardInput <- true 74 | startInfo.UseShellExecute <- false 75 | 76 | process'.StartInfo <- startInfo 77 | process'.Start() |> ignore 78 | process'.BeginOutputReadLine() |> ignore 79 | process'.OutputDataReceived.Add(fun(args) -> 80 | Console.WriteLine("executeCmd: {0} {1}", args.Data, DateTime.Now)) 81 | Thread.Sleep(1000) |> ignore 82 | 83 | let startService() = 84 | if isServiceInstalled "CSharpSample" then 85 | executeCmd "/C sc start CSharpSample" 86 | 87 | let uninstallService() = 88 | if isServiceInstalled "CSharpSample" then 89 | executeCmd "/C sc stop CSharpSample" 90 | executeCmd "/C sc delete CSharpSample" 91 | 92 | let loadUserPassword() = 93 | let password = File.ReadAllText("../../../../password.txt") 94 | let secured = new SecureString() 95 | for c in password.ToCharArray() do secured.AppendChar(c) 96 | secured 97 | 98 | let createEnviroment enviromentName = 99 | uninstallService() 100 | let targetPath = getTargetPath enviromentName 101 | deleteDirectory targetPath 102 | copyExecutable sourcePath targetPath 103 | deleteLogDir targetPath 104 | 105 | let createEnviromentForRollbackWithFailure = 106 | uninstallService() 107 | let name = "rollback" 108 | createEnviroment name 109 | File.Delete("FSharp.Scheduler.dll") 110 | name -------------------------------------------------------------------------------- /tests/Cronix.Tests/ServiceSystemTest.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Tests 2 | 3 | open FsUnit.Xunit 4 | open Xunit 5 | open Cronix 6 | open Chessie.ErrorHandling 7 | open System 8 | open System.Threading 9 | open System.Collections.Generic 10 | open System.Diagnostics 11 | open ServiceEnviroment 12 | open System.IO 13 | 14 | [] 15 | type SetupServiceTest() = 16 | 17 | [] 18 | let ``load assemblies returns an array of dll names``() = 19 | loadAssemblies() 20 | |> successTee (fun(obj : string[], _) -> obj |> Array.iter (fun(m:string) -> Console.WriteLine(m))) 21 | |> ignore 22 | () 23 | 24 | [] 25 | let ``get startup script returns success``() = 26 | if IO.File.Exists("Startup.fsx.bak") then IO.File.Move("Startup.fsx.bak", "Startup.fsx") 27 | match getStartupScript() with 28 | | Ok (script, msgs) -> script |> should not' NullOrEmptyString 29 | | Fail msgs -> failwith "Expected Success Tee" 30 | | _ -> () 31 | 32 | 33 | [] 34 | let ``get startup script returns failure``() = 35 | if IO.File.Exists("Startup.fsx") then IO.File.Move("Startup.fsx", "Startup.fsx.bak") 36 | match getStartupScript() with 37 | | Ok (_, _) -> failwith "Expected Failure Tee" 38 | | Fail msgs -> IO.File.Move("Startup.fsx.bak", "Startup.fsx") 39 | | _ -> () 40 | 41 | 42 | [] 43 | let ``comlipe script returns success``() = 44 | 45 | let assemblies = 46 | [| 47 | "Chessie.dll"; 48 | "Cronix.Tests.dll"; 49 | "Cronix.dll"; 50 | "FSharp.Compiler.CodeDom.dll"; 51 | "FSharp.Core.dll"; 52 | "FsUnit.CustomMatchers.dll"; 53 | "FsUnit.Xunit.dll"; 54 | "NCrontab.dll"; 55 | "NHamcrest.dll"; 56 | "NLog.dll"; 57 | "xunit.abstractions.dll"; 58 | "xunit.dll"; 59 | "xunit.runner.utility.desktop.dll"; 60 | "xunit.runner.visualstudio.testadapter.dll"; 61 | "System.dll"; 62 | |] 63 | 64 | let result = compileScript({ source = IO.File.ReadAllText("Startup.fsx"); 65 | compiled = None; 66 | referencedAssemblies = assemblies; 67 | scheduleManager = new ScheduleManager() }) 68 | 69 | match result with 70 | | Ok (state, msgs) -> () 71 | | Fail msgs -> failwith "Expected Success Tee" 72 | | _ -> () 73 | 74 | [] 75 | let ``compile script returns failure when exception is thrown``() = 76 | let result = compileScript({ source = IO.File.ReadAllText("Startup.fsx"); 77 | compiled = None; 78 | referencedAssemblies = [||]; 79 | scheduleManager = new ScheduleManager() }) 80 | match result with 81 | | Ok (_, _) -> failwith "Expected Failure Tee" 82 | | Fail msgs -> msgs.[0] |> should startWith "Failed to compile startup script." 83 | msgs.[0] 84 | 85 | 86 | [] 87 | type ServiceSystemTest() = 88 | let result = new List() 89 | let mutable process' : Process = null 90 | do 91 | uninstallService() 92 | 93 | let createProcess (enviromentName) (parameter) = 94 | let targetPath = getTargetPath enviromentName 95 | let executable = System.IO.Path.Combine(targetPath, executable) 96 | let startInfo = new ProcessStartInfo() 97 | startInfo.WindowStyle <- System.Diagnostics.ProcessWindowStyle.Normal 98 | startInfo.FileName <- executable 99 | if parameter <> null then startInfo.Arguments <- parameter 100 | startInfo.RedirectStandardOutput <- true 101 | startInfo.RedirectStandardInput <- true 102 | startInfo.UseShellExecute <- false 103 | 104 | process' <- Process.Start(startInfo) 105 | process'.BeginOutputReadLine() |> ignore 106 | process'.OutputDataReceived.Add(fun(args) -> 107 | Console.WriteLine("event: {0} {1}", args.Data, DateTime.Now) 108 | result.Add(args.Data)) 109 | 110 | let exit() = 111 | process'.StandardInput.WriteLine("exit") |> ignore 112 | 113 | let wait() = process'.WaitForExit(10000) |> ignore 114 | 115 | let rec searchInLogFile enviroment message till = 116 | Thread.Sleep(200) |> ignore 117 | if till > DateTime.UtcNow then 118 | let dir = getTargetPath enviroment 119 | let logName = sprintf "%s.log" <| DateTime.Now.ToString("yyyy-MM-dd") 120 | let fileName = Path.Combine(dir, "logs", logName) 121 | if File.Exists fileName then 122 | let content = File.ReadAllText(fileName) 123 | if content.Contains(message) = false then searchInLogFile enviroment message till 124 | else 125 | searchInLogFile enviroment message till 126 | else failwith <| sprintf "message '%s' not found." message 127 | 128 | [] 129 | let ``Run in debug mode``() = 130 | createEnviroment "debugEnv" 131 | createProcess "debugEnv" "debug" 132 | Thread.Sleep(10000) 133 | exit() 134 | wait() 135 | result |> should contain "runService" 136 | 137 | [] 138 | let ``Install service``() = 139 | createEnviroment "serviceEnv" 140 | createProcess "serviceEnv" "install" 141 | wait() 142 | result |> should contain "installed" 143 | 144 | [] 145 | let ``Run installed service``() = 146 | createEnviroment "serviceEnv" 147 | createProcess "serviceEnv" "install" 148 | Thread.Sleep(5000) 149 | wait() 150 | result |> should contain "installed" 151 | startService() 152 | searchInLogFile "serviceEnv" "Trigger<'job1'> has been executed." <| DateTime.UtcNow.AddMinutes 2.0 153 | 154 | [] 155 | let ``Rollback instalation on installation failure``() = 156 | createEnviroment "serviceEnv" 157 | createProcess "serviceEnv" "install" 158 | wait() 159 | createProcess "serviceEnv" "install" 160 | wait() 161 | result |> should contain "rollback: The specified service already exists" 162 | 163 | [] 164 | let ``Uninstall service``() = 165 | createEnviroment "serviceEnv" 166 | createProcess "serviceEnv" "install" 167 | wait() 168 | createProcess "serviceEnv" "uninstall" 169 | wait() 170 | result |> should contain "uninstalled" 171 | 172 | [] 173 | let ``Print guide for invalid param``() = 174 | createEnviroment "serviceEnv" 175 | createProcess "serviceEnv" "?" 176 | wait() 177 | result |> should contain "printGuide" 178 | 179 | [] 180 | let ``Print guide for no param``() = 181 | createEnviroment "serviceEnv" 182 | createProcess "serviceEnv" "" 183 | wait() 184 | result |> should contain "printGuide" 185 | 186 | interface IDisposable with 187 | member x.Dispose() = 188 | if process' <> null then 189 | if process'.HasExited = false then 190 | process'.Kill()|> ignore 191 | 192 | uninstallService() -------------------------------------------------------------------------------- /tests/Cronix.Tests/Startup.fsx: -------------------------------------------------------------------------------- 1 | #if INTERACTIVE 2 | #I "." 3 | #I "bin\Debug" 4 | #I "bin\Release" 5 | #r "Cronix.dll" 6 | #r "Chessie.dll" 7 | #endif 8 | 9 | namespace Cronix.Startup 10 | 11 | module RunAtStartup = 12 | 13 | open System.Threading 14 | open System 15 | open Cronix 16 | open Messages 17 | 18 | let sampleJob (token : CancellationToken) = 19 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 20 | Thread.Sleep 100 21 | 22 | let start (scheduler : IScheduleManager) = 23 | scheduler.ScheduleJob "job1" "* * * * *" <| JobCallback(sampleJob) |> ignore -------------------------------------------------------------------------------- /tests/Cronix.Tests/TriggerEnviroment.fs: -------------------------------------------------------------------------------- 1 | module TriggerEnviroment 2 | 3 | open System 4 | open System.Threading 5 | open Xunit 6 | open FsUnit.Xunit 7 | open Chessie.ErrorHandling 8 | open Cronix 9 | open Triggers 10 | 11 | 12 | let rec wait dateTime = 13 | let now = DateTime.UtcNow 14 | if now < dateTime then 15 | Console.WriteLine("now: {0}, datetime: {1}", now, dateTime) 16 | Thread.Sleep(5000) 17 | wait dateTime 18 | 19 | type TriggerStateCallback = delegate of unit -> TriggerState 20 | 21 | let rec waitForState (actual : TriggerStateCallback) (expected : TriggerState) = 22 | let invokedActual = actual.Invoke() 23 | let pa = invokedActual.ToString() 24 | let pe = expected.ToString() 25 | printf "actual: %s expected %s" pa pe |> ignore 26 | if invokedActual <> expected then 27 | Thread.Sleep(100) 28 | waitForState actual expected |> ignore 29 | 30 | let tokenSource = new CancellationTokenSource() 31 | 32 | (* Callbacks *) 33 | 34 | let callback (token : CancellationToken) = 35 | printf "callback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 36 | Thread.Sleep 100 37 | 38 | let overlapCallback (token : CancellationToken) = 39 | printf "overlapCallback executed at (UTC) %s\n" <| DateTime.UtcNow.ToString() 40 | Thread.Sleep (60 * 1000 * 2) // 2minutes 41 | 42 | let throwingCallback (token : CancellationToken) = 43 | Thread.Sleep 500 44 | failwith "exception message" 45 | 46 | let cancellingCallback (token : CancellationToken) = 47 | Thread.Sleep 500 48 | raise (OperationCanceledException()) -------------------------------------------------------------------------------- /tests/Cronix.Tests/TriggerTests.fs: -------------------------------------------------------------------------------- 1 | namespace Cronix.Tests 2 | 3 | open Xunit 4 | open FsUnit.Xunit 5 | open System.Threading 6 | open System 7 | open System.Collections.Concurrent 8 | 9 | open Cronix 10 | open Triggers 11 | open System.Threading 12 | open NCrontab 13 | open System 14 | open Logging 15 | open TriggerEnviroment 16 | 17 | 18 | [] 19 | type TriggerTests() = 20 | 21 | let date = new DateTime(2015, 02, 05, 20, 52, 1) 22 | let expr = "* * * * *" 23 | 24 | [] 25 | let ``Should return occurrenceAt`` () = 26 | calculatetOccurrenceAt(expr, date) |> should equal <| new DateTime(2015, 02, 05, 20, 53, 0) 27 | 28 | [] 29 | let ``Should return changeArgs`` () = 30 | let due, interval = calculateTimerArgs(expr, date, calculatetOccurrenceAt(expr, date)) 31 | 32 | due |> should equal 59000 33 | interval |> should equal 60000 34 | 35 | [] 36 | let ``Should not throw on failing jobs`` () = 37 | timerCallback("trowing exception", JobCallback(throwingCallback), tokenSource.Token) 38 | timerCallback("throwing OperationCanceledException from job", JobCallback(cancellingCallback), tokenSource.Token) 39 | tokenSource.Cancel() 40 | timerCallback("throwing OperationCanceledException from timerCallback", JobCallback(callback), tokenSource.Token) 41 | 42 | 43 | [] 44 | type TriggerSystemTest() = 45 | 46 | let trigger = new Trigger("job#1", "* * * * *", JobCallback(callback)) :> ITrigger 47 | let overlapTrigger = new Trigger("job#1", "* * * * *", JobCallback(overlapCallback)) :> ITrigger 48 | let throwingTrigger = new Trigger("job#1", "* * * * *", JobCallback(throwingCallback)) :> ITrigger 49 | let cancellingTrigger = new Trigger("job#1", "* * * * *", JobCallback(cancellingCallback)) :> ITrigger 50 | 51 | let triggerStateCallback = TriggerStateCallback(fun _ -> trigger.State) 52 | let throwingStateCallback = TriggerStateCallback(fun _ -> trigger.State) 53 | 54 | [] 55 | let ``Should not throw on failing jobs`` () = 56 | throwingTrigger.Fire() |> ignore 57 | cancellingTrigger.Fire() |> ignore 58 | 59 | [] 60 | let ``Should handle overlaping jobs`` () = 61 | overlapTrigger.Start() |> ignore 62 | let firstOccurance = overlapTrigger.OccurrenceAt 63 | wait firstOccurance 64 | 65 | let secondOccurance = overlapTrigger.OccurrenceAt 66 | wait secondOccurance 67 | 68 | Console.WriteLine("firstOccurance: {0}", firstOccurance) 69 | Console.WriteLine("secondOccurance: {0}", secondOccurance) 70 | Console.WriteLine("diff: {0}", (secondOccurance - firstOccurance)) 71 | 72 | (secondOccurance - firstOccurance).Minutes |> should equal 1 73 | overlapTrigger.Terminate() 74 | 75 | [] 76 | let ``Should change the state durring execution`` () = 77 | waitForState triggerStateCallback Stopped 78 | trigger.Start() |> ignore 79 | waitForState triggerStateCallback Idle 80 | waitForState triggerStateCallback Executing 81 | trigger.Stop() |> ignore 82 | waitForState triggerStateCallback Stopped 83 | trigger.Terminate() |> ignore 84 | waitForState triggerStateCallback Terminated 85 | 86 | [] 87 | let ``Should not allow to enable / disable/ fire terminate when trigger is disposed`` () = 88 | trigger.Start() |> ignore 89 | trigger.Dispose() |> ignore 90 | waitForState triggerStateCallback Terminated 91 | trigger.Start() |> ignore 92 | waitForState triggerStateCallback Terminated 93 | trigger.Stop() |> ignore 94 | waitForState triggerStateCallback Terminated 95 | trigger.Terminate() |> ignore 96 | waitForState triggerStateCallback Terminated 97 | trigger.Fire() |> ignore 98 | waitForState triggerStateCallback Terminated 99 | 100 | [] 101 | let ``Should be able to Fire when terminated``() = 102 | trigger.Terminate() |> ignore 103 | trigger.Fire() |> ignore 104 | trigger.State |> should equal Terminated 105 | 106 | [] 107 | let ``Should not change current state when Firing trigger`` () = 108 | trigger.State |> should equal Stopped 109 | trigger.Fire() |> ignore 110 | trigger.State |> should equal Stopped -------------------------------------------------------------------------------- /tests/Cronix.Tests/paket.references: -------------------------------------------------------------------------------- 1 | xUnit 2 | xunit.runner.visualstudio 3 | FsUnit.xUnit 4 | NLog 5 | NCronTab 6 | chessie 7 | Microsoft.AspNet.SignalR.Core 8 | Microsoft.AspNet.SignalR.SystemWeb 9 | -------------------------------------------------------------------------------- /todo.txt: -------------------------------------------------------------------------------- 1 | pending: 2 | - mono support (raspberrypi2?; build server, bitcoin ticker) 3 | - installation as a service under linux 4 | - logs preview in app 5 | - loggary integration 6 | - more samples 7 | - videos, tutorials 8 | 9 | - hrmsc - exchange rate notifier 10 | 11 | - bitcoin ticker project (SAMPLE) 12 | -- sql provider 13 | -- configuration provider 14 | -- json provider 15 | -- appconfig provider 16 | -- based on cronix 17 | -- (get data job) 18 | -- (clean up data job) 19 | -- (sql tune job) 20 | + documentation + manual 21 | 22 | 0.3.3 23 | ----------------------- 24 | Bug fixes: 25 | - FT: layout fixes in distro 26 | 27 | Todo: 28 | - configure CI to compile only from master 29 | 30 | 0.3.2 31 | ----------------------- 32 | - FT: fix font problem 33 | 34 | 35 | Documentation: 36 | Sample Series: 37 | - bitcoin ticker 38 | - hrmsc exchange rate notifier 39 | 40 | Video: 41 | - (live) creating a cronix service 42 | 43 | * Add 'user voice' to github page 44 | 45 | Todo: 46 | - stable build 47 | 48 | Promotion: 49 | - linkedin groups 50 | - facebook groups 51 | - F# groups (fpfish, reddit, twiteer) 52 | 53 | 54 | 55 | --------------------------------------------------------------------------------